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:
committed by
Nick Craig-Wood
parent
5bbc5d5545
commit
52ac7e0e18
+2
-1
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user