| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- // Package conffile contains code to load, manipulate, and access config file
- // settings.
- package conffile
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "os"
- "runtime"
- "tailscale.com/feature/buildfeatures"
- "tailscale.com/ipn"
- )
- // Config describes a config file.
- type Config struct {
- Path string // disk path of HuJSON, or VMUserDataPath
- Raw []byte // raw bytes from disk, in HuJSON form
- Std []byte // standardized JSON form
- Version string // "alpha0" for now
- // Parsed is the parsed config, converted from its on-disk version to the
- // latest known format.
- //
- // As of 2023-10-15 there is exactly one format ("alpha0") so this is both
- // the on-disk format and the in-memory upgraded format.
- Parsed ipn.ConfigVAlpha
- }
- // WantRunning reports whether c is non-nil and it's configured to be running.
- func (c *Config) WantRunning() bool {
- return c != nil && !c.Parsed.Enabled.EqualBool(false)
- }
- // VMUserDataPath is a sentinel value for Load to use to get the data
- // from the VM's metadata service's user-data field.
- const VMUserDataPath = "vm:user-data"
- // hujsonStandardize is set to hujson.Standardize by conffile_hujson.go on
- // platforms that support config files.
- var hujsonStandardize func([]byte) ([]byte, error)
- // Load reads and parses the config file at the provided path on disk.
- func Load(path string) (*Config, error) {
- switch runtime.GOOS {
- case "ios", "android":
- // compile-time for deadcode elimination
- return nil, fmt.Errorf("config file loading not supported on %q", runtime.GOOS)
- }
- var c Config
- c.Path = path
- var err error
- switch path {
- case VMUserDataPath:
- c.Raw, err = readVMUserData()
- default:
- c.Raw, err = os.ReadFile(path)
- }
- if err != nil {
- return nil, err
- }
- if buildfeatures.HasHuJSONConf && hujsonStandardize != nil {
- c.Std, err = hujsonStandardize(c.Raw)
- if err != nil {
- return nil, fmt.Errorf("error parsing config file %s HuJSON/JSON: %w", path, err)
- }
- } else {
- c.Std = c.Raw // config file must be valid JSON with ts_omit_hujsonconf
- }
- var ver struct {
- Version string `json:"version"`
- }
- if err := json.Unmarshal(c.Std, &ver); err != nil {
- if !buildfeatures.HasHuJSONConf {
- return nil, fmt.Errorf("error parsing config file %s, which must be valid standard JSON: %w", path, err)
- }
- return nil, fmt.Errorf("error parsing config file %s: %w", path, err)
- }
- switch ver.Version {
- case "":
- return nil, fmt.Errorf("error parsing config file %s: no \"version\" field defined", path)
- case "alpha0":
- default:
- return nil, fmt.Errorf("error parsing config file %s: unsupported \"version\" value %q; want \"alpha0\" for now", path, ver.Version)
- }
- c.Version = ver.Version
- jd := json.NewDecoder(bytes.NewReader(c.Std))
- jd.DisallowUnknownFields()
- err = jd.Decode(&c.Parsed)
- if err != nil {
- return nil, fmt.Errorf("error parsing config file %s: %w", path, err)
- }
- if jd.More() {
- return nil, fmt.Errorf("error parsing config file %s: trailing data after JSON object", path)
- }
- return &c, nil
- }
|