diff --git a/basepath.go b/basepath.go index 47438dd..aa1e38c 100644 --- a/basepath.go +++ b/basepath.go @@ -37,7 +37,16 @@ type BasePathFile struct { func (f *BasePathFile) Name() string { sourcename := f.File.Name() - return strings.TrimPrefix(sourcename, filepath.Clean(f.path)) + base := filepath.Clean(f.path) + name := strings.TrimPrefix(sourcename, base) + if name == sourcename { + // filepath.Clean drops the leading separator from the base path, so a + // name produced by a wrapped (nested) BasePathFile can still carry the + // base preceded by a separator. Strip that form as well, otherwise the + // base segment leaks into the reported name. + name = strings.TrimPrefix(sourcename, string(filepath.Separator)+base) + } + return name } func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) { diff --git a/basepath_test.go b/basepath_test.go index 2ecc440..603b48e 100644 --- a/basepath_test.go +++ b/basepath_test.go @@ -307,3 +307,31 @@ func TestBasePathTempFile(t *testing.T) { t.Fatalf("TempFile realpath leaked: expected %s, got %s", expected, actual) } } + +// Regression test for #428: a nested BasePathFs must strip its own base +// segment from BasePathFile.Name(), even though filepath.Clean drops the +// leading separator that the wrapped (inner) file name carries. +func TestNestedBasePathFileName(t *testing.T) { + baseFs := &MemMapFs{} + if err := baseFs.MkdirAll("/files/123", 0o777); err != nil { + t.Fatal(err) + } + fs1 := NewBasePathFs(baseFs, "/files") + fs2 := NewBasePathFs(fs1, "123") + + f, err := fs2.OpenFile("/afero-test.txt", os.O_CREATE|os.O_RDWR, 0o644) + if err != nil { + t.Fatalf("open through nested BasePathFs failed: %v", err) + } + defer f.Close() + + if expected, actual := "/afero-test.txt", f.Name(); expected != actual { + t.Fatalf("nested BasePathFile.Name() = %q, want %q", actual, expected) + } + + // The returned name must round-trip: using it via fs2 resolves back to + // the same file instead of double-prepending the base segment. + if err := fs2.Chmod(f.Name(), 0o600); err != nil { + t.Fatalf("chmod via returned name failed: %v", err) + } +}