package host import ( "bytes" "context" "encoding/json" "errors" "os" "path/filepath" "reflect" "sort" "strings" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/rootstate" ) func productionGenerationHandovers(ctx context.Context) ([]hostbox.GenerationHandover, error) { path := filepath.Join(claimRetireJournalDir("", ""), "serving") s, err := rootstate.Open(path, false, false) if errors.Is(err, os.ErrNotExist) { return nil, nil } if err != nil { return nil, err } defer s.Close() out, err := generationReadHandovers(ctx, s) if err != nil { return nil, err } directory, err := os.Open(path) if err != nil { return nil, err } defer directory.Close() if err = directory.Sync(); err != nil { return nil, err } return out, nil } func generationReadHandovers(ctx context.Context, s rootstate.Store) ([]hostbox.GenerationHandover, error) { names, err := s.Names() if err != nil { return nil, err } if len(names) > 8192 { return nil, os.ErrPermission } sort.Strings(names) var out []hostbox.GenerationHandover for _, name := range names { if err := ctx.Err(); err != nil { return nil, err } if strings.HasPrefix(name, ".handover-") { continue } raw, err := s.Read(name) if err != nil { return nil, err } j, err := hostbox.DecodeGenerationHandover(raw) if err != nil || name != j.ID+".json" { return nil, os.ErrPermission } out = append(out, j) } return out, nil } func generationTransfers(ctx context.Context, snapshot generationInventory) (map[int]int, error) { out := map[int]int{} for _, j := range snapshot.Handovers { if err := ctx.Err(); err != nil { return nil, err } raw, err := json.Marshal(j) if err != nil { return nil, err } if _, err = hostbox.DecodeGenerationHandover(raw); err != nil { return nil, err } p, n := j.Prior, j.Successor oldIndex, nextIndex := -1, -1 for i, payload := range snapshot.Payloads { in := payload.Inventory if in.Host != p.Host || in.Incarnation != p.Incarnation { continue } if in.InstallationStamp == p.Stamp { if oldIndex != -1 || j.ServingInstallation == nil || payload.Revision != j.ServingInstallation.InventoryRevision || !reflect.DeepEqual(in, j.Inventory) { return nil, os.ErrPermission } oldIndex = i } if in.InstallationStamp == n.Stamp { if nextIndex != -1 || in.Box != n.Box || in.Enrollment != n.Enrollment || in.DirectoryRevision != n.DirectoryRevision { return nil, os.ErrPermission } nextIndex = i } } if oldIndex < 0 || nextIndex < 0 { return nil, os.ErrPermission } if _, exists := out[oldIndex]; exists { return nil, os.ErrPermission } bound := false for _, view := range snapshot.Installations { if view.Host != p.Host { continue } in, err := generationInstallation(view, generationOwner(p)) if err != nil || rootHistoryInstallation(in) != rootHistoryInstallation(*j.ServingInstallation) { return nil, os.ErrPermission } retired := false for _, old := range view.Retired { if old == (hostguard.Captured{Incarnation: p.Incarnation, Stamp: p.Stamp}) { retired = true } } next := p next.Stamp, next.Session = n.Stamp, n.Session if _, err := generationInstallation(view, generationOwner(next)); err != nil || !retired { return nil, os.ErrPermission } receipt := p.Session == 0 && reflect.DeepEqual(j.Receipt, hostguard.FenceReceipt{}) for _, r := range view.Receipts { if r.Target == p && j.Receipt.Target == p && bytes.Equal(r.Bytes, j.Receipt.Bytes) && verifyRetirementReceipt(ctx, r) == nil { receipt = true } } if !receipt { return nil, os.ErrPermission } bound = true } if !bound { return nil, os.ErrPermission } next := snapshot.Payloads[nextIndex].Inventory for _, old := range j.Inventory.Entries { matches := 0 for _, entry := range next.Entries { if entry.Kind != old.Kind || entry.ManagedRoot != old.ManagedRoot || entry.RelativePath != old.RelativePath || entry.ArtifactID != old.ArtifactID { continue } if old.Materialization != "materialized" || entry.Materialization != "materialized" || old.Ownership != j.Before.Route.ClaimOwner || entry.Ownership != j.After.Route.ClaimOwner { return nil, os.ErrPermission } switch old.Kind { case "route": if old.RowIdentity != j.Before.Route.ClaimRowID || entry.RowIdentity != j.After.Route.ClaimRowID { return nil, os.ErrPermission } case "path": if old.Type != entry.Type || old.Inode != entry.Inode || (old.Identity.Boot != "" && !old.Identity.Same(entry.Identity)) || (old.Identity.Boot == "" && (old.Device != entry.Device || entry.Identity.Boot != p.BootID || entry.Identity.Device != entry.Device || entry.Identity.Inode != entry.Inode)) { return nil, os.ErrPermission } case "timer": found := false for i, timer := range j.Before.Timers { if old.RelativePath == p.Host+"/"+timer.Name && old.RowIdentity == timer.ClaimRowID && entry.RowIdentity == j.After.Timers[i].ClaimRowID { found = true } } if !found { return nil, os.ErrPermission } default: return nil, os.ErrPermission } matches++ } if matches != 1 { return nil, os.ErrPermission } } out[oldIndex] = nextIndex } return out, nil }