volume: return error instead of panicking when .dat open fails (#11619)

* volume: return error instead of panicking when .dat open fails

When backend.OpenVolumeFile returns an error (e.g. disk below
-minFreeSpace), dataFile is nil.  Calling backend.NewDiskFile(nil)
immediately after caused a nil-pointer panic in f.Stat()/f.Name().

Move the existing error check to run right after OpenVolumeFile, before
NewDiskFile is called, so the error is returned cleanly.

Fixes seaweedfs/seaweedfs#11615

* volume: share .dat load error handling

Extract datFileLoadError helper so open and create paths share one check.

* volume: trim dat-open-fail test comments

* volume(rust): cover unopenable .dat load path

---------

Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
Joel Town RoadandChris Lu authored and GitHub committed 2026-10-06 14:24:42 +08:00
1 parent 32e77ff980
commit 23893eb378
3 files changed
+71 -5

No files matched your search

+25
View File
@@ -9687,6 +9687,31 @@ mod tests {
}
}
// A directory occupying the .dat path fails open even as root; load must
// return the error. Mirrors Go's TestLoad_DatOpenFail_NoNilPanic.
#[test]
fn test_load_dat_open_fail_returns_error() {
let tmp = TempDir::new().unwrap();
let dir = tmp.path().to_str().unwrap();
{
let _v = make_test_volume(dir);
}
let dat_path = format!("{dir}/1.dat");
std::fs::remove_file(&dat_path).unwrap();
std::fs::create_dir(&dat_path).unwrap();
let result = Volume::new(
dir,
dir,
VolumeId(1),
NeedleMapKind::InMemory,
&VolumeSpec::default(),
);
assert!(result.is_err(), "unopenable .dat must fail load");
}
#[test]
fn test_remote_only_volume_load_reads_from_tier_backend() {
let tmp = TempDir::new().unwrap();
+11 -5
View File
@@ -198,6 +198,9 @@ func (v *Volume) load(alsoLoadIndex bool, createDatIfMissing bool, needleMapKind
dataFile, err = backend.OpenVolumeFile(v.FileName(".dat"), os.O_RDONLY)
v.noWriteOrDelete = true
}
if err != nil {
return datFileLoadError(v.FileName(".dat"), err)
}
v.lastModifiedTsSeconds = uint64(modifiedTime.Unix())
if fileSize >= super_block.SuperBlockSize {
alreadyHasSuperBlock = true
@@ -212,11 +215,7 @@ func (v *Volume) load(alsoLoadIndex bool, createDatIfMissing bool, needleMapKind
}
if err != nil {
if !os.IsPermission(err) {
return fmt.Errorf("cannot load volume data %s: %v", v.FileName(".dat"), err)
} else {
return fmt.Errorf("load data file %s: %v", v.FileName(".dat"), err)
}
return datFileLoadError(v.FileName(".dat"), err)
}
if alreadyHasSuperBlock {
@@ -417,3 +416,10 @@ func (v *Volume) load(alsoLoadIndex bool, createDatIfMissing bool, needleMapKind
return err
}
func datFileLoadError(fileName string, err error) error {
if os.IsPermission(err) {
return fmt.Errorf("load data file %s: %v", fileName, err)
}
return fmt.Errorf("cannot load volume data %s: %v", fileName, err)
}
@@ -0,0 +1,35 @@
package storage
import (
"os"
"testing"
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
)
// A directory occupying the .dat path fails open even as root; load must
// return the error rather than crash inside backend.NewDiskFile.
func TestLoad_DatOpenFail_NoNilPanic(t *testing.T) {
dir := t.TempDir()
v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
if err != nil {
t.Fatalf("create volume: %v", err)
}
v.Close()
datPath := VolumeFileName(dir, "", 1) + ".dat"
if err := os.Remove(datPath); err != nil {
t.Fatalf("remove .dat: %v", err)
}
if err := os.Mkdir(datPath, 0755); err != nil {
t.Fatalf("mkdir .dat: %v", err)
}
v2, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
if err == nil {
v2.Close()
t.Fatal("expected error when .dat cannot be opened, got nil")
}
}