package hostbox import ( "bytes" "context" "encoding/json" "io" "path/filepath" ) type GenerationPayload struct { Inventory StopInventory Revision uint64 } // GenerationPayloads reads both sides of the inventory/revision relation. // An inner join would silently discard an obligation with a missing revision. // The caller still has to bind these records to root installations and inspect // kernel references; Complete in a stored payload cannot establish that alone. func (j *LaunchJournal) GenerationPayloads(ctx context.Context) ([]GenerationPayload, error) { if j == nil || j.db == nil || j.remote != nil { return nil, ErrUnsupported } if err := journalIdentity(j.path, j.identity); err != nil { return nil, err } rows, err := journalExecutorFor(ctx, j.db).QueryContext(ctx, ` SELECT k.host,k.incarnation,k.stamp,i.body,v.revision FROM (SELECT host,incarnation,stamp FROM stop_inventory UNION SELECT host,incarnation,stamp FROM journal_inventory_versions) k LEFT JOIN stop_inventory i USING(host,incarnation,stamp) LEFT JOIN journal_inventory_versions v USING(host,incarnation,stamp) ORDER BY k.host,k.incarnation,k.stamp`) if err != nil { return nil, err } defer rows.Close() var out []GenerationPayload for rows.Next() { var host, incarnation string var stamp uint64 var raw []byte var p GenerationPayload if err := rows.Scan(&host, &incarnation, &stamp, &raw, &p.Revision); err != nil { return nil, err } if len(raw) > 1<<20 || len(out) >= 8192 { return nil, ErrTypedInventory } dec := json.NewDecoder(bytes.NewReader(raw)) dec.DisallowUnknownFields() if dec.Decode(&p.Inventory) != nil || dec.Decode(new(any)) != io.EOF { return nil, ErrTypedInventory } in := p.Inventory if !component(host) || incarnation == "" || stamp == 0 || p.Revision == 0 || in.Host != host || in.Incarnation != incarnation || in.InstallationStamp != stamp || !in.Complete || in.Box == "" || in.Enrollment == "" || in.DirectoryRevision == "" { return nil, ErrTypedInventory } ids := map[string]bool{} for _, e := range in.Entries { if !component(e.ArtifactID) || ids[e.ArtifactID] || !filepath.IsLocal(e.RelativePath) || e.Ownership == "" || (e.Materialization != "intended" && e.Materialization != "materialized") { return nil, ErrTypedInventory } ids[e.ArtifactID] = true switch e.Kind { case "route", "timer": if e.ManagedRoot != "registry" || e.RowIdentity == "" || e.Device != 0 || e.Inode != 0 || e.Type != 0 { return nil, ErrTypedInventory } case "path": if e.ManagedRoot != "data" || e.RowIdentity != "" || (e.Materialization == "materialized" && (e.Device == 0 || e.Inode == 0 || e.Type == 0)) { return nil, ErrTypedInventory } default: return nil, ErrTypedInventory } } out = append(out, p) } if err := rows.Err(); err != nil { return nil, err } if err := journalIdentity(j.path, j.identity); err != nil { return nil, err } return out, nil }