package hostbox import ( "bytes" "context" "database/sql" "encoding/json" "io" "net/url" "os" "path/filepath" ) // GenerationLaunches includes hosts absent from the reservation directory. // Only the checked local journal is eligible; an agent list is not a census. func (j *LaunchJournal) GenerationLaunches(ctx context.Context) ([]LaunchInventory, 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 identity, body FROM launches ORDER BY identity") if err != nil { return nil, err } defer rows.Close() var out []LaunchInventory for rows.Next() { var key string var body []byte if err := rows.Scan(&key, &body); err != nil { return nil, err } if len(body) > 1<<20 || len(out) >= 8192 { return nil, ErrTypedInventory } var in LaunchInventory dec := json.NewDecoder(bytes.NewReader(body)) dec.DisallowUnknownFields() if dec.Decode(&in) != nil || dec.Decode(new(any)) != io.EOF || !validLaunchOwner(in.Target) || launchKey(in.Target) != key || !in.Completed.Valid() || in.ControllerPID <= 0 || in.ProcessStart == "" { return nil, ErrTypedInventory } out = append(out, in) } if err := rows.Err(); err != nil { return nil, err } if err := journalIdentity(j.path, j.identity); err != nil { return nil, err } return out, nil } // OpenGenerationLaunchReader never creates a database or initializes its schema. // Production callers must check root ownership of the database and sidecars. func OpenGenerationLaunchReader(path string) (*LaunchJournal, error) { if !filepath.IsAbs(path) { return nil, ErrUnsupported } if err := checkJournalPath(path); err != nil { return nil, err } identity, err := os.Lstat(path) if err != nil { return nil, err } db, err := sql.Open("sqlite", "file:"+url.PathEscape(path)+"?mode=ro&_pragma=busy_timeout(5000)") if err != nil { return nil, err } db.SetMaxOpenConns(1) return &LaunchJournal{db: db, path: path, identity: identity}, nil }