Browse Source

cmd/syncthing, lib/locations: Separate data and config dirs (fixes #4924) (#6309)

Simon Frei 5 years ago
parent
commit
e25e71cdde
3 changed files with 110 additions and 52 deletions
  1. 54 23
      cmd/syncthing/main.go
  2. 1 1
      etc/linux-desktop/syncthing-start.desktop
  3. 55 28
      lib/locations/locations.go

+ 54 - 23
cmd/syncthing/main.go

@@ -65,6 +65,12 @@ I.e. to prefix each log line with date and time, set -logflags=3 (1 + 2 from
 above). The value 0 is used to disable all of the above. The default is to
 show time only (2).
 
+Logging always happens to the command line (stdout) and optionally to the
+file at the path specified by -logfile=path. In addition to an path, the special
+values "default" and "-" may be used. The former logs to DATADIR/syncthing.log
+(see -data-dir), which is the default on Windows, and the latter only to stdout,
+no file, which is the default anywhere else.
+
 
 Development Settings
 --------------------
@@ -149,7 +155,9 @@ var (
 
 type RuntimeOptions struct {
 	syncthing.Options
+	homeDir          string
 	confDir          string
+	dataDir          string
 	resetDatabase    bool
 	showVersion      bool
 	showPaths        bool
@@ -197,11 +205,13 @@ func defaultRuntimeOptions() RuntimeOptions {
 		options.logFlags = logger.DebugFlags
 	}
 
-	if runtime.GOOS != "windows" {
-		// On non-Windows, we explicitly default to "-" which means stdout. On
-		// Windows, the blank options.logFile will later be replaced with the
-		// default path, unless the user has manually specified "-" or
-		// something else.
+	// On non-Windows, we explicitly default to "-" which means stdout. On
+	// Windows, the "default" options.logFile will later be replaced with the
+	// default path, unless the user has manually specified "-" or
+	// something else.
+	if runtime.GOOS == "windows" {
+		options.logFile = "default"
+	} else {
 		options.logFile = "-"
 	}
 
@@ -214,7 +224,9 @@ func parseCommandLineOptions() RuntimeOptions {
 	flag.StringVar(&options.generateDir, "generate", "", "Generate key and config in specified dir, then exit")
 	flag.StringVar(&options.guiAddress, "gui-address", options.guiAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
 	flag.StringVar(&options.guiAPIKey, "gui-apikey", options.guiAPIKey, "Override GUI API key")
-	flag.StringVar(&options.confDir, "home", "", "Set configuration directory")
+	flag.StringVar(&options.homeDir, "home", "", "Set configuration and data directory")
+	flag.StringVar(&options.confDir, "config", "", "Set configuration directory (config and keys)")
+	flag.StringVar(&options.dataDir, "data", "", "Set data directory (database and logs)")
 	flag.IntVar(&options.logFlags, "logflags", options.logFlags, "Select information in log line prefix (see below)")
 	flag.BoolVar(&options.noBrowser, "no-browser", false, "Do not start browser")
 	flag.BoolVar(&options.browserOnly, "browser-only", false, "Open GUI in browser")
@@ -232,7 +244,7 @@ func parseCommandLineOptions() RuntimeOptions {
 	flag.BoolVar(&options.Verbose, "verbose", false, "Print verbose log output")
 	flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
 	flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
-	flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (still always logs to stdout).")
+	flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (see below).")
 	flag.IntVar(&options.logMaxSize, "log-max-size", options.logMaxSize, "Maximum size of any file (zero to disable log rotation).")
 	flag.IntVar(&options.logMaxFiles, "log-max-old-files", options.logMaxFiles, "Number of old files to keep (zero to keep only current).")
 	flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
@@ -254,6 +266,17 @@ func parseCommandLineOptions() RuntimeOptions {
 	return options
 }
 
+func setLocation(enum locations.BaseDirEnum, loc string) error {
+	if !filepath.IsAbs(loc) {
+		var err error
+		loc, err = filepath.Abs(loc)
+		if err != nil {
+			return err
+		}
+	}
+	return locations.SetBaseDir(enum, loc)
+}
+
 func main() {
 	options := parseCommandLineOptions()
 	l.SetFlags(options.logFlags)
@@ -271,25 +294,33 @@ func main() {
 		osutil.HideConsole()
 	}
 
-	if options.confDir != "" {
-		// Not set as default above because the string can be really long.
-		if !filepath.IsAbs(options.confDir) {
-			var err error
-			options.confDir, err = filepath.Abs(options.confDir)
-			if err != nil {
-				l.Warnln("Failed to make options path absolute:", err)
-				os.Exit(syncthing.ExitError.AsInt())
-			}
-		}
-		if err := locations.SetBaseDir(locations.ConfigBaseDir, options.confDir); err != nil {
-			l.Warnln(err)
-			os.Exit(syncthing.ExitError.AsInt())
+	// Not set as default above because the strings can be really long.
+	var err error
+	homeSet := options.homeDir != ""
+	confSet := options.confDir != ""
+	dataSet := options.dataDir != ""
+	switch {
+	case dataSet != confSet:
+		err = errors.New("either both or none of -conf and -data must be given, use -home to set both at once")
+	case homeSet && dataSet:
+		err = errors.New("-home must not be used together with -conf and -data")
+	case homeSet:
+		if err = setLocation(locations.ConfigBaseDir, options.homeDir); err == nil {
+			err = setLocation(locations.DataBaseDir, options.homeDir)
+		}
+	case dataSet:
+		if err = setLocation(locations.ConfigBaseDir, options.confDir); err == nil {
+			err = setLocation(locations.DataBaseDir, options.dataDir)
 		}
 	}
+	if err != nil {
+		l.Warnln("Command line options:", err)
+		os.Exit(syncthing.ExitError.AsInt())
+	}
 
-	if options.logFile == "" {
-		// Blank means use the default logfile location. We must set this
-		// *after* expandLocations above.
+	if options.logFile == "default" || options.logFile == "" {
+		// We must set this *after* expandLocations above.
+		// Handling an empty value is for backwards compatibility (<1.4.1).
 		options.logFile = locations.Get(locations.LogFile)
 	}
 

+ 1 - 1
etc/linux-desktop/syncthing-start.desktop

@@ -2,7 +2,7 @@
 Name=Start Syncthing
 GenericName=File synchronization
 Comment=Starts the main syncthing process in the background.
-Exec=/usr/bin/syncthing -no-browser -logfile=~/.config/syncthing/syncthing.log
+Exec=/usr/bin/syncthing -no-browser -logfile=default
 Icon=syncthing
 Terminal=false
 Type=Application

+ 55 - 28
lib/locations/locations.go

@@ -39,11 +39,23 @@ const (
 type BaseDirEnum string
 
 const (
+	// Overridden by -home flag
 	ConfigBaseDir BaseDirEnum = "config"
-	HomeBaseDir   BaseDirEnum = "home"
+	DataBaseDir   BaseDirEnum = "data"
+	// User's home directory, *not* -home flag
+	UserHomeBaseDir BaseDirEnum = "userHome"
 )
 
+// Platform dependent directories
+var baseDirs = make(map[BaseDirEnum]string, 3)
+
 func init() {
+	userHome := userHomeDir()
+	config := defaultConfigDir(userHome)
+	baseDirs[UserHomeBaseDir] = userHome
+	baseDirs[ConfigBaseDir] = config
+	baseDirs[DataBaseDir] = defaultDataDir(userHome, config)
+
 	err := expandLocations()
 	if err != nil {
 		fmt.Println(err)
@@ -68,11 +80,7 @@ func GetBaseDir(baseDir BaseDirEnum) string {
 	return baseDirs[baseDir]
 }
 
-// Platform dependent directories
-var baseDirs = map[BaseDirEnum]string{
-	ConfigBaseDir: defaultConfigDir(), // Overridden by -home flag
-	HomeBaseDir:   homeDir(),          // User's home directory, *not* -home flag
-}
+var databaseDirname = "index-v0.14.0.db"
 
 // Use the variables from baseDirs here
 var locationTemplates = map[LocationEnum]string{
@@ -81,13 +89,13 @@ var locationTemplates = map[LocationEnum]string{
 	KeyFile:       "${config}/key.pem",
 	HTTPSCertFile: "${config}/https-cert.pem",
 	HTTPSKeyFile:  "${config}/https-key.pem",
-	Database:      "${config}/index-v0.14.0.db",
-	LogFile:       "${config}/syncthing.log", // -logfile on Windows
-	CsrfTokens:    "${config}/csrftokens.txt",
-	PanicLog:      "${config}/panic-${timestamp}.log",
-	AuditLog:      "${config}/audit-${timestamp}.log",
+	Database:      "${data}/" + databaseDirname,
+	LogFile:       "${data}/syncthing.log", // -logfile on Windows
+	CsrfTokens:    "${data}/csrftokens.txt",
+	PanicLog:      "${data}/panic-${timestamp}.log",
+	AuditLog:      "${data}/audit-${timestamp}.log",
 	GUIAssets:     "${config}/gui",
-	DefFolder:     "${home}/Sync",
+	DefFolder:     "${userHome}/Sync",
 }
 
 var locations = make(map[LocationEnum]string)
@@ -114,7 +122,7 @@ func expandLocations() error {
 // defaultConfigDir returns the default configuration directory, as figured
 // out by various the environment variables present on each platform, or dies
 // trying.
-func defaultConfigDir() string {
+func defaultConfigDir(userHome string) string {
 	switch runtime.GOOS {
 	case "windows":
 		if p := os.Getenv("LocalAppData"); p != "" {
@@ -123,34 +131,53 @@ func defaultConfigDir() string {
 		return filepath.Join(os.Getenv("AppData"), "Syncthing")
 
 	case "darwin":
-		dir, err := fs.ExpandTilde("~/Library/Application Support/Syncthing")
-		if err != nil {
-			fmt.Println(err)
-			panic("Failed to get default config dir")
-		}
-		return dir
+		return filepath.Join(userHome, "Library/Application Support/Syncthing")
 
 	default:
 		if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" {
 			return filepath.Join(xdgCfg, "syncthing")
 		}
-		dir, err := fs.ExpandTilde("~/.config/syncthing")
-		if err != nil {
-			fmt.Println(err)
-			panic("Failed to get default config dir")
+		return filepath.Join(userHome, ".config/syncthing")
+	}
+}
+
+// defaultDataDir returns the default data directory, which usually is the
+// config directory but might be something else.
+func defaultDataDir(userHome, config string) string {
+	switch runtime.GOOS {
+	case "windows", "darwin":
+		return config
+
+	default:
+		// If a database exists at the "normal" location, use that anyway.
+		if _, err := os.Lstat(filepath.Join(config, databaseDirname)); err == nil {
+			return config
+		}
+		// Always use this env var, as it's explicitly set by the user
+		if xdgHome := os.Getenv("XDG_DATA_HOME"); xdgHome != "" {
+			return filepath.Join(xdgHome, "syncthing")
+		}
+		// Only use the XDG default, if a syncthing specific dir already
+		// exists. Existence of ~/.local/share is not deemed enough, as
+		// it may also exist erroneously on non-XDG systems.
+		xdgDefault := filepath.Join(userHome, ".local/share/syncthing")
+		if _, err := os.Lstat(xdgDefault); err == nil {
+			return xdgDefault
 		}
-		return dir
+		// FYI: XDG_DATA_DIRS is not relevant, as it is for system-wide
+		// data dirs, not user specific ones.
+		return config
 	}
 }
 
-// homeDir returns the user's home directory, or dies trying.
-func homeDir() string {
-	home, err := fs.ExpandTilde("~")
+// 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 home
+	return userHome
 }
 
 func GetTimestamped(key LocationEnum) string {