//go:build linux package hostbox import ( "context" "errors" "os" "path/filepath" "testing" "time" ) func TestPlan9JournalMigrationDurableAndNeverReimportsLegacy(t *testing.T) { s, _, in := journalFixture(t) legacy := t.TempDir() old, err := OpenLaunchJournal(filepath.Join(legacy, "launches.db")) if err != nil { t.Fatal(err) } if err = old.Create(t.Context(), in); err != nil { t.Fatal(err) } if err = old.Close(); err != nil { t.Fatal(err) } inventory, err := OpenStopInventoryDB(filepath.Join(legacy, "inventory.db")) if err != nil { t.Fatal(err) } if err = inventory.WriteStopInventory(t.Context(), StopInventory{Complete: true, Host: in.Target.Host, Incarnation: "inc", InstallationStamp: 1, Box: "box", Enrollment: "enrollment", DirectoryRevision: "1"}); err != nil { t.Fatal(err) } inventory.Close() if err = s.MigrateLegacy(t.Context(), legacy, "box", "enrollment", nil, nil); err == nil { t.Fatal("unquiesced import accepted") } released := false freeze := func(context.Context) (func(), error) { return func() { released = true }, nil } if err = s.MigrateLegacy(t.Context(), legacy, "box", "enrollment", freeze, nil); !errors.Is(err, ErrLegacyJournalProof) { t.Fatalf("unproven legacy history: %v", err) } if got, err := s.Launches.HasHost(t.Context(), in.Target.Host); err != nil || got { t.Fatalf("unproven history committed: %v %v", got, err) } if err = s.MigrateLegacy(t.Context(), legacy, "box", "enrollment", freeze, func(ctx context.Context, _ LaunchInventory, _ StopInventory, _ StopKernelJournal) error { if deadline, ok := ctx.Deadline(); !ok || time.Until(deadline) > 120*time.Second { t.Fatal("unbounded proof context") } if s.Launches.db.Stats().InUse != 0 { t.Fatal("proof runs with root SQLite connection held") } probe, cancel := context.WithTimeout(ctx, 100*time.Millisecond) defer cancel() var one int return s.Launches.db.QueryRowContext(probe, "SELECT 1").Scan(&one) }); err != nil { t.Fatal(err) } if !released { t.Fatal("quiescence not released") } if _, err = s.Launches.Read(t.Context(), in.Target); err != nil { t.Fatal(err) } if _, err = s.Inventory.ReadExactStopInventory(t.Context(), in.Target); err != nil { t.Fatal(err) } // Root can continue writing. A stale legacy snapshot is never replayed. if err = s.Launches.RecordProducer(t.Context(), in.Target, "new-producer"); err != nil { t.Fatal(err) } if err = s.MigrateLegacy(t.Context(), legacy, "box", "enrollment", nil, nil); err != nil { t.Fatal(err) } got, err := s.Launches.Read(t.Context(), in.Target) if err != nil || len(got.ProducerIDs) != 1 { t.Fatalf("stale import: %+v %v", got, err) } if _, err = os.Stat(filepath.Join(legacy, "launches.db")); err != nil { t.Fatal("legacy evidence removed", err) } } func TestPlan9JournalMigrationRejectsLinksAndRollsBackBothTables(t *testing.T) { s, _, in := journalFixture(t) legacy := t.TempDir() old, err := OpenLaunchJournal(filepath.Join(legacy, "launches.db")) if err != nil { t.Fatal(err) } if err = old.Create(t.Context(), in); err != nil { t.Fatal(err) } old.Close() victim := filepath.Join(t.TempDir(), "victim") if err = os.WriteFile(victim, []byte("unaltered"), 0600); err != nil { t.Fatal(err) } if err = os.Symlink(victim, filepath.Join(legacy, "inventory.db")); err != nil { t.Fatal(err) } freeze := func(context.Context) (func(), error) { return func() {}, nil } if err = s.MigrateLegacy(t.Context(), legacy, "box", "enrollment", freeze, func(context.Context, LaunchInventory, StopInventory, StopKernelJournal) error { return nil }); err == nil { t.Fatal("symlink import accepted") } if got, err := s.Launches.HasHost(t.Context(), in.Target.Host); err != nil || got { t.Fatalf("partial migration: %v %v", got, err) } raw, err := os.ReadFile(victim) if err != nil || string(raw) != "unaltered" { t.Fatal("victim changed", err) } if err = os.Remove(filepath.Join(legacy, "inventory.db")); err != nil { t.Fatal(err) } if err = os.Link(victim, filepath.Join(legacy, "inventory.db")); err != nil { t.Fatal(err) } if err = s.MigrateLegacy(t.Context(), legacy, "box", "enrollment", freeze, func(context.Context, LaunchInventory, StopInventory, StopKernelJournal) error { return nil }); err == nil { t.Fatal("hardlink import accepted") } if _, err = os.Stat(victim); errors.Is(err, os.ErrNotExist) { t.Fatal("victim removed") } }