| 1 | //go:build linux |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "errors" |
| 8 | "fmt" |
| 9 | "os" |
| 10 | "path/filepath" |
| 11 | "reflect" |
| 12 | |
| 13 | "github.com/carlosframework/platform/internal/hostbox" |
| 14 | "github.com/carlosframework/platform/internal/hostguard" |
| 15 | ) |
| 16 | |
| 17 | // systemd removes an edge unit's empty delegated subtree after SIGKILL. A |
| 18 | // seed that never reserved a tenant role can settle that absence in the same |
| 19 | // boot. This path neither kills a replacement group nor invents a reboot. |
| 20 | func (k LinuxStopKernel) recoverUnlaunchedAbsent(ctx context.Context, t hostguard.FenceTarget, in StopInventory, j hostbox.StopKernelJournal, journal interface { |
| 21 | SettleRecovered(context.Context, hostguard.FenceTarget) error |
| 22 | }) (StopKernelEvidence, DrainAcknowledgement, error) { |
| 23 | refuse := func(err error) (StopKernelEvidence, DrainAcknowledgement, error) { |
| 24 | return StopKernelEvidence{}, DrainAcknowledgement{}, err |
| 25 | } |
| 26 | if j.V != 1 || j.PendingRoles || len(j.Cgroups) != 1 || len(j.UnitReservationIDs) != 0 { |
| 27 | return refuse(ErrClaimStopInventory) |
| 28 | } |
| 29 | raw, ok := k.Journal.(interface { |
| 30 | Read(context.Context, hostguard.FenceTarget) (hostbox.LaunchInventory, error) |
| 31 | }) |
| 32 | if !ok { |
| 33 | return refuse(ErrClaimStopInventory) |
| 34 | } |
| 35 | launch, err := raw.Read(ctx, t) |
| 36 | if err != nil { |
| 37 | return refuse(err) |
| 38 | } |
| 39 | if len(launch.Reservations) != 0 || len(launch.Groups) != 1 { |
| 40 | return refuse(ErrClaimStopInventory) |
| 41 | } |
| 42 | paths, err := k.stopDescriptorPaths(in) |
| 43 | if err != nil { |
| 44 | return refuse(err) |
| 45 | } |
| 46 | check := func(expected hostbox.StopKernelJournal) error { |
| 47 | fresh, err := k.readContainedJournal(ctx, t) |
| 48 | if err != nil { |
| 49 | return err |
| 50 | } |
| 51 | if !reflect.DeepEqual(fresh, expected) { |
| 52 | return ErrClaimStopInventory |
| 53 | } |
| 54 | again, err := raw.Read(ctx, t) |
| 55 | if err != nil { |
| 56 | return err |
| 57 | } |
| 58 | if len(again.Reservations) != 0 || !reflect.DeepEqual(again.Groups, launch.Groups) { |
| 59 | return ErrClaimStopInventory |
| 60 | } |
| 61 | boot, err := recoveryControllerAt(k.ProcRoot, t, fresh) |
| 62 | if err != nil { |
| 63 | return err |
| 64 | } |
| 65 | if boot != t.BootID { |
| 66 | return ErrClaimStopIdentity |
| 67 | } |
| 68 | if _, err := os.Lstat(filepath.Join(k.CgroupRoot, j.Cgroups[0].RelativePath)); !errors.Is(err, os.ErrNotExist) { |
| 69 | if err != nil { |
| 70 | return err |
| 71 | } |
| 72 | return ErrClaimStopInventory |
| 73 | } |
| 74 | return scanStopDescriptors(ctx, k.ProcRoot, j.Files, paths) |
| 75 | } |
| 76 | if err := check(j); err != nil { |
| 77 | return refuse(err) |
| 78 | } |
| 79 | if err := journal.SettleRecovered(ctx, t); err != nil { |
| 80 | return refuse(err) |
| 81 | } |
| 82 | settled := j |
| 83 | settled.Settled = true |
| 84 | if err := check(settled); err != nil { |
| 85 | return refuse(err) |
| 86 | } |
| 87 | group := j.Cgroups[0] |
| 88 | evidence := StopKernelEvidence{ObservedBootID: t.BootID, InventorySHA256: digestStop(in), LocalJournalSHA256: digestStop(settled), CgroupIdentities: []string{fmt.Sprintf("unlaunched-absent=%s;previous_identity=%d:%d;controller=%s", group.RelativePath, group.Device, group.Inode, t.ControllerID)}, UnitInvocationIDs: []string{}} |
| 89 | return evidence, DrainClaimAcknowledgement(t.FenceID, t.ControllerID, j.ProcessStart, j.ProducerIDs, j.Completed), nil |
| 90 | } |
| 91 | |