conffile.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. // Package conffile contains code to load, manipulate, and access config file
  4. // settings.
  5. package conffile
  6. import (
  7. "bytes"
  8. "encoding/json"
  9. "fmt"
  10. "os"
  11. "runtime"
  12. "tailscale.com/feature/buildfeatures"
  13. "tailscale.com/ipn"
  14. )
  15. // Config describes a config file.
  16. type Config struct {
  17. Path string // disk path of HuJSON, or VMUserDataPath
  18. Raw []byte // raw bytes from disk, in HuJSON form
  19. Std []byte // standardized JSON form
  20. Version string // "alpha0" for now
  21. // Parsed is the parsed config, converted from its on-disk version to the
  22. // latest known format.
  23. //
  24. // As of 2023-10-15 there is exactly one format ("alpha0") so this is both
  25. // the on-disk format and the in-memory upgraded format.
  26. Parsed ipn.ConfigVAlpha
  27. }
  28. // WantRunning reports whether c is non-nil and it's configured to be running.
  29. func (c *Config) WantRunning() bool {
  30. return c != nil && !c.Parsed.Enabled.EqualBool(false)
  31. }
  32. // VMUserDataPath is a sentinel value for Load to use to get the data
  33. // from the VM's metadata service's user-data field.
  34. const VMUserDataPath = "vm:user-data"
  35. // hujsonStandardize is set to hujson.Standardize by conffile_hujson.go on
  36. // platforms that support config files.
  37. var hujsonStandardize func([]byte) ([]byte, error)
  38. // Load reads and parses the config file at the provided path on disk.
  39. func Load(path string) (*Config, error) {
  40. switch runtime.GOOS {
  41. case "ios", "android":
  42. // compile-time for deadcode elimination
  43. return nil, fmt.Errorf("config file loading not supported on %q", runtime.GOOS)
  44. }
  45. var c Config
  46. c.Path = path
  47. var err error
  48. switch path {
  49. case VMUserDataPath:
  50. c.Raw, err = readVMUserData()
  51. default:
  52. c.Raw, err = os.ReadFile(path)
  53. }
  54. if err != nil {
  55. return nil, err
  56. }
  57. if buildfeatures.HasHuJSONConf && hujsonStandardize != nil {
  58. c.Std, err = hujsonStandardize(c.Raw)
  59. if err != nil {
  60. return nil, fmt.Errorf("error parsing config file %s HuJSON/JSON: %w", path, err)
  61. }
  62. } else {
  63. c.Std = c.Raw // config file must be valid JSON with ts_omit_hujsonconf
  64. }
  65. var ver struct {
  66. Version string `json:"version"`
  67. }
  68. if err := json.Unmarshal(c.Std, &ver); err != nil {
  69. if !buildfeatures.HasHuJSONConf {
  70. return nil, fmt.Errorf("error parsing config file %s, which must be valid standard JSON: %w", path, err)
  71. }
  72. return nil, fmt.Errorf("error parsing config file %s: %w", path, err)
  73. }
  74. switch ver.Version {
  75. case "":
  76. return nil, fmt.Errorf("error parsing config file %s: no \"version\" field defined", path)
  77. case "alpha0":
  78. default:
  79. return nil, fmt.Errorf("error parsing config file %s: unsupported \"version\" value %q; want \"alpha0\" for now", path, ver.Version)
  80. }
  81. c.Version = ver.Version
  82. jd := json.NewDecoder(bytes.NewReader(c.Std))
  83. jd.DisallowUnknownFields()
  84. err = jd.Decode(&c.Parsed)
  85. if err != nil {
  86. return nil, fmt.Errorf("error parsing config file %s: %w", path, err)
  87. }
  88. if jd.More() {
  89. return nil, fmt.Errorf("error parsing config file %s: trailing data after JSON object", path)
  90. }
  91. return &c, nil
  92. }