Files
soft_quay/core/updater/filesystem.go
T

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
})
}