package installer import ( "errors" "fmt" "os" "path/filepath" ) var ( ErrUnsafeInstallLayout = errors.New("unsafe install directory layout") ErrStagingMissing = errors.New("staging directory is missing") ErrBackupExists = errors.New("backup directory already exists") ) type appLayout struct { root string current string staging string backup string transaction string transactionBackup string } type directoryState struct { current bool staging bool backup bool } func inspectAppLayout(root string) (appLayout, error) { if root == "" { return appLayout{}, fmt.Errorf("%w: empty root", ErrUnsafeInstallLayout) } absolute, err := filepath.Abs(root) if err != nil { return appLayout{}, fmt.Errorf("%w: %v", ErrUnsafeInstallLayout, err) } info, err := os.Lstat(absolute) if err != nil { return appLayout{}, fmt.Errorf("%w: root: %v", ErrUnsafeInstallLayout, err) } if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { return appLayout{}, fmt.Errorf("%w: root is not a real directory", ErrUnsafeInstallLayout) } transaction, transactionBackup := transactionPaths(absolute) return appLayout{ root: absolute, current: filepath.Join(absolute, "current"), staging: filepath.Join(absolute, "staging"), backup: filepath.Join(absolute, "backup"), transaction: transaction, transactionBackup: transactionBackup, }, nil } func inspectDirectories(layout appLayout) (directoryState, error) { current, err := inspectManagedDirectory(layout.current) if err != nil { return directoryState{}, err } staging, err := inspectManagedDirectory(layout.staging) if err != nil { return directoryState{}, err } backup, err := inspectManagedDirectory(layout.backup) if err != nil { return directoryState{}, err } return directoryState{ current: current, staging: staging, backup: backup, }, nil } func inspectManagedDirectory(path string) (bool, error) { info, err := os.Lstat(path) if os.IsNotExist(err) { return false, nil } if err != nil { return false, fmt.Errorf("%w: inspect %s: %v", ErrUnsafeInstallLayout, path, err) } if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() { return false, fmt.Errorf("%w: %s is not a real directory", ErrUnsafeInstallLayout, path) } return true, nil } func removeManagedDirectory(layout appLayout, target string) error { return removeManagedDirectoryWithFence(layout, target, defaultDurability()) } func removeManagedDirectoryWithFence( layout appLayout, target string, fence durabilityFence, ) error { if filepath.Dir(target) != layout.root { return fmt.Errorf("%w: refuse removal outside app root", ErrUnsafeInstallLayout) } base := filepath.Base(target) if base != "current" && base != "staging" && base != "backup" { return fmt.Errorf("%w: refuse removal of %s", ErrUnsafeInstallLayout, base) } exists, err := inspectManagedDirectory(target) if err != nil { return err } if !exists { return nil } if err := os.RemoveAll(target); err != nil { return fmt.Errorf("remove managed directory %s: %w", base, err) } return syncDirectoryWithFence(fence, layout.root, "app root after directory removal") }