From 52ac7e0e185f20b9fa9504691c3bdef317fcf02e Mon Sep 17 00:00:00 2001 From: youdie006 Date: Tue, 8 Sep 2026 18:36:55 +0900 Subject: [PATCH] fs: fix about showing a negative total when a quota reaches the int64 maximum NewUsageValue exists to clip an oversized quota to the maximum value of an int64, which is what dc95f36bc added it for when Box raised the Enterprise space_amount to 1e+18 and started returning it as a float. For the float64 instantiation the guard misses its own boundary. float64(math.MaxInt64) is not 2**63-1, it rounds up to 2**63, so a quota of exactly 2**63 fails the comparison and falls through to the int64 conversion, which the spec leaves implementation dependent for an unrepresentable value. On linux/amd64 it wraps: Before: rclone about -> Total=-9223372036854775808 After: rclone about -> Total=9223372036854775807 A negative total is not just a wrong number. vfs.Statfs documents -1 as "not known", vfs.fillInMissingSizes branches on total < 0, and serve sftp only computes its usage percentage when total > 0, so the value is read back as a missing quota. The int64 and uint64 instantiations are unaffected, since for them T(int64(math.MaxInt64)) is exact and clipping MaxInt64 to MaxInt64 is a no-op. --- fs/types.go | 3 ++- fs/types_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 fs/types_test.go diff --git a/fs/types.go b/fs/types.go index aed6926b6..7b4267c21 100644 --- a/fs/types.go +++ b/fs/types.go @@ -340,7 +340,8 @@ func NewUsageValue[T interface { int64 | uint64 | float64 }](value T) *int64 { p := new(int64) - if value > T(int64(math.MaxInt64)) { + // float64(math.MaxInt64) rounds up to 2**63 which doesn't fit in an int64 + if value >= T(int64(math.MaxInt64)) { *p = math.MaxInt64 } else { *p = int64(value) diff --git a/fs/types_test.go b/fs/types_test.go new file mode 100644 index 000000000..cd47bfcdb --- /dev/null +++ b/fs/types_test.go @@ -0,0 +1,53 @@ +package fs + +import ( + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewUsageValueInt64(t *testing.T) { + for _, test := range []struct { + in int64 + want int64 + }{ + {0, 0}, + {1 << 60, 1 << 60}, + {math.MaxInt64, math.MaxInt64}, + } { + assert.Equal(t, test.want, *NewUsageValue(test.in), "in=%d", test.in) + } +} + +func TestNewUsageValueUint64(t *testing.T) { + for _, test := range []struct { + in uint64 + want int64 + }{ + {0, 0}, + {math.MaxInt64, math.MaxInt64}, + {math.MaxInt64 + 1, math.MaxInt64}, + {math.MaxUint64, math.MaxInt64}, + } { + assert.Equal(t, test.want, *NewUsageValue(test.in), "in=%d", test.in) + } +} + +func TestNewUsageValueFloat64(t *testing.T) { + // Largest float64 strictly below 2**63 - this still fits in an int64. + const belowMax = float64(9223372036854773760) + for _, test := range []struct { + in float64 + want int64 + }{ + {0, 0}, + {1e18, 1000000000000000000}, // Box reports space_amount like this + {belowMax, 9223372036854773760}, + {float64(math.MaxInt64), math.MaxInt64}, // rounds up to 2**63 + {1e19, math.MaxInt64}, + {math.Inf(1), math.MaxInt64}, + } { + assert.Equal(t, test.want, *NewUsageValue(test.in), "in=%v", test.in) + } +}