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 host
4
5import (
6 "errors"
7 "io"
8 "os"
9 "path/filepath"
10 "strings"
11
12 "github.com/carlosframework/platform/internal/hostbox"
13 "github.com/carlosframework/platform/internal/instancefs"
14 "github.com/carlosframework/platform/internal/provision"
15 "github.com/carlosframework/platform/internal/rootstate"
16 "golang.org/x/sys/unix"
17)
18
19type servingParkNamespace struct {
20 Topology *servingParkTopology `json:",omitempty"`
21 BootID string `json:",omitempty"`
22 Version int
23 Parent, Root servingArtifactDirectory
24}
25
26type servingParkFS struct {
27 data string
28 state rootstate.Store
29 owner uint32
30 mount func(string) (bool, error)
31 boot func() (string, error)
32 volume func(uint64) string
33 handle func(*os.File) (servingParkPersistentDirectory, error)
34 rename func(int, string, int, string, uint) error
35 sync func(int) error
36 unlink func(int, string, int) error
37}
38
39// Production construction always requires root and a nosymfollow mount. Tests
40// use the private fields to exercise filesystems without mount privileges.
41func newServingParkFS(data string, state rootstate.Store) *servingParkFS {
42 return &servingParkFS{data: data, state: state, owner: 0, mount: instancefs.MountNoSymfollow, boot: servingParkBootID, handle: servingParkPersistentDir, volume: servingParkVolume, rename: unix.Renameat2, sync: unix.Fsync, unlink: unix.Unlinkat}
43}
44
45func servingParkDirID(f *os.File) (servingArtifactDirectory, error) {
46 var st unix.Statx_t
47 if err := unix.Statx(int(f.Fd()), "", unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, unix.STATX_BASIC_STATS|unix.STATX_MNT_ID, &st); err != nil {
48 return servingArtifactDirectory{}, err
49 }
50 if st.Mask&unix.STATX_MNT_ID == 0 || st.Mode&unix.S_IFMT != unix.S_IFDIR || st.Ino == 0 || st.Mnt_id == 0 {
51 return servingArtifactDirectory{}, hostbox.ErrChanged
52 }
53 return servingArtifactDirectory{Device: unix.Mkdev(st.Dev_major, st.Dev_minor), Inode: st.Ino, Mount: st.Mnt_id}, nil
54}
55
56func (f *servingParkFS) parent() (*os.File, servingArtifactDirectory, error) {
57 var zero servingArtifactDirectory
58 if f == nil || f.state == nil || f.mount == nil || f.rename == nil || f.sync == nil || f.unlink == nil || !filepath.IsAbs(f.data) || filepath.Clean(f.data) != f.data || uint32(os.Geteuid()) != f.owner {
59 return nil, zero, hostbox.ErrUnsupported
60 }
61 path := filepath.Join(f.data, "instances")
62 p, err := instancefs.OpenDirBeneath(path, ".")
63 if err != nil {
64 return nil, zero, err
65 }
66 id, err := servingParkDirID(p)
67 if err != nil {
68 p.Close()
69 return nil, zero, err
70 }
71 ok, err := f.mount(path)
72 if err != nil || !ok {
73 p.Close()
74 return nil, zero, hostbox.ErrChanged
75 }
76 // The pathname's effective mount must still name the pinned descriptor.
77 again, err := instancefs.OpenDirBeneath(path, ".")
78 if err != nil {
79 p.Close()
80 return nil, zero, err
81 }
82 next, err := servingParkDirID(again)
83 again.Close()
84 if err != nil || next != id {
85 p.Close()
86 return nil, zero, hostbox.ErrChanged
87 }
88 return p, id, nil
89}
90
91func servingParkOpenDir(parent *os.File, name string) (*os.File, error) {
92 if name == "" || name == "." || name == ".." || strings.ContainsAny(name, "/\\\x00") {
93 return nil, hostbox.ErrChanged
94 }
95 fd, err := unix.Openat(int(parent.Fd()), name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
96 if err != nil {
97 return nil, err
98 }
99 return os.NewFile(uintptr(fd), name), nil
100}
101
102func (f *servingParkFS) privateDir(parent *os.File, name string) (*os.File, servingArtifactDirectory, error) {
103 var zero servingArtifactDirectory
104 d, err := servingParkOpenDir(parent, name)
105 if err != nil {
106 return nil, zero, err
107 }
108 var st unix.Stat_t
109 err = unix.Fstat(int(d.Fd()), &st)
110 if err != nil || st.Uid != f.owner || st.Mode&0077 != 0 {
111 d.Close()
112 return nil, zero, hostbox.ErrChanged
113 }
114 id, err := servingParkDirID(d)
115 if err != nil {
116 d.Close()
117 return nil, zero, err
118 }
119 return d, id, nil
120}
121
122// namespace refuses every unrecorded obstruction. Quarantine recovery must be
123// completed before a production caller can recover an unknown park root.
124func (f *servingParkFS) namespace() (*os.File, servingParkNamespace, error) {
125 var n servingParkNamespace
126 boot, err := f.currentBoot()
127 if err != nil {
128 return nil, n, err
129 }
130 parent, id, err := f.parent()
131 if err != nil {
132 return nil, n, err
133 }
134 defer parent.Close()
135 n, err = f.readNamespace()
136 if errors.Is(err, os.ErrNotExist) {
137 p, e := f.namespaceParent()
138 if e == nil && (p.Parent != id || f.checkBoot(p.BootID) != nil) {
139 return nil, n, hostbox.ErrChanged
140 }
141 if e != nil && !errors.Is(e, os.ErrNotExist) {
142 return nil, n, e
143 }
144 if err = f.checkNamespaceQuarantine(n); err != nil {
145 return nil, n, err
146 }
147 // EEXIST is deliberately not adoption authority for a service-owned name.
148 if err = unix.Mkdirat(int(parent.Fd()), ".serving-parked", 0700); err != nil {
149 return nil, n, err
150 }
151 d, rid, e := f.privateDir(parent, ".serving-parked")
152 if e != nil {
153 return nil, n, e
154 }
155 if rid.Device != id.Device || rid.Mount != id.Mount {
156 d.Close()
157 return nil, n, hostbox.ErrChanged
158 }
159 n = servingParkNamespace{Version: 1, Parent: id, Root: rid, BootID: boot}
160 n.Topology, e = f.captureTopology(parent, d, n)
161 if e != nil {
162 d.Close()
163 return nil, n, e
164 }
165 if e = f.sync(int(parent.Fd())); e == nil {
166 l := servingParkLedger{state: f.state}
167 e = l.write("namespace.json", n)
168 }
169 if e != nil {
170 d.Close()
171 return nil, n, e
172 }
173 return d, n, nil
174 }
175 if err != nil {
176 return nil, n, err
177 }
178 if n.Version != 1 || n.Parent != id || f.checkBoot(n.BootID) != nil {
179 return nil, n, hostbox.ErrChanged
180 }
181 if err = f.checkNamespaceQuarantine(n); err != nil {
182 return nil, n, err
183 }
184 d, rid, err := f.privateDir(parent, ".serving-parked")
185 if err != nil {
186 return nil, n, err
187 }
188 if rid != n.Root || rid.Device != id.Device || rid.Mount != id.Mount {
189 d.Close()
190 return nil, n, hostbox.ErrChanged
191 }
192 return d, n, nil
193}
194
195func (f *servingParkFS) source(a servingParkAuthorization) (*os.File, servingArtifactDirectory, error) {
196 var zero servingArtifactDirectory
197 host := a.Successor.Host
198 if host == "" || host == "." || host == ".." || strings.ContainsAny(host, "/\\\x00") || len(host)+7 > 255 {
199 return nil, zero, hostbox.ErrChanged
200 }
201 p, pid, err := f.parent()
202 if err != nil {
203 return nil, zero, err
204 }
205 defer p.Close()
206 d, err := servingParkOpenDir(p, provision.InstanceID(host))
207 if err != nil {
208 return nil, zero, err
209 }
210 id, err := servingParkDirID(d)
211 if err != nil || id != a.Directory || id.Device != pid.Device || id.Mount != pid.Mount {
212 d.Close()
213 return nil, zero, hostbox.ErrChanged
214 }
215 return d, pid, nil
216}
217
218func servingParkStat(parent *os.File, name string) (servingParkFile, error) {
219 out := servingParkFile{Name: name}
220 var st unix.Statx_t
221 err := unix.Statx(int(parent.Fd()), name, unix.AT_SYMLINK_NOFOLLOW, unix.STATX_BASIC_STATS|unix.STATX_MNT_ID, &st)
222 if errors.Is(err, unix.ENOENT) {
223 return out, nil
224 }
225 if err != nil {
226 return out, err
227 }
228 if st.Mask&unix.STATX_MNT_ID == 0 || st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 || st.Ino == 0 {
229 return out, hostbox.ErrChanged
230 }
231 // Allocated bytes are the upper bound reclaimable by removing this link;
232 // pressure callers must still remeasure f_bavail after deleting it.
233 if st.Blocks > ^uint64(0)/512 {
234 return out, hostbox.ErrChanged
235 }
236 out.Present, out.Device, out.Inode, out.Mount, out.Bytes, out.Mode, out.Links = true, unix.Mkdev(st.Dev_major, st.Dev_minor), st.Ino, st.Mnt_id, st.Blocks*512, uint32(st.Mode)&unix.S_IFMT, st.Nlink
237 out.Persistent, err = servingParkPersistentArtifact(parent, out)
238 return out, err
239}
240
241func sameServingParkFile(a, b servingParkFile) bool {
242 return a.Name == b.Name && a.Present == b.Present && a.Device == b.Device && a.Inode == b.Inode && a.Mount == b.Mount && a.Mode == b.Mode && a.Links == b.Links && a.Bytes == b.Bytes && (a.Persistent == (servingParkPersistentFile{}) || a.Persistent == b.Persistent)
243}
244
245func (f *servingParkFS) Capture(a servingParkAuthorization) (servingParkSource, error) {
246 var out servingParkSource
247 source, parent, err := f.source(a)
248 if err != nil {
249 return out, err
250 }
251 defer source.Close()
252 if f.handle == nil || f.volume == nil {
253 return out, hostbox.ErrUnsupported
254 }
255 out.Directory, out.Parent = a.Directory, parent
256 p, pid, err := f.parent()
257 if err != nil {
258 return out, err
259 }
260 defer p.Close()
261 if pid != parent {
262 return out, hostbox.ErrChanged
263 }
264 out.ParentPersistent, err = f.handle(p)
265 if err != nil {
266 return out, err
267 }
268 out.Volume = f.volume(parent.Device)
269 out.BootID, err = f.currentBoot()
270 if err != nil {
271 return out, err
272 }
273 if f.handle == nil {
274 return out, hostbox.ErrUnsupported
275 }
276 out.Persistent, err = f.handle(source)
277 if err != nil {
278 return out, err
279 }
280 if !out.Persistent.valid() {
281 return out, hostbox.ErrChanged
282 }
283 for i, suffix := range []string{"", ".gen", "-wal", "-shm"} {
284 file, err := servingParkStat(source, a.Successor.Host+".db"+suffix)
285 if err != nil {
286 return out, err
287 }
288 if file.Present && (file.Device != parent.Device || file.Mount != parent.Mount) {
289 return out, hostbox.ErrChanged
290 }
291 out.Files[i] = file
292 }
293 marker := out.Files[1]
294 if marker.Present {
295 if a.Legacy {
296 return out, hostbox.ErrChanged
297 }
298 fd, err := unix.Openat(int(source.Fd()), marker.Name, unix.O_RDONLY|unix.O_NONBLOCK|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
299 if err != nil {
300 return out, err
301 }
302 mf := os.NewFile(uintptr(fd), marker.Name)
303 var st unix.Stat_t
304 if err = unix.Fstat(fd, &st); err != nil || uint64(st.Dev) != marker.Device || st.Ino != marker.Inode || st.Mode&unix.S_IFMT != unix.S_IFREG || st.Nlink != 1 {
305 mf.Close()
306 return out, hostbox.ErrChanged
307 }
308 body, err := io.ReadAll(io.LimitReader(mf, 4097))
309 mf.Close()
310 if err != nil || len(body) > 4096 || strings.TrimSpace(string(body)) != a.Predecessor.Generation {
311 return out, hostbox.ErrChanged
312 }
313 again, err := servingParkStat(source, marker.Name)
314 if err != nil || !sameServingParkFile(marker, again) {
315 return out, hostbox.ErrChanged
316 }
317 }
318 if f.checkBoot(out.BootID) != nil {
319 return out, hostbox.ErrChanged
320 }
321 again, pid, err := f.source(a)
322 if err != nil {
323 return out, err
324 }
325 defer again.Close()
326 persistent, err := f.handle(again)
327 if err != nil || pid != out.Parent || persistent != out.Persistent {
328 return out, hostbox.ErrChanged
329 }
330 return out, nil
331}
332
333func (f *servingParkFS) event(r *servingParkRecord) (*os.File, *os.File, error) {
334 root, n, err := f.eventRoot(r)
335 if err != nil {
336 return nil, nil, err
337 }
338 if n.Parent != r.Source.Parent {
339 root.Close()
340 return nil, nil, hostbox.ErrChanged
341 }
342 event, id, err := f.privateDir(root, r.Authorization.ID)
343 // Older records did not pin their namespace. Their exact event must still
344 // exist before they can be associated with the prior recorded root.
345 if (err != nil || id != r.Event) && r.Prepared && r.Namespace == (servingArtifactDirectory{}) {
346 if event != nil {
347 event.Close()
348 }
349 root.Close()
350 root, n, err = f.displacedRoot(r)
351 if err != nil {
352 return nil, nil, err
353 }
354 event, id, err = f.privateDir(root, r.Authorization.ID)
355 }
356 if err != nil {
357 root.Close()
358 return nil, nil, err
359 }
360 if !r.Prepared || id != r.Event || id.Device != n.Root.Device || id.Mount != n.Root.Mount {
361 root.Close()
362 event.Close()
363 return nil, nil, hostbox.ErrChanged
364 }
365 return root, event, nil
366}
367
368func (f *servingParkFS) Prepare(r *servingParkRecord, save func() error) error {
369 if err := f.checkRecordBoot(r); err != nil {
370 return err
371 }
372 if r.Prepared {
373 root, event, err := f.event(r)
374 if err != nil {
375 return err
376 }
377 defer root.Close()
378 defer event.Close()
379 if err := f.sync(int(root.Fd())); err != nil {
380 return err
381 }
382 return f.recordEventRoot(r, root, save)
383 }
384 root, n, err := f.namespace()
385 if err != nil {
386 return err
387 }
388 defer root.Close()
389 if n.Parent != r.Source.Parent {
390 return hostbox.ErrChanged
391 }
392 if err = unix.Mkdirat(int(root.Fd()), r.Authorization.ID, 0700); err != nil && !errors.Is(err, unix.EEXIST) {
393 return err
394 }
395 event, id, err := f.privateDir(root, r.Authorization.ID)
396 if err != nil {
397 return err
398 }
399 defer event.Close()
400 if id.Device != n.Root.Device || id.Mount != n.Root.Mount {
401 return hostbox.ErrChanged
402 }
403 // An event exists only under a recorded root-private namespace and a durable
404 // intent. Before its identity is recorded it must be entirely empty.
405 names, err := event.Readdirnames(1)
406 if err != nil && !errors.Is(err, io.EOF) {
407 return err
408 }
409 if len(names) != 0 {
410 return hostbox.ErrChanged
411 }
412 if err = f.sync(int(root.Fd())); err != nil {
413 return err
414 }
415 if r.Source.Persistent != (servingParkPersistentDirectory{}) {
416 if f.checkBoot(r.Source.BootID) != nil || f.handle == nil {
417 return hostbox.ErrChanged
418 }
419 persistent, e := f.handle(event)
420 if e != nil {
421 return e
422 }
423 if !persistent.valid() || persistent.Owner != f.owner || persistent.Mode&0077 != 0 || persistent.Filesystem != r.Source.Persistent.Filesystem {
424 return hostbox.ErrChanged
425 }
426 r.EventPersistent = persistent
427 }
428 r.Event, r.Prepared, r.Namespace = id, true, n.Root
429 return save()
430}
431
432func (f *servingParkFS) Move(r *servingParkRecord, save func() error) error {
433 if err := f.checkRecordBoot(r); err != nil {
434 return err
435 }
436 root, event, err := f.event(r)
437 if err != nil {
438 return err
439 }
440 defer root.Close()
441 defer event.Close()
442 source, parent, err := f.source(r.Authorization)
443 if err != nil {
444 return err
445 }
446 defer source.Close()
447 if parent != r.Source.Parent {
448 return hostbox.ErrChanged
449 }
450 // Check the complete set before moving any name.
451 for _, recorded := range r.Source.Files {
452 src, e := servingParkStat(source, recorded.Name)
453 if e != nil {
454 return e
455 }
456 dst, e := servingParkStat(event, recorded.Name)
457 if e != nil {
458 return e
459 }
460 if !recorded.Present {
461 if src.Present || dst.Present {
462 return hostbox.ErrChanged
463 }
464 } else if recorded.Moved {
465 if src.Present || !sameServingParkFile(recorded, dst) {
466 return hostbox.ErrChanged
467 }
468 } else if !((sameServingParkFile(recorded, src) && !dst.Present) || (!src.Present && sameServingParkFile(recorded, dst))) {
469 return hostbox.ErrChanged
470 }
471 }
472 for i := range r.Source.Files {
473 item := &r.Source.Files[i]
474 if !item.Present {
475 continue
476 }
477 src, e := servingParkStat(source, item.Name)
478 if e != nil {
479 return e
480 }
481 if src.Present {
482 if item.Moved || !sameServingParkFile(*item, src) {
483 return hostbox.ErrChanged
484 }
485 if e = f.rename(int(source.Fd()), item.Name, int(event.Fd()), item.Name, unix.RENAME_NOREPLACE); e != nil {
486 return e
487 }
488 }
489 // A replacement raced into rename may be retained here, never deleted.
490 dst, e := servingParkStat(event, item.Name)
491 if e != nil || !sameServingParkFile(*item, dst) {
492 return hostbox.ErrChanged
493 }
494 if e = f.sync(int(source.Fd())); e != nil {
495 return e
496 }
497 if e = f.sync(int(event.Fd())); e != nil {
498 return e
499 }
500 if !item.Moved {
501 item.Moved = true
502 if e = save(); e != nil {
503 return e
504 }
505 }
506 }
507 return nil
508}
509
510func (f *servingParkFS) Delete(r *servingParkRecord, save func() error) error {
511 if err := f.checkReapVolume(r); err != nil {
512 return err
513 }
514 if err := f.checkRecordBoot(r); err != nil {
515 return err
516 }
517 if r.EventRemoveIntent {
518 return f.removeEvent(r, save)
519 }
520 root, event, err := f.event(r)
521 if err != nil {
522 return err
523 }
524 defer root.Close()
525 defer event.Close()
526 // Validate all children before unlinking any one of them. There is no
527 // recursive deletion and no pathname derived from a registry row.
528 names, err := event.Readdirnames(-1)
529 if err != nil {
530 return err
531 }
532 expected := make(map[string]servingParkFile)
533 for _, item := range r.Source.Files {
534 if item.Present {
535 expected[item.Name] = item
536 }
537 }
538 for _, name := range names {
539 item, ok := expected[name]
540 if !ok || item.Deleted {
541 return hostbox.ErrChanged
542 }
543 actual, e := servingParkStat(event, name)
544 if e != nil || !sameServingParkFile(item, actual) {
545 return hostbox.ErrChanged
546 }
547 }
548 for _, item := range r.Source.Files {
549 if !item.Present {
550 continue
551 }
552 actual, e := servingParkStat(event, item.Name)
553 if e != nil {
554 return e
555 }
556 if item.Deleted && actual.Present || !actual.Present && !item.DeleteIntent || actual.Present && !sameServingParkFile(item, actual) {
557 return hostbox.ErrChanged
558 }
559 }
560 if err := f.recordEventRoot(r, root, save); err != nil {
561 return err
562 }
563 for i := range r.Source.Files {
564 item := &r.Source.Files[i]
565 if !item.Present {
566 continue
567 }
568 actual, e := servingParkStat(event, item.Name)
569 if e != nil {
570 return e
571 }
572 if item.Deleted {
573 if actual.Present {
574 return hostbox.ErrChanged
575 }
576 continue
577 }
578 if !actual.Present && !item.DeleteIntent {
579 return hostbox.ErrChanged
580 }
581 if actual.Present && !sameServingParkFile(*item, actual) {
582 return hostbox.ErrChanged
583 }
584 if !item.DeleteIntent {
585 item.DeleteIntent = true
586 if e = save(); e != nil {
587 return e
588 }
589 }
590 if actual.Present {
591 if e = f.unlink(int(event.Fd()), item.Name, 0); e != nil {
592 return e
593 }
594 }
595 if e = f.sync(int(event.Fd())); e != nil {
596 return e
597 }
598 item.Deleted = true
599 if e = save(); e != nil {
600 return e
601 }
602 }
603 return f.removeEvent(r, save)
604}
605
606func (f *servingParkFS) CheckParked(r *servingParkRecord) error {
607 if err := f.checkRecordBoot(r); err != nil {
608 return err
609 }
610 if r.EventRemoved {
611 if err := f.removedEvent(r); err != nil {
612 return err
613 }
614 source, parent, err := f.source(r.Authorization)
615 if err != nil {
616 return err
617 }
618 defer source.Close()
619 if parent != r.Source.Parent {
620 return hostbox.ErrChanged
621 }
622 for _, item := range r.Source.Files {
623 actual, err := servingParkStat(source, item.Name)
624 if err != nil || actual.Present {
625 return hostbox.ErrChanged
626 }
627 }
628 return nil
629 }
630 root, event, err := f.event(r)
631 if err != nil {
632 return err
633 }
634 defer root.Close()
635 defer event.Close()
636 source, parent, err := f.source(r.Authorization)
637 if err != nil {
638 return err
639 }
640 defer source.Close()
641 if parent != r.Source.Parent {
642 return hostbox.ErrChanged
643 }
644 expected := make(map[string]bool)
645 for _, item := range r.Source.Files {
646 src, e := servingParkStat(source, item.Name)
647 if e != nil || src.Present {
648 return hostbox.ErrChanged
649 }
650 dst, e := servingParkStat(event, item.Name)
651 if e != nil {
652 return e
653 }
654 if item.Present && !item.Deleted {
655 if !sameServingParkFile(item, dst) {
656 return hostbox.ErrChanged
657 }
658 expected[item.Name] = true
659 } else if dst.Present {
660 return hostbox.ErrChanged
661 }
662 }
663 names, err := event.Readdirnames(-1)
664 if err != nil {
665 return err
666 }
667 for _, name := range names {
668 if !expected[name] {
669 return hostbox.ErrChanged
670 }
671 }
672 return nil
673}
674
675// removeEvent removes only a recorded empty event. The durable intent makes
676// absence after a lost unlink reply replayable; it never licenses a replacement.
677func (f *servingParkFS) removeEvent(r *servingParkRecord, save func() error) error {
678 if err := f.checkReapVolume(r); err != nil {
679 return err
680 }
681 if !r.Deleting || !r.Prepared || !r.Parked {
682 return hostbox.ErrChanged
683 }
684 for _, item := range r.Source.Files {
685 if item.Present && !item.Deleted {
686 return hostbox.ErrChanged
687 }
688 }
689 root, n, err := f.eventRoot(r)
690 if err != nil {
691 return err
692 }
693 defer root.Close()
694 if n.Parent != r.Source.Parent {
695 return hostbox.ErrChanged
696 }
697 event, id, err := f.privateDir(root, r.Authorization.ID)
698 if errors.Is(err, unix.ENOENT) {
699 if !r.EventRemoveIntent {
700 return hostbox.ErrChanged
701 }
702 } else if err != nil {
703 return err
704 } else {
705 defer event.Close()
706 if r.EventRemoved || id != r.Event || id.Device != n.Root.Device || id.Mount != n.Root.Mount {
707 return hostbox.ErrChanged
708 }
709 names, e := event.Readdirnames(1)
710 if e != nil && !errors.Is(e, io.EOF) {
711 return e
712 }
713 if len(names) != 0 {
714 return hostbox.ErrChanged
715 }
716 if !r.EventRemoveIntent {
717 r.EventRemoveIntent = true
718 if err = save(); err != nil {
719 return err
720 }
721 }
722 // Root owns the parent and namespace lock, including during recovery.
723 if err = f.unlink(int(root.Fd()), r.Authorization.ID, unix.AT_REMOVEDIR); err != nil {
724 return err
725 }
726 }
727 if err = f.sync(int(root.Fd())); err != nil {
728 return err
729 }
730 if !r.EventRemoved {
731 r.EventRemoved = true
732 return save()
733 }
734 return nil
735}
736
737func (f *servingParkFS) removedEvent(r *servingParkRecord) error {
738 if !r.EventRemoveIntent || !r.EventRemoved {
739 return hostbox.ErrChanged
740 }
741 root, n, err := f.eventRoot(r)
742 if err != nil {
743 return err
744 }
745 defer root.Close()
746 if n.Parent != r.Source.Parent {
747 return hostbox.ErrChanged
748 }
749 d, _, err := f.privateDir(root, r.Authorization.ID)
750 if err == nil {
751 d.Close()
752 return hostbox.ErrChanged
753 }
754 if !errors.Is(err, unix.ENOENT) {
755 return err
756 }
757 return nil
758}
759