diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 7966668e..8160266a 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -63,7 +63,7 @@ func init() { func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) error { var err error - cert, err := loadCert(confDir, "https-") + cert, err := tls.LoadX509KeyPair(locations[locHttpsCertFile], locations[locHttpsKeyFile]) if err != nil { l.Infoln("Loading HTTPS certificate:", err) l.Infoln("Creating new HTTPS certificate") @@ -76,8 +76,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro name = tlsDefaultCommonName } - newCertificate(confDir, "https-", name) - cert, err = loadCert(confDir, "https-") + cert, err = newCertificate(locations[locHttpsCertFile], locations[locHttpsKeyFile], name) } if err != nil { return err diff --git a/cmd/syncthing/gui_csrf.go b/cmd/syncthing/gui_csrf.go index 988200ea..f9116ec3 100644 --- a/cmd/syncthing/gui_csrf.go +++ b/cmd/syncthing/gui_csrf.go @@ -11,7 +11,6 @@ import ( "fmt" "net/http" "os" - "path/filepath" "strings" "sync" "time" @@ -92,7 +91,7 @@ func newCsrfToken() string { } func saveCsrfTokens() { - name := filepath.Join(confDir, "csrftokens.txt") + name := locations[locCsrfTokens] tmp := fmt.Sprintf("%s.tmp.%d", name, time.Now().UnixNano()) f, err := os.OpenFile(tmp, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) @@ -117,8 +116,7 @@ func saveCsrfTokens() { } func loadCsrfTokens() { - name := filepath.Join(confDir, "csrftokens.txt") - f, err := os.Open(name) + f, err := os.Open(locations[locCsrfTokens]) if err != nil { return } diff --git a/cmd/syncthing/locations.go b/cmd/syncthing/locations.go new file mode 100644 index 00000000..8f57ce44 --- /dev/null +++ b/cmd/syncthing/locations.go @@ -0,0 +1,109 @@ +// Copyright (C) 2015 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 http://mozilla.org/MPL/2.0/. + +package main + +import ( + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/syncthing/syncthing/internal/osutil" +) + +type locationEnum string + +// Use strings as keys to make printout and serialization of the locations map +// more meaningful. +const ( + locConfigFile locationEnum = "config" + locCertFile = "certFile" + locKeyFile = "keyFile" + locHttpsCertFile = "httpsCertFile" + locHttpsKeyFile = "httpsKeyFile" + locDatabase = "database" + locLogFile = "logFile" + locCsrfTokens = "csrfTokens" + locPanicLog = "panicLog" + locDefFolder = "defFolder" +) + +// Platform dependent directories +var baseDirs = map[string]string{ + "config": defaultConfigDir(), // Overridden by -home flag + "home": homeDir(), // User's home directory, *not* -home flag +} + +// Use the variables from baseDirs here +var locations = map[locationEnum]string{ + locConfigFile: "${config}/config.xml", + locCertFile: "${config}/cert.pem", + locKeyFile: "${config}/key.pem", + locHttpsCertFile: "${config}/https-cert.pem", + locHttpsKeyFile: "${config}/https-key.pem", + locDatabase: "${config}/index-v0.11.0.db", + locLogFile: "${config}/syncthing.log", // -logfile on Windows + locCsrfTokens: "${config}/csrftokens.txt", + locPanicLog: "${config}/panic-20060102-150405.log", // passed through time.Format() + locDefFolder: "${home}/Sync", +} + +// expandLocations replaces the variables in the location map with actual +// directory locations. +func expandLocations() error { + for key, dir := range locations { + for varName, value := range baseDirs { + dir = strings.Replace(dir, "${"+varName+"}", value, -1) + } + var err error + dir, err = osutil.ExpandTilde(dir) + if err != nil { + return err + } + locations[key] = dir + } + return nil +} + +// defaultConfigDir returns the default configuration directory, as figured +// out by various the environment variables present on each platform, or dies +// trying. +func defaultConfigDir() string { + switch runtime.GOOS { + case "windows": + if p := os.Getenv("LocalAppData"); p != "" { + return filepath.Join(p, "Syncthing") + } + return filepath.Join(os.Getenv("AppData"), "Syncthing") + + case "darwin": + dir, err := osutil.ExpandTilde("~/Library/Application Support/Syncthing") + if err != nil { + l.Fatalln(err) + } + return dir + + default: + if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" { + return filepath.Join(xdgCfg, "syncthing") + } + dir, err := osutil.ExpandTilde("~/.config/syncthing") + if err != nil { + l.Fatalln(err) + } + return dir + } +} + +// homeDir returns the user's home directory, or dies trying. +func homeDir() string { + home, err := osutil.ExpandTilde("~") + if err != nil { + l.Fatalln(err) + } + return home +} diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 94b65336..eea9f9d8 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -199,17 +199,11 @@ var ( ) func main() { - defConfDir, err := getDefaultConfDir() - if err != nil { - l.Fatalln("home:", err) - } - if runtime.GOOS == "windows" { // On Windows, we use a log file by default. Setting the -logfile flag - // to the empty string disables this behavior. + // to "-" disables this behavior. - logFile = filepath.Join(defConfDir, "syncthing.log") - flag.StringVar(&logFile, "logfile", logFile, "Log file name (blank for stdout)") + flag.StringVar(&logFile, "logfile", "", "Log file name (use \"-\" for stdout)") // We also add an option to hide the console window flag.BoolVar(&noConsole, "no-console", false, "Hide console window") @@ -229,23 +223,30 @@ func main() { flag.BoolVar(&showVersion, "version", false, "Show version") flag.StringVar(&upgradeTo, "upgrade-to", upgradeTo, "Force upgrade directly from specified URL") - flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, defConfDir)) + flag.Usage = usageFor(flag.CommandLine, usage, fmt.Sprintf(extraUsage, baseDirs["config"])) flag.Parse() if noConsole { osutil.HideConsole() } - if confDir == "" { + if confDir != "" { // Not set as default above because the string can be really long. - confDir = defConfDir + baseDirs["config"] = confDir } - if confDir != defConfDir && filepath.Dir(logFile) == defConfDir { - // The user changed the config dir with -home, but not the log file - // location. In this case we assume they meant for the logfile to - // still live in it's default location *relative to the config dir*. - logFile = filepath.Join(confDir, "syncthing.log") + if runtime.GOOS == "windows" { + if logFile == "" { + // Use the default log file location + logFile = locations[locLogFile] + } else if logFile == "-" { + // Don't use a logFile + logFile = "" + } + } + + if err := expandLocations(); err != nil { + l.Fatalln(err) } if showVersion { @@ -272,13 +273,13 @@ func main() { } } - cert, err := loadCert(dir, "") + certFile, keyFile := filepath.Join(dir, "cert.pem"), filepath.Join(dir, "key.pem") + cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err == nil { l.Warnln("Key exists; will not overwrite.") l.Infoln("Device ID:", protocol.NewDeviceID(cert.Certificate[0])) } else { - newCertificate(dir, "", tlsDefaultCommonName) - cert, err = loadCert(dir, "") + cert, err = newCertificate(certFile, keyFile, tlsDefaultCommonName) myID = protocol.NewDeviceID(cert.Certificate[0]) if err != nil { l.Fatalln("load cert:", err) @@ -304,17 +305,12 @@ func main() { return } - confDir, err := osutil.ExpandTilde(confDir) - if err != nil { - l.Fatalln("home:", err) - } - - if info, err := os.Stat(confDir); err == nil && !info.IsDir() { - l.Fatalln("Config directory", confDir, "is not a directory") + if info, err := os.Stat(baseDirs["config"]); err == nil && !info.IsDir() { + l.Fatalln("Config directory", baseDirs["config"], "is not a directory") } // Ensure that our home directory exists. - ensureDir(confDir, 0700) + ensureDir(baseDirs["config"], 0700) if upgradeTo != "" { err := upgrade.ToURL(upgradeTo) @@ -340,7 +336,7 @@ func main() { if doUpgrade { // Use leveldb database locks to protect against concurrent upgrades - _, err = leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{OpenFilesCacheCapacity: 100}) + _, err = leveldb.OpenFile(locations[locDatabase], &opt.Options{OpenFilesCacheCapacity: 100}) if err != nil { l.Fatalln("Cannot upgrade, database seems to be locked. Is another copy of Syncthing already running?") } @@ -374,13 +370,12 @@ func syncthingMain() { runtime.GOMAXPROCS(runtime.NumCPU()) } - events.Default.Log(events.Starting, map[string]string{"home": confDir}) + events.Default.Log(events.Starting, map[string]string{"home": baseDirs["config"]}) // Ensure that that we have a certificate and key. - cert, err = loadCert(confDir, "") + cert, err = tls.LoadX509KeyPair(locations[locCertFile], locations[locKeyFile]) if err != nil { - newCertificate(confDir, "", tlsDefaultCommonName) - cert, err = loadCert(confDir, "") + cert, err = newCertificate(locations[locCertFile], locations[locKeyFile], tlsDefaultCommonName) if err != nil { l.Fatalln("load cert:", err) } @@ -398,7 +393,7 @@ func syncthingMain() { // Prepare to be able to save configuration - cfgFile := filepath.Join(confDir, "config.xml") + cfgFile := locations[locConfigFile] var myName string @@ -493,7 +488,7 @@ func syncthingMain() { l.Infoln("Local networks:", strings.Join(networks, ", ")) } - dbFile := filepath.Join(confDir, "index") + dbFile := locations[locDatabase] dbOpts := &opt.Options{OpenFilesCacheCapacity: 100} ldb, err := leveldb.OpenFile(dbFile, dbOpts) if err != nil && errors.IsCorrupted(err) { @@ -668,16 +663,11 @@ func setupGUI(cfg *config.Wrapper, m *model.Model) { } func defaultConfig(myName string) config.Configuration { - defaultFolder, err := osutil.ExpandTilde("~/Sync") - if err != nil { - l.Fatalln("home:", err) - } - newCfg := config.New(myID) newCfg.Folders = []config.FolderConfiguration{ { ID: "default", - Path: defaultFolder, + Path: locations[locDefFolder], RescanIntervalS: 60, Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}}, }, @@ -814,12 +804,7 @@ func renewUPnP(port int) { } func resetFolders() { - confDir, err := osutil.ExpandTilde(confDir) - if err != nil { - log.Fatal(err) - } - - cfgFile := filepath.Join(confDir, "config.xml") + cfgFile := locations[locConfigFile] cfg, err := config.Load(cfgFile, myID) if err != nil { log.Fatal(err) @@ -835,8 +820,7 @@ func resetFolders() { } } - idx := filepath.Join(confDir, "index") - os.RemoveAll(idx) + os.RemoveAll(locations[locDatabase]) } func restart() { @@ -882,25 +866,6 @@ func ensureDir(dir string, mode int) { } } -func getDefaultConfDir() (string, error) { - switch runtime.GOOS { - case "windows": - if p := os.Getenv("LocalAppData"); p != "" { - return filepath.Join(p, "Syncthing"), nil - } - return filepath.Join(os.Getenv("AppData"), "Syncthing"), nil - - case "darwin": - return osutil.ExpandTilde("~/Library/Application Support/Syncthing") - - default: - if xdgCfg := os.Getenv("XDG_CONFIG_HOME"); xdgCfg != "" { - return filepath.Join(xdgCfg, "syncthing"), nil - } - return osutil.ExpandTilde("~/.config/syncthing") - } -} - // getFreePort returns a free TCP port fort listening on. The ports given are // tried in succession and the first to succeed is returned. If none succeed, // a random high port is returned. diff --git a/cmd/syncthing/monitor.go b/cmd/syncthing/monitor.go index 2826d738..ced8b0ad 100644 --- a/cmd/syncthing/monitor.go +++ b/cmd/syncthing/monitor.go @@ -12,7 +12,6 @@ import ( "os" "os/exec" "os/signal" - "path/filepath" "runtime" "strings" "sync" @@ -164,7 +163,7 @@ func copyStderr(stderr io.ReadCloser, dst io.Writer) { dst.Write([]byte(line)) if strings.HasPrefix(line, "panic:") || strings.HasPrefix(line, "fatal error:") { - panicFd, err = os.Create(filepath.Join(confDir, time.Now().Format("panic-20060102-150405.log"))) + panicFd, err = os.Create(time.Now().Format(locations[locPanicLog])) if err != nil { l.Warnln("Create panic log:", err) continue diff --git a/cmd/syncthing/tls.go b/cmd/syncthing/tls.go index fcae10f2..6faafbe5 100644 --- a/cmd/syncthing/tls.go +++ b/cmd/syncthing/tls.go @@ -19,7 +19,6 @@ import ( mr "math/rand" "net" "os" - "path/filepath" "time" ) @@ -28,13 +27,7 @@ const ( tlsDefaultCommonName = "syncthing" ) -func loadCert(dir string, prefix string) (tls.Certificate, error) { - cf := filepath.Join(dir, prefix+"cert.pem") - kf := filepath.Join(dir, prefix+"key.pem") - return tls.LoadX509KeyPair(cf, kf) -} - -func newCertificate(dir, prefix, name string) { +func newCertificate(certFile, keyFile, name string) (tls.Certificate, error) { l.Infof("Generating RSA key and certificate for %s...", name) priv, err := rsa.GenerateKey(rand.Reader, tlsRSABits) @@ -63,7 +56,7 @@ func newCertificate(dir, prefix, name string) { l.Fatalln("create cert:", err) } - certOut, err := os.Create(filepath.Join(dir, prefix+"cert.pem")) + certOut, err := os.Create(certFile) if err != nil { l.Fatalln("save cert:", err) } @@ -76,7 +69,7 @@ func newCertificate(dir, prefix, name string) { l.Fatalln("save cert:", err) } - keyOut, err := os.OpenFile(filepath.Join(dir, prefix+"key.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + keyOut, err := os.OpenFile(keyFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) if err != nil { l.Fatalln("save key:", err) } @@ -88,6 +81,8 @@ func newCertificate(dir, prefix, name string) { if err != nil { l.Fatalln("save key:", err) } + + return tls.LoadX509KeyPair(certFile, keyFile) } type DowngradingListener struct { diff --git a/test/.gitignore b/test/.gitignore index 51d37e13..9dc35dcb 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -15,5 +15,5 @@ dirs-* csrftokens.txt s4d http -index +h*/index* *.syncthing-reset* diff --git a/test/cli_test.go b/test/cli_test.go index b377a690..c7e5004c 100644 --- a/test/cli_test.go +++ b/test/cli_test.go @@ -17,7 +17,7 @@ import ( ) func TestCLIReset(t *testing.T) { - dirs := []string{"s1", "s12-1", "h1/index"} + dirs := []string{"s1", "s12-1", "h1/index-v0.11.0.db"} // Create directories that reset will remove diff --git a/test/conflict_test.go b/test/conflict_test.go index 2f9c6ed2..608e7975 100644 --- a/test/conflict_test.go +++ b/test/conflict_test.go @@ -18,7 +18,7 @@ import ( func TestConflict(t *testing.T) { log.Println("Cleaning...") - err := removeAll("s1", "s2", "h1/index", "h2/index") + err := removeAll("s1", "s2", "h1/index*", "h2/index*") if err != nil { t.Fatal(err) } @@ -64,16 +64,11 @@ func TestConflict(t *testing.T) { // startup, UPnP etc complete and make sure the sender has the full index // before they connect. for i := 0; i < 20; i++ { - resp, err := sender.post("/rest/scan?folder=default", nil) + err := sender.rescan("default") if err != nil { time.Sleep(time.Second) continue } - if resp.StatusCode != 200 { - resp.Body.Close() - time.Sleep(time.Second) - continue - } break } diff --git a/test/filetype_test.go b/test/filetype_test.go index 4d68f1f8..98ef8ac1 100644 --- a/test/filetype_test.go +++ b/test/filetype_test.go @@ -61,7 +61,7 @@ func TestFileTypeChangeStaggeredVersioning(t *testing.T) { func testFileTypeChange(t *testing.T) { log.Println("Cleaning...") - err := removeAll("s1", "s2", "h1/index", "h2/index") + err := removeAll("s1", "s2", "h1/index*", "h2/index*") if err != nil { t.Fatal(err) } diff --git a/test/h2/config.xml b/test/h2/config.xml index 3563865b..d7740200 100644 --- a/test/h2/config.xml +++ b/test/h2/config.xml @@ -3,7 +3,9 @@ - + + + false 1 16 diff --git a/test/httpstress_test.go b/test/httpstress_test.go index 257624bb..a6e77ed3 100644 --- a/test/httpstress_test.go +++ b/test/httpstress_test.go @@ -23,7 +23,7 @@ import ( func TestStressHTTP(t *testing.T) { log.Println("Cleaning...") - err := removeAll("s2", "h2/index") + err := removeAll("s2", "h2/index*") if err != nil { t.Fatal(err) } diff --git a/test/ignore_test.go b/test/ignore_test.go index 37c96a3f..a62c091a 100644 --- a/test/ignore_test.go +++ b/test/ignore_test.go @@ -22,7 +22,7 @@ func TestIgnores(t *testing.T) { // Clean and start a syncthing instance log.Println("Cleaning...") - err := removeAll("s1", "h1/index") + err := removeAll("s1", "h1/index*") if err != nil { t.Fatal(err) } diff --git a/test/manypeers_test.go b/test/manypeers_test.go index a794b9f0..690a2d66 100644 --- a/test/manypeers_test.go +++ b/test/manypeers_test.go @@ -22,7 +22,7 @@ import ( func TestManyPeers(t *testing.T) { log.Println("Cleaning...") - err := removeAll("s1", "s2", "h1/index", "h2/index") + err := removeAll("s1", "s2", "h1/index*", "h2/index*") if err != nil { t.Fatal(err) } diff --git a/test/parallell_scan_test.go b/test/parallell_scan_test.go index 5c4d1fae..4d0c1ed9 100644 --- a/test/parallell_scan_test.go +++ b/test/parallell_scan_test.go @@ -18,7 +18,7 @@ import ( func TestParallellScan(t *testing.T) { log.Println("Cleaning...") - err := removeAll("s1", "h1/index") + err := removeAll("s1", "h1/index*") if err != nil { t.Fatal(err) } diff --git a/test/reconnect_test.go b/test/reconnect_test.go index f4c834c5..3447fc90 100644 --- a/test/reconnect_test.go +++ b/test/reconnect_test.go @@ -32,7 +32,7 @@ func TestRestartSenderAndReceiverDuringTransfer(t *testing.T) { func testRestartDuringTransfer(t *testing.T, restartSender, restartReceiver bool, senderDelay, receiverDelay time.Duration) { log.Println("Cleaning...") - err := removeAll("s1", "s2", "h1/index", "h2/index") + err := removeAll("s1", "s2", "h1/index*", "h2/index*") if err != nil { t.Fatal(err) } diff --git a/test/symlink_test.go b/test/symlink_test.go index 2b180eb4..e5d85c66 100644 --- a/test/symlink_test.go +++ b/test/symlink_test.go @@ -86,7 +86,7 @@ func TestSymlinksStaggeredVersioning(t *testing.T) { func testSymlinks(t *testing.T) { log.Println("Cleaning...") - err := removeAll("s1", "s2", "h1/index", "h2/index") + err := removeAll("s1", "s2", "h1/index*", "h2/index*") if err != nil { t.Fatal(err) } diff --git a/test/sync_test.go b/test/sync_test.go index a37ee856..e8b824bc 100644 --- a/test/sync_test.go +++ b/test/sync_test.go @@ -79,7 +79,7 @@ func testSyncCluster(t *testing.T) { err := removeAll("s1", "s12-1", "s2", "s12-2", "s23-2", "s3", "s23-3", - "h1/index", "h2/index", "h3/index") + "h1/index*", "h2/index*", "h3/index*") if err != nil { t.Fatal(err) } diff --git a/test/transfer-bench_test.go b/test/transfer-bench_test.go index 5170457a..de850266 100644 --- a/test/transfer-bench_test.go +++ b/test/transfer-bench_test.go @@ -16,7 +16,7 @@ import ( func TestBenchmarkTransfer(t *testing.T) { log.Println("Cleaning...") - err := removeAll("s1", "s2", "h1/index", "h2/index") + err := removeAll("s1", "s2", "h1/index*", "h2/index*") if err != nil { t.Fatal(err) } diff --git a/test/util.go b/test/util.go index 7bdd9e2f..47194456 100644 --- a/test/util.go +++ b/test/util.go @@ -241,17 +241,23 @@ func (i *inifiteReader) Read(bs []byte) (int, error) { // rm -rf func removeAll(dirs ...string) error { for _, dir := range dirs { - // Set any non-writeable files and dirs to writeable. This is necessary for os.RemoveAll to work on Windows. - filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.Mode()&0700 != 0700 { - os.Chmod(path, 0777) - } - return nil - }) - os.RemoveAll(dir) + files, err := filepath.Glob(dir) + if err != nil { + return err + } + for _, file := range files { + // Set any non-writeable files and dirs to writeable. This is necessary for os.RemoveAll to work on Windows. + filepath.Walk(file, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.Mode()&0700 != 0700 { + os.Chmod(path, 0777) + } + return nil + }) + os.RemoveAll(file) + } } return nil }