locations.go 9.5 KB

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