syncthing/lib/locations/locations.go

306 lines
9.3 KiB
Go
Raw Normal View History

2019-02-12 06:58:24 +00:00
// 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"
2019-02-12 06:58:24 +00:00
"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"
Database LocationEnum = "database"
LogFile LocationEnum = "logFile"
CsrfTokens LocationEnum = "csrfTokens"
PanicLog LocationEnum = "panicLog"
AuditLog LocationEnum = "auditLog"
gui, api: Show internal config and state paths (fixes #8323) (#8324) * lib/locations: Fix enum values camelCase. * lib/locations: Remove unused FailuresFile. * cmd/syncthing: Turn around role of locations storage. Previously the locations package was used to provide default paths, possibly with an overridden home directory. Extra paths supplied on the command line were handled and passed around in the options object. To make the changed paths available to any other interested package, override the location setting from the option if supplied, instead of vice versa when not supplied. Adapt code using this to read from the locations package instead of passing through the options object. * lib/locations: Refactor showPaths to locations package. Generate a reusable string in locations.PrettyPrintPaths(). Enumerating all possible locations in different packages is error prone, so add a new public function to generate the listing as a string in the locations package. Adapt cmd/syncthing --paths to use that instead of its own console output. * lib/locations: Include CSRF token in pretty printed paths. * lib/api: New endpoint /rest/system/paths. The paths should be available for troubleshooting from a running instance. Using the --paths CLI option is not easy in some environments, so expose the locations mapping to a JSON endpoint. Add utility function ListExpandedPaths() that also filters out any entries which still contain variable placeholders. * gui: List runtime paths in separate log viewer tab. * Wrap paths. * lib/syncthing: Utilize locations.Get() instead of passing an arg. * Include base directories, move label to table caption. * gui: Switch to hard-coded paths instead of iterating over all. * gui: Break aboutModalView into tabs. Use tabs to separate authors from included third-party software. * gui: Move paths from log viewer to about modal. * lib/locations: Adjust pretty print output order to match GUI. * gui, authors: Remove additional bot names and fix indent. The indentation changed because of the tabbed about dialog, fix the authors script to respect that. Skip Syncthing*Automation in authors list as well. * Update AUTHORS list to remove bot names. * Revert AUTHORS email order change. * Do not emphasize DB and log file locations. * Review line wrapping. * review part 1: strings.Builder, naming * Rename and extend locations.Set() with error handling. Remodel the Override() function along the existing SetBaseDir() and rename it to simply Set(). Make sure to use absolute paths when given log file or GUI assets override options. Add proper error reporting if that goes wrong. * Remove obsolete comment about empty logfile option. * Don't filter out unexpanded baseDir placeholders, only ${timestamp}. * Restore behavior regarding special "-" logfile argument. If the option is given, but with empty value, assume the no log file (same as "-"). Don't try to convert the special value to an absolute path though and document this fact in a comment for the Set() function. * Use template to check for location key validity. * Don't filter out timestamp placeholders. * lib/api: Remove paths from /rest/system/status. * lib/ur: Properly initialize map in failure data (fixes #8479) Co-authored-by: Jakob Borg <jakob@kastelo.net>
2022-08-10 06:25:13 +00:00
GUIAssets LocationEnum = "guiAssets"
2019-02-12 06:58:24 +00:00
DefFolder LocationEnum = "defFolder"
)
type BaseDirEnum string
const (
// Overridden by --home flag, $STHOMEDIR, --config flag, or $STCONFDIR
2019-02-12 06:58:24 +00:00
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"
configFileName = "config.xml"
defaultStateDir = ".local/state/syncthing"
oldDefaultConfigDir = ".config/syncthing"
2019-02-12 06:58:24 +00:00
)
// Platform dependent directories
var baseDirs = make(map[BaseDirEnum]string, 3)
2019-02-12 06:58:24 +00:00
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")
2019-02-12 06:58:24 +00:00
}
}
gui, api: Show internal config and state paths (fixes #8323) (#8324) * lib/locations: Fix enum values camelCase. * lib/locations: Remove unused FailuresFile. * cmd/syncthing: Turn around role of locations storage. Previously the locations package was used to provide default paths, possibly with an overridden home directory. Extra paths supplied on the command line were handled and passed around in the options object. To make the changed paths available to any other interested package, override the location setting from the option if supplied, instead of vice versa when not supplied. Adapt code using this to read from the locations package instead of passing through the options object. * lib/locations: Refactor showPaths to locations package. Generate a reusable string in locations.PrettyPrintPaths(). Enumerating all possible locations in different packages is error prone, so add a new public function to generate the listing as a string in the locations package. Adapt cmd/syncthing --paths to use that instead of its own console output. * lib/locations: Include CSRF token in pretty printed paths. * lib/api: New endpoint /rest/system/paths. The paths should be available for troubleshooting from a running instance. Using the --paths CLI option is not easy in some environments, so expose the locations mapping to a JSON endpoint. Add utility function ListExpandedPaths() that also filters out any entries which still contain variable placeholders. * gui: List runtime paths in separate log viewer tab. * Wrap paths. * lib/syncthing: Utilize locations.Get() instead of passing an arg. * Include base directories, move label to table caption. * gui: Switch to hard-coded paths instead of iterating over all. * gui: Break aboutModalView into tabs. Use tabs to separate authors from included third-party software. * gui: Move paths from log viewer to about modal. * lib/locations: Adjust pretty print output order to match GUI. * gui, authors: Remove additional bot names and fix indent. The indentation changed because of the tabbed about dialog, fix the authors script to respect that. Skip Syncthing*Automation in authors list as well. * Update AUTHORS list to remove bot names. * Revert AUTHORS email order change. * Do not emphasize DB and log file locations. * Review line wrapping. * review part 1: strings.Builder, naming * Rename and extend locations.Set() with error handling. Remodel the Override() function along the existing SetBaseDir() and rename it to simply Set(). Make sure to use absolute paths when given log file or GUI assets override options. Add proper error reporting if that goes wrong. * Remove obsolete comment about empty logfile option. * Don't filter out unexpanded baseDir placeholders, only ${timestamp}. * Restore behavior regarding special "-" logfile argument. If the option is given, but with empty value, assume the no log file (same as "-"). Don't try to convert the special value to an absolute path though and document this fact in a comment for the Set() function. * Use template to check for location key validity. * Don't filter out timestamp placeholders. * lib/api: Remove paths from /rest/system/status. * lib/ur: Properly initialize map in failure data (fixes #8479) Co-authored-by: Jakob Borg <jakob@kastelo.net>
2022-08-10 06:25:13 +00:00
// 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
}
2019-02-12 06:58:24 +00:00
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 {
2019-02-12 06:58:24 +00:00
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",
Database: "${data}/" + LevelDBDir,
LogFile: "${data}/syncthing.log", // --logfile on Windows
CsrfTokens: "${data}/csrftokens.txt",
PanicLog: "${data}/panic-%{timestamp}.log",
AuditLog: "${data}/audit-%{timestamp}.log",
2019-02-12 06:58:24 +00:00
GUIAssets: "${config}/gui",
DefFolder: "${userHome}/Sync",
2019-02-12 06:58:24 +00:00
}
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)]
})
2019-02-12 06:58:24 +00:00
var err error
dir, err = fs.ExpandTilde(dir)
if err != nil {
return err
}
newLocations[key] = filepath.Clean(dir)
}
locations = newLocations
return nil
}
gui, api: Show internal config and state paths (fixes #8323) (#8324) * lib/locations: Fix enum values camelCase. * lib/locations: Remove unused FailuresFile. * cmd/syncthing: Turn around role of locations storage. Previously the locations package was used to provide default paths, possibly with an overridden home directory. Extra paths supplied on the command line were handled and passed around in the options object. To make the changed paths available to any other interested package, override the location setting from the option if supplied, instead of vice versa when not supplied. Adapt code using this to read from the locations package instead of passing through the options object. * lib/locations: Refactor showPaths to locations package. Generate a reusable string in locations.PrettyPrintPaths(). Enumerating all possible locations in different packages is error prone, so add a new public function to generate the listing as a string in the locations package. Adapt cmd/syncthing --paths to use that instead of its own console output. * lib/locations: Include CSRF token in pretty printed paths. * lib/api: New endpoint /rest/system/paths. The paths should be available for troubleshooting from a running instance. Using the --paths CLI option is not easy in some environments, so expose the locations mapping to a JSON endpoint. Add utility function ListExpandedPaths() that also filters out any entries which still contain variable placeholders. * gui: List runtime paths in separate log viewer tab. * Wrap paths. * lib/syncthing: Utilize locations.Get() instead of passing an arg. * Include base directories, move label to table caption. * gui: Switch to hard-coded paths instead of iterating over all. * gui: Break aboutModalView into tabs. Use tabs to separate authors from included third-party software. * gui: Move paths from log viewer to about modal. * lib/locations: Adjust pretty print output order to match GUI. * gui, authors: Remove additional bot names and fix indent. The indentation changed because of the tabbed about dialog, fix the authors script to respect that. Skip Syncthing*Automation in authors list as well. * Update AUTHORS list to remove bot names. * Revert AUTHORS email order change. * Do not emphasize DB and log file locations. * Review line wrapping. * review part 1: strings.Builder, naming * Rename and extend locations.Set() with error handling. Remodel the Override() function along the existing SetBaseDir() and rename it to simply Set(). Make sure to use absolute paths when given log file or GUI assets override options. Add proper error reporting if that goes wrong. * Remove obsolete comment about empty logfile option. * Don't filter out unexpanded baseDir placeholders, only ${timestamp}. * Restore behavior regarding special "-" logfile argument. If the option is given, but with empty value, assume the no log file (same as "-"). Don't try to convert the special value to an absolute path though and document this fact in a comment for the Set() function. * Use template to check for location key validity. * Don't filter out timestamp placeholders. * lib/api: Remove paths from /rest/system/status. * lib/ur: Properly initialize map in failure data (fixes #8479) Co-authored-by: Jakob Borg <jakob@kastelo.net>
2022-08-10 06:25:13 +00:00
// 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, "CSRF tokens file:\n\t%s\n\n", Get(CsrfTokens))
fmt.Fprintf(&b, "Default sync folder directory:\n\t%s\n\n", Get(DefFolder))
return b.String()
}
2019-02-12 06:58:24 +00:00
// 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()
2019-02-12 06:58:24 +00:00
case build.IsDarwin:
return darwinConfigDataDir(userHome)
2019-02-12 06:58:24 +00:00
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 {
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")
2019-02-12 06:58:24 +00:00
}
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, 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, LevelDBDir)) {
return candidate
}
}
// Legacy: if a database exists under ~/.config/syncthing, use that
candidate := filepath.Join(userHome, oldDefaultConfigDir)
if 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)
2019-02-12 06:58:24 +00:00
}
// userHomeDir returns the user's home directory, or dies trying.
func userHomeDir() string {
userHome, err := fs.ExpandTilde("~")
2019-02-12 06:58:24 +00:00
if err != nil {
fmt.Println(err)
panic("Failed to get user home dir")
2019-02-12 06:58:24 +00:00
}
return userHome
2019-02-12 06:58:24 +00:00
}
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
2019-02-12 06:58:24 +00:00
// 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)
2019-02-12 06:58:24 +00:00
}
func fileExists(path string) bool {
_, err := os.Lstat(path)
return err == nil
}