locations.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. "strings"
  12. "time"
  13. "github.com/syncthing/syncthing/lib/build"
  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. PanicLog LocationEnum = "panicLog"
  28. AuditLog LocationEnum = "auditLog"
  29. GUIAssets LocationEnum = "guiAssets"
  30. DefFolder LocationEnum = "defFolder"
  31. LockFile LocationEnum = "lockFile"
  32. )
  33. type BaseDirEnum string
  34. const (
  35. // Overridden by --home flag, $STHOMEDIR, --config flag, or $STCONFDIR
  36. ConfigBaseDir BaseDirEnum = "config"
  37. // Overridden by --home flag, $STHOMEDIR, --data flag, or $STDATADIR
  38. DataBaseDir BaseDirEnum = "data"
  39. // User's home directory, *not* --home flag
  40. UserHomeBaseDir BaseDirEnum = "userHome"
  41. LevelDBDir = "index-v0.14.0.db"
  42. configFileName = "config.xml"
  43. defaultStateDir = ".local/state/syncthing"
  44. oldDefaultConfigDir = ".config/syncthing"
  45. )
  46. // Platform dependent directories
  47. var baseDirs = make(map[BaseDirEnum]string, 3)
  48. func init() {
  49. userHome := userHomeDir()
  50. config := defaultConfigDir(userHome)
  51. data := defaultDataDir(userHome, config)
  52. baseDirs[UserHomeBaseDir] = userHome
  53. baseDirs[ConfigBaseDir] = config
  54. baseDirs[DataBaseDir] = data
  55. if err := expandLocations(); err != nil {
  56. fmt.Println(err)
  57. panic("Failed to expand locations at init time")
  58. }
  59. }
  60. // Set overrides a location to the given path, making sure to it points to an
  61. // absolute path first. Only the special "-" value will be used verbatim.
  62. func Set(locationName LocationEnum, path string) error {
  63. if !filepath.IsAbs(path) && path != "-" {
  64. var err error
  65. path, err = filepath.Abs(path)
  66. if err != nil {
  67. return err
  68. }
  69. }
  70. _, ok := locationTemplates[locationName]
  71. if !ok {
  72. return fmt.Errorf("unknown location: %s", locationName)
  73. }
  74. locations[locationName] = filepath.Clean(path)
  75. return nil
  76. }
  77. func SetBaseDir(baseDirName BaseDirEnum, path string) error {
  78. if !filepath.IsAbs(path) {
  79. var err error
  80. path, err = filepath.Abs(path)
  81. if err != nil {
  82. return err
  83. }
  84. }
  85. if _, ok := baseDirs[baseDirName]; !ok {
  86. return fmt.Errorf("unknown base dir: %s", baseDirName)
  87. }
  88. baseDirs[baseDirName] = filepath.Clean(path)
  89. return expandLocations()
  90. }
  91. func Get(location LocationEnum) string {
  92. return locations[location]
  93. }
  94. func GetBaseDir(baseDir BaseDirEnum) string {
  95. return baseDirs[baseDir]
  96. }
  97. // Use the variables from baseDirs here
  98. var locationTemplates = map[LocationEnum]string{
  99. ConfigFile: "${config}/config.xml",
  100. CertFile: "${config}/cert.pem",
  101. KeyFile: "${config}/key.pem",
  102. HTTPSCertFile: "${config}/https-cert.pem",
  103. HTTPSKeyFile: "${config}/https-key.pem",
  104. Database: "${data}/" + LevelDBDir,
  105. LogFile: "${data}/syncthing.log", // --logfile on Windows
  106. PanicLog: "${data}/panic-%{timestamp}.log",
  107. AuditLog: "${data}/audit-%{timestamp}.log",
  108. GUIAssets: "${config}/gui",
  109. DefFolder: "${userHome}/Sync",
  110. LockFile: "${data}/syncthing.lock",
  111. }
  112. var locations = make(map[LocationEnum]string)
  113. // expandLocations replaces the variables in the locations map with actual
  114. // directory locations.
  115. func expandLocations() error {
  116. newLocations := make(map[LocationEnum]string)
  117. for key, dir := range locationTemplates {
  118. dir = os.Expand(dir, func(s string) string {
  119. return baseDirs[BaseDirEnum(s)]
  120. })
  121. var err error
  122. dir, err = fs.ExpandTilde(dir)
  123. if err != nil {
  124. return err
  125. }
  126. newLocations[key] = filepath.Clean(dir)
  127. }
  128. locations = newLocations
  129. return nil
  130. }
  131. // ListExpandedPaths returns a machine-readable mapping of the currently configured locations.
  132. func ListExpandedPaths() map[string]string {
  133. res := make(map[string]string, len(locations))
  134. for key, path := range baseDirs {
  135. res["baseDir-"+string(key)] = path
  136. }
  137. for key, path := range locations {
  138. res[string(key)] = path
  139. }
  140. return res
  141. }
  142. // PrettyPaths returns a nicely formatted, human-readable listing
  143. func PrettyPaths() string {
  144. var b strings.Builder
  145. fmt.Fprintf(&b, "Configuration file:\n\t%s\n\n", Get(ConfigFile))
  146. fmt.Fprintf(&b, "Device private key & certificate files:\n\t%s\n\t%s\n\n", Get(KeyFile), Get(CertFile))
  147. fmt.Fprintf(&b, "GUI / API HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", Get(HTTPSKeyFile), Get(HTTPSCertFile))
  148. fmt.Fprintf(&b, "Database location:\n\t%s\n\n", Get(Database))
  149. fmt.Fprintf(&b, "Log file:\n\t%s\n\n", Get(LogFile))
  150. fmt.Fprintf(&b, "GUI override directory:\n\t%s\n\n", Get(GUIAssets))
  151. fmt.Fprintf(&b, "Default sync folder directory:\n\t%s\n\n", Get(DefFolder))
  152. return b.String()
  153. }
  154. // defaultConfigDir returns the default configuration directory, as figured
  155. // out by various the environment variables present on each platform, or dies
  156. // trying.
  157. func defaultConfigDir(userHome string) string {
  158. switch {
  159. case build.IsWindows:
  160. return windowsConfigDataDir()
  161. case build.IsDarwin, build.IsIOS:
  162. return darwinConfigDataDir(userHome)
  163. default:
  164. return unixConfigDir(userHome, os.Getenv("XDG_CONFIG_HOME"), os.Getenv("XDG_STATE_HOME"), fileExists)
  165. }
  166. }
  167. // defaultDataDir returns the default data directory, where we store the
  168. // database, log files, etc.
  169. func defaultDataDir(userHome, configDir string) string {
  170. if build.IsWindows || build.IsDarwin || build.IsIOS {
  171. return configDir
  172. }
  173. return unixDataDir(userHome, configDir, os.Getenv("XDG_DATA_HOME"), os.Getenv("XDG_STATE_HOME"), fileExists)
  174. }
  175. func windowsConfigDataDir() string {
  176. if p := os.Getenv("LocalAppData"); p != "" {
  177. return filepath.Join(p, "Syncthing")
  178. }
  179. return filepath.Join(os.Getenv("AppData"), "Syncthing")
  180. }
  181. func darwinConfigDataDir(userHome string) string {
  182. return filepath.Join(userHome, "Library/Application Support/Syncthing")
  183. }
  184. func unixConfigDir(userHome, xdgConfigHome, xdgStateHome string, fileExists func(string) bool) string {
  185. // Legacy: if our config exists under $XDG_CONFIG_HOME/syncthing,
  186. // use that. The variable should be set to an absolute path or be
  187. // ignored, but that's not what we did previously, so we retain the
  188. // old behavior.
  189. if xdgConfigHome != "" {
  190. candidate := filepath.Join(xdgConfigHome, "syncthing")
  191. if fileExists(filepath.Join(candidate, configFileName)) {
  192. return candidate
  193. }
  194. }
  195. // Legacy: if our config exists under ~/.config/syncthing, use that
  196. candidate := filepath.Join(userHome, oldDefaultConfigDir)
  197. if fileExists(filepath.Join(candidate, configFileName)) {
  198. return candidate
  199. }
  200. // If XDG_STATE_HOME is set to an absolute path, use that
  201. if filepath.IsAbs(xdgStateHome) {
  202. return filepath.Join(xdgStateHome, "syncthing")
  203. }
  204. // Use our default
  205. return filepath.Join(userHome, defaultStateDir)
  206. }
  207. // unixDataDir returns the default data directory, where we store the
  208. // database, log files, etc, on Unix-like systems.
  209. func unixDataDir(userHome, configDir, xdgDataHome, xdgStateHome string, fileExists func(string) bool) string {
  210. // If a database exists at the config location, use that. This is the
  211. // most common case for both legacy (~/.config/syncthing) and current
  212. // (~/.local/state/syncthing) setups.
  213. if fileExists(filepath.Join(configDir, LevelDBDir)) {
  214. return configDir
  215. }
  216. // Legacy: if a database exists under $XDG_DATA_HOME/syncthing, use
  217. // that. The variable should be set to an absolute path or be ignored,
  218. // but that's not what we did previously, so we retain the old behavior.
  219. if xdgDataHome != "" {
  220. candidate := filepath.Join(xdgDataHome, "syncthing")
  221. if fileExists(filepath.Join(candidate, LevelDBDir)) {
  222. return candidate
  223. }
  224. }
  225. // Legacy: if a database exists under ~/.config/syncthing, use that
  226. candidate := filepath.Join(userHome, oldDefaultConfigDir)
  227. if fileExists(filepath.Join(candidate, LevelDBDir)) {
  228. return candidate
  229. }
  230. // If XDG_STATE_HOME is set to an absolute path, use that
  231. if filepath.IsAbs(xdgStateHome) {
  232. return filepath.Join(xdgStateHome, "syncthing")
  233. }
  234. // Use our default
  235. return filepath.Join(userHome, defaultStateDir)
  236. }
  237. // userHomeDir returns the user's home directory, or dies trying.
  238. func userHomeDir() string {
  239. userHome, err := fs.ExpandTilde("~")
  240. if err != nil {
  241. fmt.Println(err)
  242. panic("Failed to get user home dir")
  243. }
  244. return userHome
  245. }
  246. func GetTimestamped(key LocationEnum) string {
  247. return getTimestampedAt(key, time.Now())
  248. }
  249. func getTimestampedAt(key LocationEnum, when time.Time) string {
  250. // We take the roundtrip via "%{timestamp}" instead of passing the path
  251. // directly through time.Format() to avoid issues when the path we are
  252. // expanding contains numbers; otherwise for example
  253. // /home/user2006/.../panic-20060102-150405.log would get both instances of
  254. // 2006 replaced by 2015...
  255. tpl := locations[key]
  256. timestamp := when.Format("20060102-150405")
  257. return strings.ReplaceAll(tpl, "%{timestamp}", timestamp)
  258. }
  259. func fileExists(path string) bool {
  260. _, err := os.Lstat(path)
  261. return err == nil
  262. }