| 1 | //go:build linux |
| 2 | |
| 3 | package activator |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "os" |
| 8 | "os/exec" |
| 9 | "path/filepath" |
| 10 | "strconv" |
| 11 | "strings" |
| 12 | |
| 13 | "github.com/carlosframework/platform/internal/cgroup" |
| 14 | "github.com/carlosframework/platform/internal/hostbox" |
| 15 | "github.com/carlosframework/platform/internal/hostclaim" |
| 16 | "github.com/carlosframework/platform/internal/hostwrite" |
| 17 | "golang.org/x/sys/unix" |
| 18 | ) |
| 19 | |
| 20 | // Start never executes tenant code in the agent's namespace. With the |
| 21 | // isolated root protocol it asks root to run the reserved role in main's |
| 22 | // isolated unit; without it every shape stays refused. |
| 23 | func (l RoleLaunch) Start(ctx context.Context, role string, attempt hostclaim.WakeRetryAttempt, build func() *exec.Cmd, before func() error) (*exec.Cmd, *cgroup.Prepared, func(context.Context) error, RoleProducer, error) { |
| 24 | if l.Root == nil { |
| 25 | return nil, nil, nil, RoleProducer{}, ErrClaimIsolationUnavailable |
| 26 | } |
| 27 | return l.startIsolated(ctx, role, attempt, build, before) |
| 28 | } |
| 29 | |
| 30 | func roleProcessStart(pid int) (string, error) { |
| 31 | raw, err := os.ReadFile("/proc/" + strconv.Itoa(pid) + "/stat") |
| 32 | if err != nil { |
| 33 | return "", err |
| 34 | } |
| 35 | i := strings.LastIndexByte(string(raw), ')') |
| 36 | if i < 0 { |
| 37 | return "", hostwrite.ErrUnavailable |
| 38 | } |
| 39 | fields := strings.Fields(string(raw)[i+1:]) |
| 40 | if len(fields) < 20 || fields[0] == "Z" || fields[0] == "X" { |
| 41 | return "", hostwrite.ErrUnavailable |
| 42 | } |
| 43 | return fields[19], nil |
| 44 | } |
| 45 | func (l RoleLaunch) ExactHealth(ctx context.Context, cmd *exec.Cmd, a hostclaim.WakeRetryAttempt) error { |
| 46 | _, err := l.RoleCgroup(ctx, cmd, a) |
| 47 | return err |
| 48 | } |
| 49 | |
| 50 | // RoleCgroup returns the absolute cgroup directory of the started main role |
| 51 | // for attempt a after the same checks as ExactHealth: the journalled started |
| 52 | // reservation names cmd's pid and process start, its group still has the |
| 53 | // journalled device and inode, and the pid is a member of exactly that group. |
| 54 | func (l RoleLaunch) RoleCgroup(ctx context.Context, cmd *exec.Cmd, a hostclaim.WakeRetryAttempt) (string, error) { |
| 55 | if l.Journal == nil || cmd == nil || cmd.Process == nil || !filepath.IsAbs(l.CgroupRoot) { |
| 56 | return "", hostwrite.ErrUnavailable |
| 57 | } |
| 58 | inventory, err := l.Journal.Read(ctx, l.Target) |
| 59 | if err != nil { |
| 60 | return "", err |
| 61 | } |
| 62 | started := "started" |
| 63 | if l.Root != nil { |
| 64 | // A root-launched role is only ever a unit reservation root wrote. |
| 65 | started = hostbox.UnitStarted |
| 66 | } |
| 67 | for _, r := range inventory.Reservations { |
| 68 | if r.ID != a.ID+":main" || r.State != started || r.InvocationID != a.InvocationID || r.Inputs != a.LaunchInputs || r.PID != cmd.Process.Pid || r.Target.Session != a.Session { |
| 69 | continue |
| 70 | } |
| 71 | start, err := roleProcessStart(r.PID) |
| 72 | if err != nil { |
| 73 | return "", err |
| 74 | } |
| 75 | if start != r.ProcessStart { |
| 76 | return "", hostbox.ErrChanged |
| 77 | } |
| 78 | group := filepath.Join(l.CgroupRoot, r.Group.RelativePath) |
| 79 | var stat unix.Stat_t |
| 80 | if err = unix.Stat(group, &stat); err != nil { |
| 81 | return "", err |
| 82 | } |
| 83 | if uint64(stat.Dev) != r.Group.Device || stat.Ino != r.Group.Inode { |
| 84 | return "", hostbox.ErrChanged |
| 85 | } |
| 86 | rel, err := filepath.Rel("/sys/fs/cgroup", group) |
| 87 | if err != nil || strings.HasPrefix(rel, "..") { |
| 88 | return "", hostwrite.ErrUnavailable |
| 89 | } |
| 90 | raw, err := os.ReadFile("/proc/" + strconv.Itoa(r.PID) + "/cgroup") |
| 91 | if err != nil { |
| 92 | return "", err |
| 93 | } |
| 94 | found := false |
| 95 | for _, line := range strings.Split(string(raw), "\n") { |
| 96 | if line == "0::/"+rel { |
| 97 | found = true |
| 98 | } |
| 99 | } |
| 100 | if !found { |
| 101 | return "", hostbox.ErrChanged |
| 102 | } |
| 103 | return group, nil |
| 104 | } |
| 105 | return "", hostbox.ErrChanged |
| 106 | } |
| 107 | |