86 lines
2.6 KiB
Go
86 lines
2.6 KiB
Go
package fileops
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// RemoveAllUnderRoot removes target and its contents beneath root. target must
|
|
// name a proper descendant of root; removal is confined by the opened root.
|
|
func RemoveAllUnderRoot(rootPath, target string) error {
|
|
root, targetName, err := openCleanupRoot(rootPath, target)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
defer func() { _ = root.Close() }()
|
|
if err := root.RemoveAll(targetName); err != nil {
|
|
return fmt.Errorf("remove cleanup target %q: %w", targetName, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveFileUnderRoot removes an exact regular-file target below root without
|
|
// following the leaf. A missing target is successful; directories, symlinks,
|
|
// and other non-regular entries are rejected.
|
|
func RemoveFileUnderRoot(rootPath, target string) error {
|
|
root, targetName, err := openCleanupRoot(rootPath, target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() { _ = root.Close() }()
|
|
|
|
info, err := root.Lstat(targetName)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("inspect cleanup file %q: %w", targetName, err)
|
|
}
|
|
if !info.Mode().IsRegular() {
|
|
return fmt.Errorf("refusing to delete non-regular file path %q", targetName)
|
|
}
|
|
if err := root.Remove(targetName); err != nil {
|
|
return fmt.Errorf("remove cleanup file %q: %w", targetName, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func openCleanupRoot(rootPath, target string) (*os.Root, string, error) {
|
|
if strings.TrimSpace(rootPath) == "" {
|
|
return nil, "", fmt.Errorf("cleanup root is required")
|
|
}
|
|
if strings.TrimSpace(target) == "" {
|
|
return nil, "", fmt.Errorf("cleanup target is required")
|
|
}
|
|
rootAbs, err := filepath.Abs(rootPath)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("resolve cleanup root: %w", err)
|
|
}
|
|
targetAbs, err := filepath.Abs(target)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("resolve cleanup target: %w", err)
|
|
}
|
|
relative, err := filepath.Rel(rootAbs, targetAbs)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("resolve cleanup target below root: %w", err)
|
|
}
|
|
if relative == "." || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
|
|
return nil, "", fmt.Errorf("cleanup target %q must be below root %q", targetAbs, rootAbs)
|
|
}
|
|
name := filepath.Clean(relative)
|
|
if filepath.IsAbs(name) || name == "." || name == ".." || strings.HasPrefix(name, ".."+string(filepath.Separator)) {
|
|
return nil, "", fmt.Errorf("cleanup target %q must be below root %q", targetAbs, rootAbs)
|
|
}
|
|
root, err := os.OpenRoot(rootAbs)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("open cleanup root %q: %w", rootAbs, err)
|
|
}
|
|
return root, name, nil
|
|
}
|