Strict int64 parsing via the startup validation framework: a SET but invalid value (negative, float, null, non-numeric) aborts startup naming the key and value. An omitted key resolves after state_dir validation to max(75% of free bytes on the filesystem containing <state_dir>/cache/, 500 MiB), measured via an injectable statfs probe; the floor never applies to explicit values. Zero is valid and means the disk cache is disabled. The effective limit is logged at startup.
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"math"
|
|
"os"
|
|
"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
|
|
|
|
// cacheDirPerms is the permission mode for the cache directory created
|
|
// before probing free space, matching the state directory permissions.
|
|
const cacheDirPerms = 0o750
|
|
|
|
// freeSpaceFractionNumerator and freeSpaceFractionDenominator express
|
|
// the 75% share of free space used for the computed default limit as
|
|
// integer arithmetic (dividing before multiplying avoids overflow).
|
|
const (
|
|
freeSpaceFractionNumerator uint64 = 3
|
|
freeSpaceFractionDenominator uint64 = 4
|
|
)
|
|
|
|
// 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
|
|
if err := syscall.Statfs(path, &stat); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if stat.Bsize < 0 {
|
|
return 0, fmt.Errorf("statfs reported negative block size %d for %q", stat.Bsize, path)
|
|
}
|
|
|
|
blockSize := uint64(stat.Bsize) //nolint:gosec // G115: negative Bsize rejected above
|
|
|
|
return stat.Bavail * blockSize, nil
|
|
}
|
|
|
|
// ComputeDefaultCacheMaxBytes returns the default cache size limit for
|
|
// the filesystem containing cacheDir: 75% of the free bytes reported
|
|
// by probe, with a floor of DefaultCacheMaxBytesFloor.
|
|
func ComputeDefaultCacheMaxBytes(cacheDir string, probe FreeSpaceProbeFunc) (int64, error) {
|
|
freeBytes, err := probe(cacheDir)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("config key %q: cannot determine free space for %q: %w",
|
|
"cache_max_bytes", cacheDir, err)
|
|
}
|
|
|
|
computed := freeBytes / freeSpaceFractionDenominator * freeSpaceFractionNumerator
|
|
if computed > math.MaxInt64 {
|
|
computed = math.MaxInt64
|
|
}
|
|
|
|
limit := int64(computed) //nolint:gosec // G115: clamped to MaxInt64 above
|
|
|
|
if limit < DefaultCacheMaxBytesFloor {
|
|
limit = DefaultCacheMaxBytesFloor
|
|
}
|
|
|
|
return limit, nil
|
|
}
|
|
|
|
// resolveCacheMaxBytes finalizes CacheMaxBytes after state_dir
|
|
// validation: an explicitly configured value is kept as-is (no floor
|
|
// applies), while an omitted key receives the computed default based
|
|
// on free space in <state_dir>/cache/. The cache directory is created
|
|
// first so statfs measures the filesystem that will actually hold the
|
|
// cache. The effective limit is logged either way.
|
|
func (c *Config) resolveCacheMaxBytes(log *slog.Logger, probe FreeSpaceProbeFunc) error {
|
|
if !c.cacheMaxBytesExplicit {
|
|
cacheDir := filepath.Join(c.StateDir, "cache")
|
|
|
|
if err := os.MkdirAll(cacheDir, cacheDirPerms); err != nil {
|
|
return fmt.Errorf("config key %q: cannot create cache directory %q: %w",
|
|
"cache_max_bytes", cacheDir, err)
|
|
}
|
|
|
|
limit, err := ComputeDefaultCacheMaxBytes(cacheDir, probe)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.CacheMaxBytes = limit
|
|
|
|
log.Info("computed default cache size limit from free space",
|
|
"cache_max_bytes", limit,
|
|
"cache_dir", cacheDir,
|
|
)
|
|
}
|
|
|
|
log.Info("effective cache size limit",
|
|
"cache_max_bytes", c.CacheMaxBytes,
|
|
"cache_disabled", c.CacheMaxBytes == 0,
|
|
)
|
|
|
|
return nil
|
|
}
|