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.
This commit is contained in:
youdie006
2026-09-09 10:31:29 +01:00
committed by Nick Craig-Wood
parent 5bbc5d5545
commit 52ac7e0e18
2 changed files with 55 additions and 1 deletions
+2 -1
View File
@@ -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)
+53
View File
@@ -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)
}
}