package hostbox import ( "context" "fmt" "io/fs" "os" "path/filepath" ) // migrationRefusal names the root and path the migration census could not // classify, so the operator sees which entry stopped it. It still wraps the // census's own error. func migrationRefusal(err error, root, rel string) error { return fmt.Errorf("%w: migration census: %s entry %q is not classified", err, root, rel) } // CheckMigrationRoots accounts for every managed entry under offline exclusion. // Only independently retained inventory may classify an installation subtree. func CheckMigrationRoots(ctx context.Context, roots map[string]string, shared map[string][]string, inventories []StopInventory) error { entries := map[string]map[string]StopInventoryEntry{} for _, in := range inventories { if !in.Complete { return ErrTypedInventory } for _, e := range in.Entries { if e.Kind != "path" { continue } if roots[e.ManagedRoot] == "" || !managedName(e.RelativePath) || e.Materialization != "materialized" || e.Device == 0 || e.Inode == 0 { return ErrTypedInventory } if entries[e.ManagedRoot] == nil { entries[e.ManagedRoot] = map[string]StopInventoryEntry{} } if _, ok := entries[e.ManagedRoot][e.RelativePath]; ok { return ErrTypedInventory } entries[e.ManagedRoot][e.RelativePath] = e } } for _, name := range []string{"data", "opt", "units"} { root := roots[name] if !filepath.IsAbs(root) { return ErrTypedInventory } info, err := os.Lstat(root) if err != nil { return err } if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 { return ErrTypedInventory } rules, err := censusRules(shared[name], "migration-census.invalid") if err != nil { return err } count := 0 if err := filepath.WalkDir(root, func(path string, d fs.DirEntry, walkErr error) error { if walkErr != nil { return walkErr } if err := ctx.Err(); err != nil { return err } count++ if count > 65536 { return ErrTypedInventory } rel, err := filepath.Rel(root, path) if err != nil { return err } rel = filepath.ToSlash(rel) if rel == "." { return nil } if e, ok := entries[name][rel]; ok { if err := VerifyInventoryIdentity(root, e); err != nil { return err } delete(entries[name], rel) if d.IsDir() { return filepath.SkipDir } return nil } if rule, ok := rules[rel]; ok { err := rule.check(path, d) if err != nil && err != filepath.SkipDir { return migrationRefusal(err, name, rel) } return err } if name == "units" && censusVendorUnitLink(root, path) { return nil } if censusInTree(rules, rel) && (d.IsDir() || d.Type().IsRegular()) { return nil } if d.IsDir() { return nil } return migrationRefusal(ErrTypedInventory, name, rel) }); err != nil { return err } if len(entries[name]) != 0 { return ErrTypedInventory } } return nil }