diff --git a/memmap.go b/memmap.go index 30fff0f..4b61a3a 100644 --- a/memmap.go +++ b/memmap.go @@ -16,7 +16,6 @@ package afero import ( "fmt" "io" - "log" "os" "path/filepath" "sort" @@ -71,7 +70,11 @@ func (m *MemMapFs) unRegisterWithParent(fileName string) error { } parent := m.findParent(f) if parent == nil { - log.Panic("parent of ", f.Name(), " is nil") + // The parent directory is no longer registered (for example it was + // removed before this file). There is nothing to unregister from, and + // crashing the whole process is never appropriate, so surface an error + // that callers can handle. + return fmt.Errorf("parent of %s is nil", f.Name()) } parent.Lock() diff --git a/memmap_test.go b/memmap_test.go index e6a3f3d..9d452ca 100644 --- a/memmap_test.go +++ b/memmap_test.go @@ -135,6 +135,29 @@ func TestOpenFileExcl(t *testing.T) { checkPathError(t, err, "Open") } +// Regression test for #388: removing a file whose parent directory is no +// longer registered must return an error instead of panicking. Previously +// unRegisterWithParent called log.Panic, which crashed the whole process. +func TestMemFsRemoveWithUnregisteredParent(t *testing.T) { + fs := NewMemMapFs() + if err := fs.MkdirAll("/foo/bar", 0o755); err != nil { + t.Fatal(err) + } + if err := WriteFile(fs, "/foo/bar/baz.txt", []byte("data"), 0o644); err != nil { + t.Fatal(err) + } + + // Drop the parent directory entry, leaving the file dangling. + if err := fs.Remove("/foo/bar"); err != nil { + t.Fatalf("removing parent dir failed: %v", err) + } + + // Removing the now-orphaned file must not panic; it must return an error. + if err := fs.Remove("/foo/bar/baz.txt"); err == nil { + t.Fatalf("expected an error removing a file with an unregistered parent, got nil") + } +} + // Ensure Permissions are set on OpenFile/Mkdir/MkdirAll func TestPermSet(t *testing.T) { const fileName = "/myFileTest"