carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "os"
6 "strings"
7 "time"
8
9 "github.com/carlosframework/platform/internal/instancectl"
10 "github.com/carlosframework/platform/internal/rootstate"
11)
12
13type claimPredecessorKernel struct {
14 ctl func(context.Context, ...string) (string, error)
15 boot func() (string, error)
16 group func(string) (rootstate.FileIdentity, bool, error)
17}
18
19func (k claimPredecessorKernel) show(ctx context.Context, unit string) (map[string]string, error) {
20 if k.ctl == nil || k.group == nil || !instancectl.ValidBoundClaimUnit(unit) {
21 return nil, os.ErrPermission
22 }
23 out, err := k.ctl(ctx, "show", "-p", "ActiveState,InvocationID,ControlGroup,FragmentPath,DropInPaths", unit)
24 if err != nil {
25 return nil, err
26 }
27 props := map[string]string{}
28 for _, line := range strings.Split(out, "\n") {
29 if key, value, ok := strings.Cut(line, "="); ok {
30 if _, exists := props[key]; exists {
31 return nil, os.ErrPermission
32 }
33 props[key] = value
34 }
35 }
36 for _, key := range []string{"ActiveState", "InvocationID", "ControlGroup", "FragmentPath", "DropInPaths"} {
37 if _, ok := props[key]; !ok {
38 return nil, os.ErrPermission
39 }
40 }
41 return props, nil
42}
43
44func (k claimPredecessorKernel) capture(ctx context.Context, b claimUnitBinding) (claimUnitPredecessor, error) {
45 var p claimUnitPredecessor
46 props, err := k.show(ctx, b.Shape.Unit)
47 if err != nil {
48 return p, err
49 }
50 group := strings.TrimPrefix(props["ControlGroup"], "/")
51 if props["ActiveState"] != "active" || props["InvocationID"] == "" || !claimPredecessorPlacement(b.Shape.Unit, group) || props["ControlGroup"] != "/"+group || props["FragmentPath"] != "/etc/systemd/system/"+b.Shape.Unit || props["DropInPaths"] != "" {
52 return p, os.ErrPermission
53 }
54 id, populated, err := k.group(group)
55 if err != nil {
56 return p, err
57 }
58 if !populated || id == (rootstate.FileIdentity{}) {
59 return p, os.ErrPermission
60 }
61 again, err := k.show(ctx, b.Shape.Unit)
62 if err != nil {
63 return p, err
64 }
65 for key, value := range props {
66 if again[key] != value {
67 return p, os.ErrPermission
68 }
69 }
70 boot := ""
71 if k.boot != nil {
72 boot, err = k.boot()
73 if err != nil || boot == "" {
74 return p, os.ErrPermission
75 }
76 }
77 return claimUnitPredecessor{Unit: b.Shape.Unit, Invocation: props["InvocationID"], Group: group, GroupIdentity: id, Boot: boot}, nil
78}
79
80func (k claimPredecessorKernel) stop(ctx context.Context, p claimUnitPredecessor) error {
81 if !p.valid(p.Unit) {
82 return os.ErrPermission
83 }
84 ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
85 defer cancel()
86 issued := false
87 for {
88 props, err := k.show(ctx, p.Unit)
89 if err != nil {
90 return err
91 }
92 if props["InvocationID"] != "" && props["InvocationID"] != p.Invocation {
93 return os.ErrPermission
94 }
95 id, populated, err := k.group(p.Group)
96 if err != nil {
97 return err
98 }
99 if k.boot != nil {
100 boot, e := k.boot()
101 if e != nil || boot == "" || p.Boot == "" {
102 return os.ErrPermission
103 }
104 if boot != p.Boot {
105 if (props["ActiveState"] == "inactive" || props["ActiveState"] == "failed") && !populated {
106 return nil
107 }
108 return os.ErrPermission
109 }
110 }
111 if id != (rootstate.FileIdentity{}) && id != p.GroupIdentity {
112 return os.ErrPermission
113 }
114 idle := props["ActiveState"] == "inactive" || props["ActiveState"] == "failed"
115 if idle && !populated {
116 return nil
117 }
118 if props["InvocationID"] != p.Invocation || props["ControlGroup"] != "/"+p.Group || id != p.GroupIdentity {
119 return os.ErrPermission
120 }
121 if !issued {
122 if _, err = k.ctl(ctx, "stop", p.Unit); err != nil {
123 return err
124 }
125 issued = true
126 continue
127 }
128 select {
129 case <-ctx.Done():
130 return ctx.Err()
131 case <-time.After(25 * time.Millisecond):
132 }
133 }
134}
135