package imgcache import ( "context" "errors" "fmt" "math" "path/filepath" "syscall" ) // DefaultCacheMaxBytesFloor is the minimum computed default for the // cache_max_bytes setting: 500 MiB. The floor applies only to the // computed default (when the key is omitted from the configuration), // never to explicitly configured values. const DefaultCacheMaxBytesFloor int64 = 524288000 // freeSpaceFractionNumerator and freeSpaceFractionDenominator express // the 75% share used for the computed default limit as integer // arithmetic (dividing before multiplying avoids overflow). const ( freeSpaceFractionNumerator uint64 = 3 freeSpaceFractionDenominator uint64 = 4 ) var errNegativeBlockSize = errors.New("statfs reported negative block size") // FreeSpaceProbeFunc reports the number of free bytes available on the // filesystem containing path. It is a function type so tests can // inject a fake probe instead of depending on the host disk. type FreeSpaceProbeFunc func(path string) (uint64, error) // defaultFreeSpaceProbe reports free filesystem bytes via statfs on // the given path, as available to unprivileged processes. func defaultFreeSpaceProbe(path string) (uint64, error) { var stat syscall.Statfs_t err := syscall.Statfs(path, &stat) if err != nil { return 0, err } if stat.Bsize < 0 { return 0, fmt.Errorf("%w %d for %q", errNegativeBlockSize, stat.Bsize, path) } blockSize := uint64(stat.Bsize) return stat.Bavail * blockSize, nil } // computeDefaultMaxBytes returns the default cache size limit: 75% of // the sum of the free bytes probe reports for /cache/ and // the bytes the cache already holds, with a floor of // DefaultCacheMaxBytesFloor. Counting what the cache holds keeps the // limit from shrinking as the cache fills. func (c *Cache) computeDefaultMaxBytes( ctx context.Context, probe FreeSpaceProbeFunc, ) (int64, error) { cacheDir := filepath.Join(c.config.StateDir, "cache") freeBytes, err := probe(cacheDir) if err != nil { return 0, fmt.Errorf( "default cache_max_bytes: cannot determine free space for %q: %w", cacheDir, err) } usedBytes, err := c.UsageBytes(ctx) if err != nil { return 0, err } // Both terms are at most math.MaxInt64, so the sum cannot overflow. //nolint:gosec // G115: UsageBytes sums file sizes, never negative spaceBytes := min(freeBytes, math.MaxInt64) + uint64(usedBytes) computed := spaceBytes / freeSpaceFractionDenominator * freeSpaceFractionNumerator computed = min(computed, math.MaxInt64) // gosec cannot see that min() above bounds computed, so it reads // this conversion as potentially overflowing. It cannot: computed is // at most math.MaxInt64 on every path here. //nolint:gosec // G115: clamped to MaxInt64 by min above limit := int64(computed) limit = max(limit, DefaultCacheMaxBytesFloor) return limit, nil }