//go:build linux package host import ( "context" "errors" "fmt" "os" "path/filepath" "reflect" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostguard" ) // systemd removes an edge unit's empty delegated subtree after SIGKILL. A // seed that never reserved a tenant role can settle that absence in the same // boot. This path neither kills a replacement group nor invents a reboot. func (k LinuxStopKernel) recoverUnlaunchedAbsent(ctx context.Context, t hostguard.FenceTarget, in StopInventory, j hostbox.StopKernelJournal, journal interface { SettleRecovered(context.Context, hostguard.FenceTarget) error }) (StopKernelEvidence, DrainAcknowledgement, error) { refuse := func(err error) (StopKernelEvidence, DrainAcknowledgement, error) { return StopKernelEvidence{}, DrainAcknowledgement{}, err } if j.V != 1 || j.PendingRoles || len(j.Cgroups) != 1 || len(j.UnitReservationIDs) != 0 { return refuse(ErrClaimStopInventory) } raw, ok := k.Journal.(interface { Read(context.Context, hostguard.FenceTarget) (hostbox.LaunchInventory, error) }) if !ok { return refuse(ErrClaimStopInventory) } launch, err := raw.Read(ctx, t) if err != nil { return refuse(err) } if len(launch.Reservations) != 0 || len(launch.Groups) != 1 { return refuse(ErrClaimStopInventory) } paths, err := k.stopDescriptorPaths(in) if err != nil { return refuse(err) } check := func(expected hostbox.StopKernelJournal) error { fresh, err := k.readContainedJournal(ctx, t) if err != nil { return err } if !reflect.DeepEqual(fresh, expected) { return ErrClaimStopInventory } again, err := raw.Read(ctx, t) if err != nil { return err } if len(again.Reservations) != 0 || !reflect.DeepEqual(again.Groups, launch.Groups) { return ErrClaimStopInventory } boot, err := recoveryControllerAt(k.ProcRoot, t, fresh) if err != nil { return err } if boot != t.BootID { return ErrClaimStopIdentity } if _, err := os.Lstat(filepath.Join(k.CgroupRoot, j.Cgroups[0].RelativePath)); !errors.Is(err, os.ErrNotExist) { if err != nil { return err } return ErrClaimStopInventory } return scanStopDescriptors(ctx, k.ProcRoot, j.Files, paths) } if err := check(j); err != nil { return refuse(err) } if err := journal.SettleRecovered(ctx, t); err != nil { return refuse(err) } settled := j settled.Settled = true if err := check(settled); err != nil { return refuse(err) } group := j.Cgroups[0] 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{}} return evidence, DrainClaimAcknowledgement(t.FenceID, t.ControllerID, j.ProcessStart, j.ProducerIDs, j.Completed), nil }