locations.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright (C) 2019 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package locations
  7. import (
  8. "fmt"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. "time"
  14. "github.com/syncthing/syncthing/lib/fs"
  15. )
  16. type LocationEnum string
  17. // Use strings as keys to make printout and serialization of the locations map
  18. // more meaningful.
  19. const (
  20. ConfigFile LocationEnum = "config"
  21. CertFile LocationEnum = "certFile"
  22. KeyFile LocationEnum = "keyFile"
  23. HTTPSCertFile LocationEnum = "httpsCertFile"
  24. HTTPSKeyFile LocationEnum = "httpsKeyFile"
  25. Database LocationEnum = "database"
  26. LogFile LocationEnum = "logFile"
  27. CsrfTokens LocationEnum = "csrfTokens"
  28. PanicLog LocationEnum = "panicLog"
  29. AuditLog LocationEnum = "auditLog"
  30. GUIAssets LocationEnum = "GUIAssets"
  31. DefFolder LocationEnum = "defFolder"
  32. FailuresFile LocationEnum = "FailuresFile"
  33. )
  34. type BaseDirEnum string
  35. const (
  36. // Overridden by --home flag
  37. ConfigBaseDir BaseDirEnum = "config"
  38. DataBaseDir BaseDirEnum = "data"
  39. // User's home directory, *not* --home flag
  40. UserHomeBaseDir BaseDirEnum = "userHome"
  41. LevelDBDir = "index-v0.14.0.db"
  42. )
  43. // Platform dependent directories
  44. var baseDirs = make(map[BaseDirEnum]string, 3)
  45. func init() {
  46. userHome := userHomeDir()
  47. config := defaultConfigDir(userHome)
  48. baseDirs[UserHomeBaseDir] = userHome
  49. baseDirs[ConfigBaseDir] = config
  50. baseDirs[DataBaseDir] = defaultDataDir(userHome, config)
  51. err := expandLocations()
  52. if err != nil {
  53. fmt.Println(err)
  54. panic("Failed to expand locations at init time")
  55. }
  56. }
  57. func SetBaseDir(baseDirName BaseDirEnum, path string) error {
  58. if !filepath.IsAbs(path) {
  59. var err error
  60. path, err = filepath.Abs(path)
  61. if err != nil {
  62. return err
  63. }
  64. }
  65. _, ok := baseDirs[baseDirName]
  66. if !ok {
  67. return fmt.Errorf("unknown base dir: %s", baseDirName)
  68. }
  69. baseDirs[baseDirName] = filepath.Clean(path)
  70. return expandLocations()
  71. }
  72. func Get(location LocationEnum) string {
  73. return locations[location]
  74. }
  75. func GetBaseDir(baseDir BaseDirEnum) string {
  76. return baseDirs[baseDir]
  77. }
  78. // Use the variables from baseDirs here
  79. var locationTemplates = map[LocationEnum]string{
  80. ConfigFile: "${config}/config.xml",
  81. CertFile: "${config}/cert.pem",
  82. KeyFile: "${config}/key.pem",
  83. HTTPSCertFile: "${config}/https-cert.pem",
  84. HTTPSKeyFile: "${config}/https-key.pem",
  85. Database: "${data}/" + LevelDBDir,
  86. LogFile: "${data}/syncthing.log", // --logfile on Windows
  87. CsrfTokens: "${data}/csrftokens.txt",
  88. PanicLog: "${data}/panic-${timestamp}.log",
  89. AuditLog: "${data}/audit-${timestamp}.log",
  90. GUIAssets: "${config}/gui",
  91. DefFolder: "${userHome}/Sync",
  92. FailuresFile: "${data}/failures-unreported.txt",
  93. }
  94. var locations = make(map[LocationEnum]string)
  95. // expandLocations replaces the variables in the locations map with actual
  96. // directory locations.
  97. func expandLocations() error {
  98. newLocations := make(map[LocationEnum]string)
  99. for key, dir := range locationTemplates {
  100. for varName, value := range baseDirs {
  101. dir = strings.ReplaceAll(dir, "${"+string(varName)+"}", value)
  102. }
  103. var err error
  104. dir, err = fs.ExpandTilde(dir)
  105. if err != nil {
  106. return err
  107. }
  108. newLocations[key] = filepath.Clean(dir)
  109. }
  110. locations = newLocations
  111. return nil
  112. }
  113. // defaultConfigDir returns the default configuration directory, as figured
  114. // out by various the environment variables present on each platform, or dies
  115. // trying.
  116. func defaultConfigDir(userHome string) string {
  117. switch runtime.GOOS {
  118. case "windows":
  119. if p := os.Getenv("LocalAppData"); p != "" {
  120. return filepath.Join(p, "Syncthing")
  121. }
  122. return filepath.Join(os.Getenv("AppData"), "Syncthing")
  123. case "darwin":
  124. return filepath.Join(userHome, "Library/Application Support/Syncthing")
  125. default:
  126. if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
  127. return filepath.Join(xdgCfg, "syncthing")
  128. }
  129. return filepath.Join(userHome, ".config/syncthing")
  130. }
  131. }
  132. // defaultDataDir returns the default data directory, which usually is the
  133. // config directory but might be something else.
  134. func defaultDataDir(userHome, config string) string {
  135. switch runtime.GOOS {
  136. case "windows", "darwin":
  137. return config
  138. default:
  139. // If a database exists at the "normal" location, use that anyway.
  140. if _, err := os.Lstat(filepath.Join(config, LevelDBDir)); err == nil {
  141. return config
  142. }
  143. // Always use this env var, as it's explicitly set by the user
  144. if xdgHome := os.Getenv("XDG_DATA_HOME"); xdgHome != "" {
  145. return filepath.Join(xdgHome, "syncthing")
  146. }
  147. // Only use the XDG default, if a syncthing specific dir already
  148. // exists. Existence of ~/.local/share is not deemed enough, as
  149. // it may also exist erroneously on non-XDG systems.
  150. xdgDefault := filepath.Join(userHome, ".local/share/syncthing")
  151. if _, err := os.Lstat(xdgDefault); err == nil {
  152. return xdgDefault
  153. }
  154. // FYI: XDG_DATA_DIRS is not relevant, as it is for system-wide
  155. // data dirs, not user specific ones.
  156. return config
  157. }
  158. }
  159. // userHomeDir returns the user's home directory, or dies trying.
  160. func userHomeDir() string {
  161. userHome, err := fs.ExpandTilde("~")
  162. if err != nil {
  163. fmt.Println(err)
  164. panic("Failed to get user home dir")
  165. }
  166. return userHome
  167. }
  168. func GetTimestamped(key LocationEnum) string {
  169. // We take the roundtrip via "${timestamp}" instead of passing the path
  170. // directly through time.Format() to avoid issues when the path we are
  171. // expanding contains numbers; otherwise for example
  172. // /home/user2006/.../panic-20060102-150405.log would get both instances of
  173. // 2006 replaced by 2015...
  174. tpl := locations[key]
  175. now := time.Now().Format("20060102-150405")
  176. return strings.ReplaceAll(tpl, "${timestamp}", now)
  177. }