100 lines
2.4 KiB
Go
100 lines
2.4 KiB
Go
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: %v", 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: %v", 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)
|
|
}
|