Prepare optional spell catalog inputs
This commit is contained in:
@@ -22,6 +22,32 @@ func RemoveAllUnderRoot(rootPath, target string) error {
|
||||
return removeConfinedEntry(root, targetName)
|
||||
}
|
||||
|
||||
// RemoveFileUnderRoot removes an exact regular-file target below root without
|
||||
// following symlinked ancestors or 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 := openConfinedCleanupTarget(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 openConfinedCleanupTarget(rootPath, target string) (*os.Root, string, error) {
|
||||
if strings.TrimSpace(rootPath) == "" {
|
||||
return nil, "", fmt.Errorf("cleanup root is required")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package fileops
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -77,3 +79,47 @@ func TestRemoveAllUnderRootRejectsSymlinkInTree(t *testing.T) {
|
||||
t.Fatalf("outside sentinel was changed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveFileUnderRootRemovesOnlyRegularFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
target := filepath.Join(root, "inputs", "spell_catalog.json")
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll() error = %v", err)
|
||||
}
|
||||
if err := os.WriteFile(target, []byte("{}\n"), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile() error = %v", err)
|
||||
}
|
||||
if err := RemoveFileUnderRoot(root, target); err != nil {
|
||||
t.Fatalf("RemoveFileUnderRoot() error = %v", err)
|
||||
}
|
||||
if _, err := os.Lstat(target); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("Lstat() error = %v, want not exist", err)
|
||||
}
|
||||
if err := RemoveFileUnderRoot(root, target); err != nil {
|
||||
t.Fatalf("RemoveFileUnderRoot(missing) error = %v", err)
|
||||
}
|
||||
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
setup func(string) error
|
||||
}{
|
||||
{name: "directory", setup: func(path string) error { return os.Mkdir(path, 0o755) }},
|
||||
{name: "symlink", setup: func(path string) error { return os.Symlink(filepath.Join(root, "outside"), path) }},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.setup(target); err != nil {
|
||||
t.Fatalf("setup target: %v", err)
|
||||
}
|
||||
err := RemoveFileUnderRoot(root, target)
|
||||
if err == nil || !strings.Contains(err.Error(), "non-regular") {
|
||||
t.Fatalf("RemoveFileUnderRoot() error = %v, want non-regular rejection", err)
|
||||
}
|
||||
if _, err := os.Lstat(target); err != nil {
|
||||
t.Fatalf("ambiguous target was removed: %v", err)
|
||||
}
|
||||
if err := os.Remove(target); err != nil {
|
||||
t.Fatalf("cleanup target: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user