package host import ( "context" "os" "strings" "time" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/rootstate" ) type claimPredecessorKernel struct { ctl func(context.Context, ...string) (string, error) boot func() (string, error) group func(string) (rootstate.FileIdentity, bool, error) } func (k claimPredecessorKernel) show(ctx context.Context, unit string) (map[string]string, error) { if k.ctl == nil || k.group == nil || !instancectl.ValidBoundClaimUnit(unit) { return nil, os.ErrPermission } out, err := k.ctl(ctx, "show", "-p", "ActiveState,InvocationID,ControlGroup,FragmentPath,DropInPaths", unit) if err != nil { return nil, err } props := map[string]string{} for _, line := range strings.Split(out, "\n") { if key, value, ok := strings.Cut(line, "="); ok { if _, exists := props[key]; exists { return nil, os.ErrPermission } props[key] = value } } for _, key := range []string{"ActiveState", "InvocationID", "ControlGroup", "FragmentPath", "DropInPaths"} { if _, ok := props[key]; !ok { return nil, os.ErrPermission } } return props, nil } func (k claimPredecessorKernel) capture(ctx context.Context, b claimUnitBinding) (claimUnitPredecessor, error) { var p claimUnitPredecessor props, err := k.show(ctx, b.Shape.Unit) if err != nil { return p, err } group := strings.TrimPrefix(props["ControlGroup"], "/") if props["ActiveState"] != "active" || props["InvocationID"] == "" || !claimPredecessorPlacement(b.Shape.Unit, group) || props["ControlGroup"] != "/"+group || props["FragmentPath"] != "/etc/systemd/system/"+b.Shape.Unit || props["DropInPaths"] != "" { return p, os.ErrPermission } id, populated, err := k.group(group) if err != nil { return p, err } if !populated || id == (rootstate.FileIdentity{}) { return p, os.ErrPermission } again, err := k.show(ctx, b.Shape.Unit) if err != nil { return p, err } for key, value := range props { if again[key] != value { return p, os.ErrPermission } } boot := "" if k.boot != nil { boot, err = k.boot() if err != nil || boot == "" { return p, os.ErrPermission } } return claimUnitPredecessor{Unit: b.Shape.Unit, Invocation: props["InvocationID"], Group: group, GroupIdentity: id, Boot: boot}, nil } func (k claimPredecessorKernel) stop(ctx context.Context, p claimUnitPredecessor) error { if !p.valid(p.Unit) { return os.ErrPermission } ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() issued := false for { props, err := k.show(ctx, p.Unit) if err != nil { return err } if props["InvocationID"] != "" && props["InvocationID"] != p.Invocation { return os.ErrPermission } id, populated, err := k.group(p.Group) if err != nil { return err } if k.boot != nil { boot, e := k.boot() if e != nil || boot == "" || p.Boot == "" { return os.ErrPermission } if boot != p.Boot { if (props["ActiveState"] == "inactive" || props["ActiveState"] == "failed") && !populated { return nil } return os.ErrPermission } } if id != (rootstate.FileIdentity{}) && id != p.GroupIdentity { return os.ErrPermission } idle := props["ActiveState"] == "inactive" || props["ActiveState"] == "failed" if idle && !populated { return nil } if props["InvocationID"] != p.Invocation || props["ControlGroup"] != "/"+p.Group || id != p.GroupIdentity { return os.ErrPermission } if !issued { if _, err = k.ctl(ctx, "stop", p.Unit); err != nil { return err } issued = true continue } select { case <-ctx.Done(): return ctx.Err() case <-time.After(25 * time.Millisecond): } } }