filer: reuse IsEqualOrUnder for the rename subdirectory check (#12338)

* filer: test the rename subdirectory check through CanRename

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* filer: reuse IsEqualOrUnder for the rename subdirectory check

A trailing slash on the old name made the source path end in a slash,
so a child of the source was no longer seen as under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris LuandClaude Opus 5.5 authored and GitHub committed 2026-11-02 10:28:48 -08:00
1 parent d650abe0e7
commit 690aabda0e
2 files changed
+30 -22

No files matched your search

+1 -8
View File
@@ -10,7 +10,7 @@ import (
func (f *Filer) CanRename(ctx context.Context, source, target util.FullPath, oldName string) error {
sourcePath := source.Child(oldName)
if movesIntoItself(sourcePath, target) {
if util.IsEqualOrUnder(string(target), string(sourcePath)) {
return fmt.Errorf("mv: can not move directory to a subdirectory of itself")
}
@@ -33,13 +33,6 @@ func (f *Filer) CanRename(ctx context.Context, source, target util.FullPath, old
return nil
}
// movesIntoItself reports whether target, the new parent directory, is
// sourcePath or lies under it. A sibling that only shares a name prefix,
// such as /a.d for /a, does not.
func movesIntoItself(sourcePath, target util.FullPath) bool {
return target == sourcePath || target.IsUnder(sourcePath)
}
func (f *Filer) DetectBucket(source util.FullPath) (bucket string) {
if strings.HasPrefix(string(source), f.DirBucketsPath+"/") {
bucketAndObjectKey := string(source)[len(f.DirBucketsPath)+1:]
+29 -14
View File
@@ -1,6 +1,9 @@
package filer
import (
"context"
"os"
"strings"
"testing"
"github.com/seaweedfs/seaweedfs/weed/util"
@@ -30,25 +33,37 @@ func TestDetectBucket(t *testing.T) {
}
}
func TestMovesIntoItself(t *testing.T) {
func TestCanRenameIntoItself(t *testing.T) {
f, store := newTestFilerWithStubStore(t)
f.DirBucketsPath = "/buckets"
if err := store.InsertEntry(context.Background(), &Entry{FullPath: "/dir/a", Attr: Attr{Mode: os.ModeDir}}); err != nil {
t.Fatal(err)
}
tests := []struct {
name string
sourcePath string
target string
want bool
name string
oldName string
target string
reject bool
}{
{"into itself", "/dir/a", "/dir/a", true},
{"into a child", "/dir/a", "/dir/a/b", true},
{"into a deeper descendant", "/dir/a", "/dir/a/b/c", true},
{"into a sibling sharing the name prefix", "/dir/a", "/dir/a.d", false},
{"into a descendant of that sibling", "/dir/a", "/dir/ab/c", false},
{"into the parent", "/dir/a", "/dir", false},
{"into an unrelated directory", "/dir/a", "/other", false},
{"into itself", "a", "/dir/a", true},
{"into a child", "a", "/dir/a/b", true},
{"into a deeper descendant", "a", "/dir/a/b/c", true},
{"into a child with a trailing slash", "a", "/dir/a/b/", true},
{"old name with a trailing slash into a child", "a/", "/dir/a/b", true},
{"into a sibling sharing the name prefix", "a", "/dir/a.d", false},
{"into a descendant of that sibling", "a", "/dir/ab/c", false},
{"into the parent", "a", "/dir", false},
{"into an unrelated directory", "a", "/other", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := movesIntoItself(util.FullPath(tt.sourcePath), util.FullPath(tt.target)); got != tt.want {
t.Errorf("movesIntoItself(%q, %q) = %v, want %v", tt.sourcePath, tt.target, got, tt.want)
err := f.CanRename(context.Background(), "/dir", util.FullPath(tt.target), tt.oldName)
rejected := err != nil && strings.Contains(err.Error(), "subdirectory of itself")
if rejected != tt.reject {
t.Errorf("CanRename(/dir, %q, %q) = %v, want rejected=%v", tt.target, tt.oldName, err, tt.reject)
}
if !tt.reject && err != nil {
t.Errorf("CanRename(/dir, %q, %q) = %v, want nil", tt.target, tt.oldName, err)
}
})
}