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 hostbox
4
5import (
6 "context"
7 "errors"
8 "io"
9 "os"
10 "path/filepath"
11 "strings"
12
13 "github.com/carlosframework/platform/internal/hostguard"
14 "golang.org/x/sys/unix"
15)
16
17// ResolveRoleIntentions is called by root only after a durable fence and drain,
18// or proof that the recorded controller is dead. It captures only an empty
19// direct child of the recorded parent. Unknown descendants are never adopted.
20func (j *LaunchJournal) ResolveRoleIntentions(ctx context.Context, target hostguard.FenceTarget, root string, reboot bool) error {
21 in, err := j.Read(ctx, target)
22 if err != nil {
23 return err
24 }
25 for _, r := range in.Reservations {
26 if r.State != "intended" {
27 continue
28 }
29 group, err := resolveRoleIntention(root, r, reboot)
30 if err != nil {
31 return err
32 }
33 if err := j.mutate(ctx, target, func(current *LaunchInventory) error {
34 for i, old := range current.Reservations {
35 if old.ID != r.ID {
36 continue
37 }
38 if old != r {
39 return ErrChanged
40 }
41 current.Reservations[i].State = "settled"
42 if group != nil {
43 current.Reservations[i].Group = *group
44 current.Groups = append(current.Groups, *group)
45 }
46 return nil
47 }
48 return ErrChanged
49 }); err != nil {
50 return err
51 }
52 }
53 return nil
54}
55
56func resolveRoleIntention(root string, r LaunchReservation, reboot bool) (*LaunchGroup, error) {
57 if !filepath.IsAbs(root) || !managedName(r.Parent.RelativePath) || !managedName(r.Group.RelativePath) || filepath.Dir(r.Group.RelativePath) != r.Parent.RelativePath || r.Parent.Device == 0 || r.Parent.Inode == 0 || r.Group.Device != 0 || r.Group.Inode != 0 {
58 return nil, ErrTypedInventory
59 }
60 parentEntry, err := openParent(root, r.Parent.RelativePath, false)
61 if reboot && errors.Is(err, unix.ENOENT) {
62 return nil, nil
63 }
64 if err != nil {
65 return nil, err
66 }
67 defer unix.Close(parentEntry.dir)
68 fd, err := unix.Openat(parentEntry.dir, parentEntry.base, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
69 if reboot {
70 if errors.Is(err, unix.ENOENT) {
71 return nil, nil
72 }
73 if err == nil {
74 unix.Close(fd)
75 }
76 return nil, ErrTypedInventory
77 }
78 if err != nil {
79 return nil, err
80 }
81 defer unix.Close(fd)
82 var parent unix.Stat_t
83 if err = unix.Fstat(fd, &parent); err != nil {
84 return nil, err
85 }
86 if uint64(parent.Dev) != r.Parent.Device || parent.Ino != r.Parent.Inode {
87 return nil, ErrChanged
88 }
89 name := filepath.Base(r.Group.RelativePath)
90 childFD, err := unix.Openat(fd, name, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
91 if errors.Is(err, unix.ENOENT) {
92 return nil, nil
93 }
94 if err != nil {
95 return nil, err
96 }
97 child := os.NewFile(uintptr(childFD), name)
98 defer child.Close()
99 var st unix.Stat_t
100 if err = unix.Fstat(childFD, &st); err != nil {
101 return nil, err
102 }
103 eventsFD, err := unix.Openat(childFD, "cgroup.events", unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
104 if err != nil {
105 return nil, err
106 }
107 events := os.NewFile(uintptr(eventsFD), "cgroup.events")
108 data, err := io.ReadAll(io.LimitReader(events, 4097))
109 events.Close()
110 if err != nil || len(data) > 4096 {
111 return nil, ErrTypedInventory
112 }
113 seen := false
114 for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") {
115 fields := strings.Fields(line)
116 if len(fields) != 2 {
117 return nil, ErrTypedInventory
118 }
119 if fields[0] == "populated" {
120 if seen || fields[1] != "0" {
121 return nil, ErrTypedInventory
122 }
123 seen = true
124 }
125 }
126 if !seen {
127 return nil, ErrTypedInventory
128 }
129 entries, err := child.ReadDir(-1)
130 if err != nil {
131 return nil, err
132 }
133 for _, e := range entries {
134 if e.IsDir() || e.Type()&os.ModeSymlink != 0 {
135 return nil, ErrTypedInventory
136 }
137 }
138 var again unix.Stat_t
139 if err = unix.Fstatat(fd, name, &again, unix.AT_SYMLINK_NOFOLLOW); err != nil {
140 return nil, err
141 }
142 if again.Dev != st.Dev || again.Ino != st.Ino || again.Mode&unix.S_IFMT != unix.S_IFDIR {
143 return nil, ErrChanged
144 }
145 if err = unix.Fstatat(parentEntry.dir, parentEntry.base, &again, unix.AT_SYMLINK_NOFOLLOW); err != nil {
146 return nil, err
147 }
148 if again.Dev != parent.Dev || again.Ino != parent.Ino || again.Mode&unix.S_IFMT != unix.S_IFDIR {
149 return nil, ErrChanged
150 }
151 group := r.Group
152 group.Device = uint64(st.Dev)
153 group.Inode = st.Ino
154 return &group, nil
155}
156