Prepare optional spell catalog inputs

This commit is contained in:
2026-08-29 15:03:44 +00:00
parent 5831c0c9e6
commit b3363f87d6
7 changed files with 369 additions and 8 deletions

View File

@@ -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")

View File

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