diff --git a/lib/config/folderconfiguration.go b/lib/config/folderconfiguration.go index a00c8876c..bfa9bc755 100644 --- a/lib/config/folderconfiguration.go +++ b/lib/config/folderconfiguration.go @@ -235,7 +235,7 @@ func (f *FolderConfiguration) SharedWith(device protocol.DeviceID) bool { return false } -func (f *FolderConfiguration) CheckAvailableSpace(req int64) error { +func (f *FolderConfiguration) CheckAvailableSpace(req uint64) error { val := f.MinDiskFree.BaseValue() if val <= 0 { return nil @@ -245,11 +245,8 @@ func (f *FolderConfiguration) CheckAvailableSpace(req int64) error { if err != nil { return nil } - usage.Free -= req - if usage.Free > 0 { - if err := CheckFreeSpace(f.MinDiskFree, usage); err == nil { - return nil - } + if !checkAvailableSpace(req, f.MinDiskFree, usage) { + return fmt.Errorf("insufficient space in %v %v", fs.Type(), fs.URI()) } - return fmt.Errorf("insufficient space in %v %v", fs.Type(), fs.URI()) + return nil } diff --git a/lib/config/size.go b/lib/config/size.go index fb6518245..8e92c82e6 100644 --- a/lib/config/size.go +++ b/lib/config/size.go @@ -73,25 +73,36 @@ func (s *Size) ParseDefault(str string) error { return err } -func CheckFreeSpace(req Size, usage fs.Usage) error { - val := req.BaseValue() +// CheckFreeSpace checks that the free space does not fall below the minimum required free space. +func CheckFreeSpace(minFree Size, usage fs.Usage) error { + val := minFree.BaseValue() if val <= 0 { return nil } - if req.Percentage() { + if minFree.Percentage() { freePct := (float64(usage.Free) / float64(usage.Total)) * 100 if freePct < val { - return fmt.Errorf("%.1f %% < %v", freePct, req) + return fmt.Errorf("%.1f %% < %v", freePct, minFree) } } else if float64(usage.Free) < val { - return fmt.Errorf("%sB < %v", formatSI(usage.Free), req) + return fmt.Errorf("%sB < %v", formatSI(usage.Free), minFree) } return nil } -func formatSI(b int64) string { +// checkAvailableSpace checks that the free space does not fall below the minimum +// required free space, considering additional required space for a future operation. +func checkAvailableSpace(req uint64, minFree Size, usage fs.Usage) bool { + if usage.Free < req { + return false + } + usage.Free -= req + return CheckFreeSpace(minFree, usage) == nil +} + +func formatSI(b uint64) string { switch { case b < 1000: return fmt.Sprintf("%d ", b) diff --git a/lib/config/size_test.go b/lib/config/size_test.go index c567b22c9..2d094cd1c 100644 --- a/lib/config/size_test.go +++ b/lib/config/size_test.go @@ -9,6 +9,7 @@ package config import ( "testing" + "github.com/syncthing/syncthing/lib/fs" "github.com/syncthing/syncthing/lib/util" ) @@ -66,6 +67,10 @@ func TestParseSize(t *testing.T) { // The empty string is a valid zero {"", true, 0, false}, {" ", true, 0, false}, + // Just numbers are fine too + {"0", true, 0, false}, + {"3", true, 3, false}, + {"34.3", true, 34.3, false}, } for _, tc := range cases { @@ -94,7 +99,7 @@ func TestParseSize(t *testing.T) { func TestFormatSI(t *testing.T) { cases := []struct { - bytes int64 + bytes uint64 result string }{ { @@ -130,3 +135,32 @@ func TestFormatSI(t *testing.T) { } } } + +func TestCheckAvailableSize(t *testing.T) { + cases := []struct { + req, free, total uint64 + minFree string + ok bool + }{ + {10, 1e8, 1e9, "1%", true}, + {1e4, 1e3, 1e9, "1%", false}, + {1e2, 1e3, 1e9, "1%", false}, + {1e9, 1 << 62, 1 << 63, "1%", true}, + {10, 1e8, 1e9, "1M", true}, + {1e4, 1e3, 1e9, "1M", false}, + {1e2, 1e3, 1e9, "1M", false}, + {1e9, 1 << 62, 1 << 63, "1M", true}, + } + + for _, tc := range cases { + minFree, err := ParseSize(tc.minFree) + if err != nil { + t.Errorf("Failed to parse %v: %v", tc.minFree, err) + continue + } + usage := fs.Usage{Free: tc.free, Total: tc.total} + if ok := checkAvailableSpace(tc.req, minFree, usage); ok != tc.ok { + t.Errorf("checkAvailableSpace(%v, %v, %v) == %v, expected %v", tc.req, minFree, usage, ok, tc.ok) + } + } +} diff --git a/lib/fs/basicfs.go b/lib/fs/basicfs.go index cf39985f0..081916b7a 100644 --- a/lib/fs/basicfs.go +++ b/lib/fs/basicfs.go @@ -295,8 +295,8 @@ func (f *BasicFilesystem) Usage(name string) (Usage, error) { return Usage{}, err } return Usage{ - Free: int64(u.Free), - Total: int64(u.Total), + Free: u.Free, + Total: u.Total, }, nil } diff --git a/lib/fs/filesystem.go b/lib/fs/filesystem.go index 13735927b..d03c73978 100644 --- a/lib/fs/filesystem.go +++ b/lib/fs/filesystem.go @@ -91,8 +91,8 @@ func (fm FileMode) String() string { // Usage represents filesystem space usage type Usage struct { - Free int64 - Total int64 + Free uint64 + Total uint64 } type Matcher interface { diff --git a/lib/model/folder_sendrecv.go b/lib/model/folder_sendrecv.go index 6f396e8ba..605ae2079 100644 --- a/lib/model/folder_sendrecv.go +++ b/lib/model/folder_sendrecv.go @@ -995,7 +995,7 @@ func (f *sendReceiveFolder) renameFile(cur, source, target protocol.FileInfo, sn tempName := fs.TempName(target.Name) if f.versioner != nil { - err = f.CheckAvailableSpace(source.Size) + err = f.CheckAvailableSpace(uint64(source.Size)) if err == nil { err = osutil.Copy(f.CopyRangeMethod, f.fs, f.fs, source.Name, tempName) if err == nil { @@ -1239,7 +1239,7 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch } for state := range in { - if err := f.CheckAvailableSpace(state.file.Size); err != nil { + if err := f.CheckAvailableSpace(uint64(state.file.Size)); err != nil { state.fail(err) // Nothing more to do for this failed file, since it would use to much disk space out <- state.sharedPullerState