| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- // Copyright (C) 2019 The Syncthing Authors.
- //
- // This Source Code Form is subject to the terms of the Mozilla Public
- // License, v. 2.0. If a copy of the MPL was not distributed with this file,
- // You can obtain one at https://mozilla.org/MPL/2.0/.
- package locations
- import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "time"
- "github.com/syncthing/syncthing/lib/build"
- "github.com/syncthing/syncthing/lib/fs"
- )
- type LocationEnum string
- // Use strings as keys to make printout and serialization of the locations map
- // more meaningful.
- const (
- ConfigFile LocationEnum = "config"
- CertFile LocationEnum = "certFile"
- KeyFile LocationEnum = "keyFile"
- HTTPSCertFile LocationEnum = "httpsCertFile"
- HTTPSKeyFile LocationEnum = "httpsKeyFile"
- LegacyDatabase LocationEnum = "legacyDatabase"
- Database LocationEnum = "database"
- LogFile LocationEnum = "logFile"
- PanicLog LocationEnum = "panicLog"
- AuditLog LocationEnum = "auditLog"
- GUIAssets LocationEnum = "guiAssets"
- DefFolder LocationEnum = "defFolder"
- LockFile LocationEnum = "lockFile"
- )
- type BaseDirEnum string
- const (
- // Overridden by --home flag, $STHOMEDIR, --config flag, or $STCONFDIR
- ConfigBaseDir BaseDirEnum = "config"
- // Overridden by --home flag, $STHOMEDIR, --data flag, or $STDATADIR
- DataBaseDir BaseDirEnum = "data"
- // User's home directory, *not* --home flag
- UserHomeBaseDir BaseDirEnum = "userHome"
- levelDBDir = "index-v0.14.0.db"
- databaseName = "index-v2"
- configFileName = "config.xml"
- defaultStateDir = ".local/state/syncthing"
- oldDefaultConfigDir = ".config/syncthing"
- )
- // Platform dependent directories
- var baseDirs = make(map[BaseDirEnum]string)
- func init() {
- userHome := userHomeDir()
- config := defaultConfigDir(userHome)
- data := defaultDataDir(userHome, config)
- baseDirs[UserHomeBaseDir] = userHome
- baseDirs[ConfigBaseDir] = config
- baseDirs[DataBaseDir] = data
- if err := expandLocations(); err != nil {
- fmt.Println(err)
- panic("Failed to expand locations at init time")
- }
- }
- // Set overrides a location to the given path, making sure to it points to an
- // absolute path first. Only the special "-" value will be used verbatim.
- func Set(locationName LocationEnum, path string) error {
- if !filepath.IsAbs(path) && path != "-" {
- var err error
- path, err = filepath.Abs(path)
- if err != nil {
- return err
- }
- }
- _, ok := locationTemplates[locationName]
- if !ok {
- return fmt.Errorf("unknown location: %s", locationName)
- }
- locations[locationName] = filepath.Clean(path)
- return nil
- }
- func SetBaseDir(baseDirName BaseDirEnum, path string) error {
- if !filepath.IsAbs(path) {
- var err error
- path, err = filepath.Abs(path)
- if err != nil {
- return err
- }
- }
- if _, ok := baseDirs[baseDirName]; !ok {
- return fmt.Errorf("unknown base dir: %s", baseDirName)
- }
- baseDirs[baseDirName] = filepath.Clean(path)
- return expandLocations()
- }
- func Get(location LocationEnum) string {
- return locations[location]
- }
- func GetBaseDir(baseDir BaseDirEnum) string {
- return baseDirs[baseDir]
- }
- // Use the variables from baseDirs here
- var locationTemplates = map[LocationEnum]string{
- ConfigFile: "${config}/config.xml",
- CertFile: "${config}/cert.pem",
- KeyFile: "${config}/key.pem",
- HTTPSCertFile: "${config}/https-cert.pem",
- HTTPSKeyFile: "${config}/https-key.pem",
- LegacyDatabase: "${data}/" + levelDBDir,
- Database: "${data}/" + databaseName,
- LogFile: "${data}/syncthing.log", // --logfile on Windows
- PanicLog: "${data}/panic-%{timestamp}.log",
- AuditLog: "${data}/audit-%{timestamp}.log",
- GUIAssets: "${config}/gui",
- DefFolder: "${userHome}/Sync",
- LockFile: "${data}/syncthing.lock",
- }
- var locations = make(map[LocationEnum]string)
- // expandLocations replaces the variables in the locations map with actual
- // directory locations.
- func expandLocations() error {
- newLocations := make(map[LocationEnum]string)
- for key, dir := range locationTemplates {
- dir = os.Expand(dir, func(s string) string {
- return baseDirs[BaseDirEnum(s)]
- })
- var err error
- dir, err = fs.ExpandTilde(dir)
- if err != nil {
- return err
- }
- newLocations[key] = filepath.Clean(dir)
- }
- locations = newLocations
- return nil
- }
- // ListExpandedPaths returns a machine-readable mapping of the currently configured locations.
- func ListExpandedPaths() map[string]string {
- res := make(map[string]string, len(locations))
- for key, path := range baseDirs {
- res["baseDir-"+string(key)] = path
- }
- for key, path := range locations {
- res[string(key)] = path
- }
- return res
- }
- // PrettyPaths returns a nicely formatted, human-readable listing
- func PrettyPaths() string {
- var b strings.Builder
- fmt.Fprintf(&b, "Configuration file:\n\t%s\n\n", Get(ConfigFile))
- fmt.Fprintf(&b, "Device private key & certificate files:\n\t%s\n\t%s\n\n", Get(KeyFile), Get(CertFile))
- fmt.Fprintf(&b, "GUI / API HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", Get(HTTPSKeyFile), Get(HTTPSCertFile))
- fmt.Fprintf(&b, "Database location:\n\t%s\n\n", Get(Database))
- fmt.Fprintf(&b, "Log file:\n\t%s\n\n", Get(LogFile))
- fmt.Fprintf(&b, "GUI override directory:\n\t%s\n\n", Get(GUIAssets))
- fmt.Fprintf(&b, "Default sync folder directory:\n\t%s\n\n", Get(DefFolder))
- return b.String()
- }
- // defaultConfigDir returns the default configuration directory, as figured
- // out by various the environment variables present on each platform, or dies
- // trying.
- func defaultConfigDir(userHome string) string {
- switch {
- case build.IsWindows:
- return windowsConfigDataDir()
- case build.IsDarwin, build.IsIOS:
- return darwinConfigDataDir(userHome)
- default:
- return unixConfigDir(userHome, os.Getenv("XDG_CONFIG_HOME"), os.Getenv("XDG_STATE_HOME"), fileExists)
- }
- }
- // defaultDataDir returns the default data directory, where we store the
- // database, log files, etc.
- func defaultDataDir(userHome, configDir string) string {
- if build.IsWindows || build.IsDarwin || build.IsIOS {
- return configDir
- }
- return unixDataDir(userHome, configDir, os.Getenv("XDG_DATA_HOME"), os.Getenv("XDG_STATE_HOME"), fileExists)
- }
- func windowsConfigDataDir() string {
- if p := os.Getenv("LocalAppData"); p != "" {
- return filepath.Join(p, "Syncthing")
- }
- return filepath.Join(os.Getenv("AppData"), "Syncthing")
- }
- func darwinConfigDataDir(userHome string) string {
- return filepath.Join(userHome, "Library/Application Support/Syncthing")
- }
- func unixConfigDir(userHome, xdgConfigHome, xdgStateHome string, fileExists func(string) bool) string {
- // Legacy: if our config exists under $XDG_CONFIG_HOME/syncthing,
- // use that. The variable should be set to an absolute path or be
- // ignored, but that's not what we did previously, so we retain the
- // old behavior.
- if xdgConfigHome != "" {
- candidate := filepath.Join(xdgConfigHome, "syncthing")
- if fileExists(filepath.Join(candidate, configFileName)) {
- return candidate
- }
- }
- // Legacy: if our config exists under ~/.config/syncthing, use that
- candidate := filepath.Join(userHome, oldDefaultConfigDir)
- if fileExists(filepath.Join(candidate, configFileName)) {
- return candidate
- }
- // If XDG_STATE_HOME is set to an absolute path, use that
- if filepath.IsAbs(xdgStateHome) {
- return filepath.Join(xdgStateHome, "syncthing")
- }
- // Use our default
- return filepath.Join(userHome, defaultStateDir)
- }
- // unixDataDir returns the default data directory, where we store the
- // database, log files, etc, on Unix-like systems.
- func unixDataDir(userHome, configDir, xdgDataHome, xdgStateHome string, fileExists func(string) bool) string {
- // If a database exists at the config location, use that. This is the
- // most common case for both legacy (~/.config/syncthing) and current
- // (~/.local/state/syncthing) setups.
- if fileExists(filepath.Join(configDir, databaseName)) ||
- fileExists(filepath.Join(configDir, levelDBDir)) {
- return configDir
- }
- // Legacy: if a database exists under $XDG_DATA_HOME/syncthing, use
- // that. The variable should be set to an absolute path or be ignored,
- // but that's not what we did previously, so we retain the old behavior.
- if xdgDataHome != "" {
- candidate := filepath.Join(xdgDataHome, "syncthing")
- if fileExists(filepath.Join(candidate, databaseName)) ||
- fileExists(filepath.Join(candidate, levelDBDir)) {
- return candidate
- }
- }
- // Legacy: if a database exists under ~/.config/syncthing, use that
- candidate := filepath.Join(userHome, oldDefaultConfigDir)
- if fileExists(filepath.Join(candidate, databaseName)) ||
- fileExists(filepath.Join(candidate, levelDBDir)) {
- return candidate
- }
- // If XDG_STATE_HOME is set to an absolute path, use that
- if filepath.IsAbs(xdgStateHome) {
- return filepath.Join(xdgStateHome, "syncthing")
- }
- // Use our default
- return filepath.Join(userHome, defaultStateDir)
- }
- // userHomeDir returns the user's home directory, or dies trying.
- func userHomeDir() string {
- userHome, err := fs.ExpandTilde("~")
- if err != nil {
- fmt.Println(err)
- panic("Failed to get user home dir")
- }
- return userHome
- }
- func GetTimestamped(key LocationEnum) string {
- return getTimestampedAt(key, time.Now())
- }
- func getTimestampedAt(key LocationEnum, when time.Time) string {
- // We take the roundtrip via "%{timestamp}" instead of passing the path
- // directly through time.Format() to avoid issues when the path we are
- // expanding contains numbers; otherwise for example
- // /home/user2006/.../panic-20060102-150405.log would get both instances of
- // 2006 replaced by 2015...
- tpl := locations[key]
- timestamp := when.Format("20060102-150405")
- return strings.ReplaceAll(tpl, "%{timestamp}", timestamp)
- }
- func fileExists(path string) bool {
- _, err := os.Lstat(path)
- return err == nil
- }
|