package host import ( "context" "os" "path/filepath" "strings" "github.com/carlosframework/platform/internal/registry" ) // Registry rows can contradict retained obligations, but cannot remove them. // Read them through the service-user parser, including on the final recheck. func productionGenerationRegistryInventory(ctx context.Context, snapshot generationInventory) (generationInventory, error) { reg, err := openServiceRegistryReadOnly(filepath.Join(dataDir(), "registry.db")) if err != nil { return generationInventory{}, err } defer reg.Close() return generationRegistryInventory(ctx, reg, snapshot) } func generationRegistryInventory(ctx context.Context, reg *registry.Registry, snapshot generationInventory) (generationInventory, error) { var zero generationInventory if reg == nil { return zero, os.ErrPermission } if err := generationRetainedTimers(snapshot); err != nil { return zero, err } retired, err := generationRetirements(ctx, snapshot) if err != nil { return zero, err } transferred, err := generationTransfers(ctx, snapshot) if err != nil { return zero, err } type key struct{ kind, host, name string } expected := map[key]registry.ClaimOwnership{} hosts := map[string]bool{} for index, p := range snapshot.Payloads { in := p.Inventory hosts[in.Host] = true if _, ok := transferred[index]; ok { continue } if retired[index] != "" { continue } for _, entry := range in.Entries { if entry.Kind != "route" && entry.Kind != "timer" { continue } name := "" if entry.Kind == "timer" { name = strings.TrimPrefix(entry.RelativePath, in.Host+"/") if name == "" || name == "." || name == ".." || strings.ContainsAny(name, "/\\") || entry.RelativePath != in.Host+"/"+name { return zero, os.ErrPermission } } else if entry.RelativePath != in.Host { return zero, os.ErrPermission } own := registry.ClaimOwnership{ClaimIncarnation: in.Incarnation, ClaimStamp: in.InstallationStamp, ClaimOwner: entry.Ownership, ClaimRowID: entry.RowIdentity} k := key{entry.Kind, in.Host, name} if !own.Valid() || entry.ManagedRoot != "registry" || entry.Materialization != "materialized" { return zero, os.ErrPermission } if _, exists := expected[k]; exists { return zero, os.ErrPermission } if entry.TimerRetired { continue } expected[k] = own if len(expected) > 8192 { return zero, os.ErrPermission } } } candidates, err := reg.ArtifactHosts() if err != nil { return zero, err } if len(candidates) > 8192 { return zero, os.ErrPermission } for _, host := range candidates { if !hosts[host] { return zero, os.ErrPermission } } for host := range hosts { if err := ctx.Err(); err != nil { return zero, err } route, err := reg.Get(host) if err != nil { return zero, err } k := key{"route", host, ""} own, exists := expected[k] if (route != nil) != exists { return zero, os.ErrPermission } if exists { if route.Host != host || route.ClaimOwnership != own { return zero, os.ErrPermission } delete(expected, k) } timers, err := reg.TimersFor(host) if err != nil { return zero, err } if len(timers) > 8192 { return zero, os.ErrPermission } for _, timer := range timers { k := key{"timer", host, timer.Name} own, exists := expected[k] if !exists || timer.Host != host || timer.ClaimOwnership != own || timer.Registration != own.ClaimRowID { return zero, os.ErrPermission } delete(expected, k) } } if len(expected) != 0 { return zero, os.ErrPermission } return snapshot, ctx.Err() }