Files
nfs/cmd/nfsd/config.go
T
petrbalvin a9b8039ef7
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
feat: full NFSv4.2 server and client in pure Go
Assisted-by: GLM 5.3 Flash
2026-09-21 18:51:17 +02:00

101 lines
3.0 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
package main
import (
"errors"
"flag"
"fmt"
"os"
"sourcedock.dev/petrbalvin/interpres/v2"
)
// A tlsFile is the [tls] table of the configuration file.
type tlsFile struct {
Cert string `toml:"cert"`
Key string `toml:"key"`
}
// An exportFile is one [[export]] table of the configuration file.
type exportFile struct {
Path string `toml:"path"`
ReadOnly bool `toml:"read-only"`
RootSquash bool `toml:"root-squash"`
}
// A fileConfig mirrors the whole configuration file. Every key is
// optional; a key the file leaves out keeps the flag or the built in
// default.
type fileConfig struct {
Listen string `toml:"listen"`
LogOps bool `toml:"log-ops"`
StateDir string `toml:"state-dir"`
MaxConnections int `toml:"max-connections"`
TLS *tlsFile `toml:"tls"`
Exports []exportFile `toml:"export"`
}
// loadConfig reads and validates one configuration file. An empty path
// reads nothing and answers the zero configuration: the file is never
// read unless the operator names it.
func loadConfig(path string) (fileConfig, error) {
var cfg fileConfig
if path == "" {
return cfg, nil
}
data, err := os.ReadFile(path)
if err != nil {
return cfg, err
}
if err := interpres.Unmarshal(data, &cfg, interpres.RejectUnknownFields(true)); err != nil {
if se, ok := errors.AsType[*interpres.SyntaxError](err); ok {
return cfg, fmt.Errorf("%s:%d:%d: %s", path, se.Line, se.Column, se.Msg)
}
return cfg, fmt.Errorf("%s: %v", path, err)
}
if len(cfg.Exports) != 1 {
return cfg, fmt.Errorf("%s: exactly one [[export]] is required, found %d", path, len(cfg.Exports))
}
if cfg.Exports[0].Path == "" {
return cfg, fmt.Errorf("%s: [[export]] names no path", path)
}
return cfg, nil
}
// applyConfig folds the file configuration under the flags: the file is
// the base, every flag the operator passed overrides it. The visited set
// is the flags present on the command line, defaults included.
func applyConfig(cfg fileConfig, fs *flag.FlagSet, set func(string) bool) {
if v := cfg.Listen; v != "" && !set("addr") {
_ = fs.Set("addr", v)
}
if len(cfg.Exports) == 1 && !set("export") {
_ = fs.Set("export", cfg.Exports[0].Path)
if cfg.Exports[0].ReadOnly && !set("ro") {
_ = fs.Set("ro", "true")
}
if cfg.Exports[0].RootSquash && !set("root-squash") {
_ = fs.Set("root-squash", "true")
}
}
if cfg.LogOps && !set("log-ops") {
_ = fs.Set("log-ops", "true")
}
if v := cfg.StateDir; v != "" && !set("state-dir") {
_ = fs.Set("state-dir", v)
}
if v := cfg.MaxConnections; v != 0 && !set("max-connections") {
_ = fs.Set("max-connections", fmt.Sprint(v))
}
if cfg.TLS != nil && !set("tls-cert") && !set("tls-key") {
if cfg.TLS.Cert != "" {
_ = fs.Set("tls-cert", cfg.TLS.Cert)
}
if cfg.TLS.Key != "" {
_ = fs.Set("tls-key", cfg.TLS.Key)
}
}
}