Browse Source

lib: Use uint64 for disk stats (ref #3930) (#7019)

Simon Frei 5 years ago
parent
commit
48da6f0f22

+ 4 - 7
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
 }

+ 17 - 6
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)

+ 35 - 1
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)
+		}
+	}
+}

+ 2 - 2
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
 }
 

+ 2 - 2
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 {

+ 2 - 2
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