124 lines
3.4 KiB
Go
124 lines
3.4 KiB
Go
package updater
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
func replaceRegularFile(directory, target, pattern string, data []byte, syncer DirectorySyncer) error {
|
|
if info, err := os.Lstat(target); err == nil {
|
|
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
|
return fmt.Errorf("%w: file target is not a regular file", ErrUnsafeLayout)
|
|
}
|
|
} else if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
temporary, err := os.CreateTemp(directory, pattern)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
temporaryPath := temporary.Name()
|
|
defer os.Remove(temporaryPath)
|
|
if err := temporary.Chmod(0o600); err != nil {
|
|
_ = temporary.Close()
|
|
return err
|
|
}
|
|
if _, err := temporary.Write(data); err != nil {
|
|
_ = temporary.Close()
|
|
return err
|
|
}
|
|
if err := temporary.Sync(); err != nil {
|
|
_ = temporary.Close()
|
|
return err
|
|
}
|
|
if err := temporary.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := os.Rename(temporaryPath, target); err != nil {
|
|
return err
|
|
}
|
|
return syncer.SyncDirectory(directory)
|
|
}
|
|
|
|
func readRegularFile(path string) ([]byte, error) {
|
|
if err := validateRegularFile(path); err != nil {
|
|
return nil, err
|
|
}
|
|
return os.ReadFile(path)
|
|
}
|
|
|
|
func removeRegularFile(path, directory string, syncer DirectorySyncer) error {
|
|
info, err := os.Lstat(path)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
|
return fmt.Errorf("%w: refuse to remove non-regular file", ErrUnsafeLayout)
|
|
}
|
|
if err := os.Remove(path); err != nil {
|
|
return err
|
|
}
|
|
return syncer.SyncDirectory(directory)
|
|
}
|
|
|
|
func renameDirectory(source, target string, layout updateLayout, syncer DirectorySyncer, description string) error {
|
|
if source != layout.target && source != layout.staging && source != layout.backup {
|
|
return fmt.Errorf("%w: source outside managed root", ErrUnsafeLayout)
|
|
}
|
|
if target != layout.target && target != layout.staging && target != layout.backup {
|
|
return fmt.Errorf("%w: target outside managed root", ErrUnsafeLayout)
|
|
}
|
|
if err := os.Rename(source, target); err != nil {
|
|
return fmt.Errorf("%s: %w", description, err)
|
|
}
|
|
if err := syncer.SyncDirectory(filepath.Dir(source)); err != nil {
|
|
return err
|
|
}
|
|
if filepath.Dir(target) != filepath.Dir(source) {
|
|
if err := syncer.SyncDirectory(filepath.Dir(target)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func removeManagedTree(path string, layout updateLayout, syncer DirectorySyncer) error {
|
|
if filepath.Dir(path) != filepath.Join(layout.root, backupsDirectoryName) && filepath.Dir(path) != filepath.Join(layout.root, stagingDirectoryName) {
|
|
return fmt.Errorf("%w: refuse removal outside managed staging or backups", ErrUnsafeLayout)
|
|
}
|
|
if err := verifyTreeNoLinks(path); err != nil {
|
|
return err
|
|
}
|
|
if err := os.RemoveAll(path); err != nil {
|
|
return err
|
|
}
|
|
return syncer.SyncDirectory(filepath.Dir(path))
|
|
}
|
|
|
|
func verifyTreeNoLinks(path string) error {
|
|
info, err := os.Lstat(path)
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
|
return fmt.Errorf("%w: managed tree is not a real directory", ErrUnsafeLayout)
|
|
}
|
|
return filepath.WalkDir(path, func(current string, entry fs.DirEntry, walkErr error) error {
|
|
if walkErr != nil {
|
|
return walkErr
|
|
}
|
|
if entry.Type()&os.ModeSymlink != 0 {
|
|
return fmt.Errorf("%w: managed tree contains a symbolic link", ErrUnsafeLayout)
|
|
}
|
|
return nil
|
|
})
|
|
}
|