package installer import ( "errors" "fmt" "io/fs" "os" "path/filepath" "sort" ) var ErrDurability = errors.New("install durability fence failed") type durabilityFence interface { syncFile(file *os.File) error syncDirectory(path string) error } type filesystemDurability struct{} func (filesystemDurability) syncFile(file *os.File) error { return file.Sync() } func (filesystemDurability) syncDirectory(path string) error { return syncDirectoryPath(path) } func defaultDurability() durabilityFence { return filesystemDurability{} } func effectiveDurability(fence durabilityFence) durabilityFence { if fence == nil { return defaultDurability() } return fence } func syncFileWithFence(fence durabilityFence, file *os.File, description string) error { if err := effectiveDurability(fence).syncFile(file); err != nil { return fmt.Errorf("%w: sync %s: %w", ErrDurability, description, err) } return nil } func syncDirectoryWithFence(fence durabilityFence, path, description string) error { if err := effectiveDurability(fence).syncDirectory(path); err != nil { return fmt.Errorf("%w: sync %s: %w", ErrDurability, description, err) } return nil } func syncStagingTree(fence durabilityFence, root string) error { directories := make([]string, 0) err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error { if err != nil { return err } if entry.Type()&os.ModeSymlink != 0 { return fmt.Errorf("%w: staging tree contains a symbolic link", ErrUnsafeInstallLayout) } if entry.IsDir() { directories = append(directories, path) } return nil }) if err != nil { return fmt.Errorf("%w: walk staging tree: %w", ErrDurability, err) } sort.Slice(directories, func(left, right int) bool { return len(directories[left]) > len(directories[right]) }) for _, directory := range directories { if err := syncDirectoryWithFence(fence, directory, "staging directory"); err != nil { return err } } parent := filepath.Dir(root) if parent != root { if err := syncDirectoryWithFence(fence, parent, "staging parent directory"); err != nil { return err } } return nil } func renameManagedDirectory( layout appLayout, source string, target string, fence durabilityFence, description string, ) error { if err := os.Rename(source, target); err != nil { return err } return syncDirectoryWithFence(fence, layout.root, description) }