2014-11-16 20:13:20 +00:00
// Copyright (C) 2014 The Syncthing Authors.
2014-09-29 19:43:32 +00:00
//
2015-03-07 20:36:35 +00:00
// 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/.
2014-06-01 20:50:14 +00:00
2014-05-15 03:40:17 +00:00
// Package config implements reading and writing of the syncthing configuration file.
2014-05-15 00:18:09 +00:00
package config
2014-03-02 22:58:14 +00:00
import (
"encoding/xml"
2015-06-23 12:55:30 +00:00
"fmt"
2014-10-06 07:25:45 +00:00
"io"
2014-12-29 12:48:26 +00:00
"math/rand"
2015-10-12 13:27:57 +00:00
"net/url"
2014-04-22 09:46:08 +00:00
"os"
2014-10-11 15:55:11 +00:00
"path/filepath"
2014-03-02 22:58:14 +00:00
"reflect"
2015-04-05 20:52:22 +00:00
"runtime"
2014-03-02 22:58:14 +00:00
"sort"
"strconv"
2015-03-07 19:48:53 +00:00
"strings"
2014-04-19 11:33:51 +00:00
2015-08-06 09:29:25 +00:00
"github.com/syncthing/syncthing/lib/osutil"
2015-09-22 17:38:46 +00:00
"github.com/syncthing/syncthing/lib/protocol"
2014-11-29 23:17:00 +00:00
"golang.org/x/crypto/bcrypt"
2014-03-02 22:58:14 +00:00
)
2015-04-04 19:49:15 +00:00
const (
OldestHandledVersion = 5
2015-06-23 12:55:30 +00:00
CurrentVersion = 12
2015-06-20 18:21:05 +00:00
MaxRescanIntervalS = 365 * 24 * 60 * 60
2015-04-04 19:49:15 +00:00
)
2014-10-08 11:52:05 +00:00
2015-09-20 13:30:25 +00:00
var (
// DefaultDiscoveryServers should be substituted when the configuration
// contains <globalAnnounceServer>default</globalAnnounceServer>. This is
// done by the "consumer" of the configuration, as we don't want these
// saved to the config.
DefaultDiscoveryServers = [ ] string {
2015-09-20 20:30:31 +00:00
"https://discovery-v4-1.syncthing.net/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA" , // 194.126.249.5, Sweden
"https://discovery-v4-2.syncthing.net/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS" , // 45.55.230.38, USA
"https://discovery-v4-3.syncthing.net/?id=7WT2BVR-FX62ZOW-TNVVW25-6AHFJGD-XEXQSBW-VO3MPL2-JBTLL4T-P4572Q4" , // 128.199.95.124, Singapore
"https://discovery-v6-1.syncthing.net/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA" , // 2001:470:28:4d6::5, Sweden
"https://discovery-v6-2.syncthing.net/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS" , // 2604:a880:800:10::182:a001, USA
"https://discovery-v6-3.syncthing.net/?id=7WT2BVR-FX62ZOW-TNVVW25-6AHFJGD-XEXQSBW-VO3MPL2-JBTLL4T-P4572Q4" , // 2400:6180:0:d0::d9:d001, Singapore
2015-09-20 13:30:25 +00:00
}
2015-09-21 08:54:21 +00:00
2015-09-21 12:20:33 +00:00
// DefaultDiscoveryServersIP is used by the usage reporting.
2015-09-21 08:54:21 +00:00
// XXX: Detect Android, and use this is we still don't have working DNS?
DefaultDiscoveryServersIP = [ ] string {
"https://194.126.249.5/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA" ,
"https://45.55.230.38/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS" ,
"https://128.199.95.124/?id=7WT2BVR-FX62ZOW-TNVVW25-6AHFJGD-XEXQSBW-VO3MPL2-JBTLL4T-P4572Q4" ,
"https://[2001:470:28:4d6::5]/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA" ,
"https://[2604:a880:800:10::182:a001]/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS" ,
"https://[2400:6180:0:d0::d9:d001]/?id=7WT2BVR-FX62ZOW-TNVVW25-6AHFJGD-XEXQSBW-VO3MPL2-JBTLL4T-P4572Q4" ,
}
2015-09-20 13:30:25 +00:00
)
2014-03-02 22:58:14 +00:00
type Configuration struct {
2015-03-10 22:45:43 +00:00
Version int ` xml:"version,attr" json:"version" `
Folders [ ] FolderConfiguration ` xml:"folder" json:"folders" `
Devices [ ] DeviceConfiguration ` xml:"device" json:"devices" `
GUI GUIConfiguration ` xml:"gui" json:"gui" `
Options OptionsConfiguration ` xml:"options" json:"options" `
IgnoredDevices [ ] protocol . DeviceID ` xml:"ignoredDevice" json:"ignoredDevices" `
2014-12-27 23:12:12 +00:00
XMLName xml . Name ` xml:"configuration" json:"-" `
2014-09-28 12:22:39 +00:00
2015-04-04 19:49:15 +00:00
OriginalVersion int ` xml:"-" json:"-" ` // The version we read from disk, before any conversion
2014-03-02 22:58:14 +00:00
}
2015-04-28 20:32:10 +00:00
func ( cfg Configuration ) Copy ( ) Configuration {
newCfg := cfg
2015-04-05 15:36:52 +00:00
// Deep copy FolderConfigurations
2015-04-28 20:32:10 +00:00
newCfg . Folders = make ( [ ] FolderConfiguration , len ( cfg . Folders ) )
for i := range newCfg . Folders {
newCfg . Folders [ i ] = cfg . Folders [ i ] . Copy ( )
2015-04-05 15:36:52 +00:00
}
// Deep copy DeviceConfigurations
2015-04-28 20:32:10 +00:00
newCfg . Devices = make ( [ ] DeviceConfiguration , len ( cfg . Devices ) )
for i := range newCfg . Devices {
newCfg . Devices [ i ] = cfg . Devices [ i ] . Copy ( )
2015-04-05 15:36:52 +00:00
}
2015-04-28 20:32:10 +00:00
newCfg . Options = cfg . Options . Copy ( )
2015-04-05 15:36:52 +00:00
// DeviceIDs are values
2015-04-28 20:32:10 +00:00
newCfg . IgnoredDevices = make ( [ ] protocol . DeviceID , len ( cfg . IgnoredDevices ) )
copy ( newCfg . IgnoredDevices , cfg . IgnoredDevices )
2015-04-05 15:36:52 +00:00
2015-04-28 20:32:10 +00:00
return newCfg
2015-04-05 15:36:52 +00:00
}
2014-09-28 11:00:38 +00:00
type FolderConfiguration struct {
2015-08-26 22:49:06 +00:00
ID string ` xml:"id,attr" json:"id" `
RawPath string ` xml:"path,attr" json:"path" `
Devices [ ] FolderDeviceConfiguration ` xml:"device" json:"devices" `
ReadOnly bool ` xml:"ro,attr" json:"readOnly" `
RescanIntervalS int ` xml:"rescanIntervalS,attr" json:"rescanIntervalS" `
IgnorePerms bool ` xml:"ignorePerms,attr" json:"ignorePerms" `
AutoNormalize bool ` xml:"autoNormalize,attr" json:"autoNormalize" `
2015-09-05 07:43:07 +00:00
MinDiskFreePct float64 ` xml:"minDiskFreePct" json:"minDiskFreePct" `
2015-08-26 22:49:06 +00:00
Versioning VersioningConfiguration ` xml:"versioning" json:"versioning" `
Copiers int ` xml:"copiers" json:"copiers" ` // This defines how many files are handled concurrently.
Pullers int ` xml:"pullers" json:"pullers" ` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int ` xml:"hashers" json:"hashers" ` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Order PullOrder ` xml:"order" json:"order" `
IgnoreDelete bool ` xml:"ignoreDelete" json:"ignoreDelete" `
2015-10-07 23:25:32 +00:00
ScanProgressIntervalS int ` xml:"scanProgressIntervalS" json:"scanProgressIntervalS" ` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
PullerSleepS int ` xml:"pullerSleepS" json:"pullerSleepS" `
PullerPauseS int ` xml:"pullerPauseS" json:"pullerPauseS" `
2015-10-13 19:50:58 +00:00
MaxConflicts int ` xml:"maxConflicts" json:"maxConflicts" `
2015-03-10 22:45:43 +00:00
Invalid string ` xml:"-" json:"invalid" ` // Set at runtime when there is an error, not saved
2014-05-25 18:49:08 +00:00
}
2015-04-28 20:32:10 +00:00
func ( f FolderConfiguration ) Copy ( ) FolderConfiguration {
c := f
c . Devices = make ( [ ] FolderDeviceConfiguration , len ( f . Devices ) )
copy ( c . Devices , f . Devices )
2015-04-05 15:36:52 +00:00
return c
}
2015-04-05 20:52:22 +00:00
func ( f FolderConfiguration ) Path ( ) string {
// This is intentionally not a pointer method, because things like
// cfg.Folders["default"].Path() should be valid.
// Attempt tilde expansion; leave unchanged in case of error
if path , err := osutil . ExpandTilde ( f . RawPath ) ; err == nil {
f . RawPath = path
}
// Attempt absolutification; leave unchanged in case of error
if ! filepath . IsAbs ( f . RawPath ) {
// Abs() looks like a fairly expensive syscall on Windows, while
// IsAbs() is a whole bunch of string mangling. I think IsAbs() may be
// somewhat faster in the general case, hence the outer if...
if path , err := filepath . Abs ( f . RawPath ) ; err == nil {
f . RawPath = path
}
}
// Attempt to enable long filename support on Windows. We may still not
// have an absolute path here if the previous steps failed.
if runtime . GOOS == "windows" && filepath . IsAbs ( f . RawPath ) && ! strings . HasPrefix ( f . RawPath , ` \\ ` ) {
return ` \\?\ ` + f . RawPath
}
return f . RawPath
}
2014-10-11 15:55:11 +00:00
func ( f * FolderConfiguration ) CreateMarker ( ) error {
if ! f . HasMarker ( ) {
2015-04-05 20:52:22 +00:00
marker := filepath . Join ( f . Path ( ) , ".stfolder" )
2014-10-11 15:55:11 +00:00
fd , err := os . Create ( marker )
if err != nil {
return err
}
fd . Close ( )
osutil . HideFile ( marker )
}
return nil
}
func ( f * FolderConfiguration ) HasMarker ( ) bool {
2015-04-05 20:52:22 +00:00
_ , err := os . Stat ( filepath . Join ( f . Path ( ) , ".stfolder" ) )
2014-10-11 15:55:11 +00:00
if err != nil {
return false
}
return true
}
2014-12-08 15:36:15 +00:00
func ( f * FolderConfiguration ) DeviceIDs ( ) [ ] protocol . DeviceID {
2015-07-22 07:02:55 +00:00
deviceIDs := make ( [ ] protocol . DeviceID , len ( f . Devices ) )
for i , n := range f . Devices {
deviceIDs [ i ] = n . DeviceID
2014-10-06 07:25:45 +00:00
}
2015-07-22 07:02:55 +00:00
return deviceIDs
2014-10-06 07:25:45 +00:00
}
2014-05-25 18:49:08 +00:00
type VersioningConfiguration struct {
2015-03-10 22:45:43 +00:00
Type string ` xml:"type,attr" json:"type" `
Params map [ string ] string ` json:"params" `
2014-05-25 18:49:08 +00:00
}
type InternalVersioningConfiguration struct {
Type string ` xml:"type,attr,omitempty" `
Params [ ] InternalParam ` xml:"param" `
}
type InternalParam struct {
Key string ` xml:"key,attr" `
Val string ` xml:"val,attr" `
}
func ( c * VersioningConfiguration ) MarshalXML ( e * xml . Encoder , start xml . StartElement ) error {
var tmp InternalVersioningConfiguration
tmp . Type = c . Type
for k , v := range c . Params {
tmp . Params = append ( tmp . Params , InternalParam { k , v } )
}
return e . EncodeElement ( tmp , start )
}
func ( c * VersioningConfiguration ) UnmarshalXML ( d * xml . Decoder , start xml . StartElement ) error {
var tmp InternalVersioningConfiguration
err := d . DecodeElement ( & tmp , & start )
if err != nil {
return err
}
c . Type = tmp . Type
c . Params = make ( map [ string ] string , len ( tmp . Params ) )
for _ , p := range tmp . Params {
c . Params [ p . Key ] = p . Val
}
return nil
2014-04-08 11:45:18 +00:00
}
2014-09-28 11:00:38 +00:00
type DeviceConfiguration struct {
2015-03-10 22:45:43 +00:00
DeviceID protocol . DeviceID ` xml:"id,attr" json:"deviceID" `
Name string ` xml:"name,attr,omitempty" json:"name" `
Addresses [ ] string ` xml:"address,omitempty" json:"addresses" `
Compression protocol . Compression ` xml:"compression,attr" json:"compression" `
CertName string ` xml:"certName,attr,omitempty" json:"certName" `
Introducer bool ` xml:"introducer,attr" json:"introducer" `
2014-03-02 22:58:14 +00:00
}
2015-04-05 15:36:52 +00:00
func ( orig DeviceConfiguration ) Copy ( ) DeviceConfiguration {
c := orig
c . Addresses = make ( [ ] string , len ( orig . Addresses ) )
copy ( c . Addresses , orig . Addresses )
return c
}
2014-09-28 11:00:38 +00:00
type FolderDeviceConfiguration struct {
2015-03-10 22:45:43 +00:00
DeviceID protocol . DeviceID ` xml:"id,attr" json:"deviceID" `
2014-08-16 22:20:21 +00:00
}
2014-03-02 22:58:14 +00:00
type OptionsConfiguration struct {
2015-06-23 12:55:30 +00:00
ListenAddress [ ] string ` xml:"listenAddress" json:"listenAddress" default:"tcp://0.0.0.0:22000" `
2015-09-20 13:30:25 +00:00
GlobalAnnServers [ ] string ` xml:"globalAnnounceServer" json:"globalAnnounceServers" json:"globalAnnounceServer" default:"default" `
2015-03-10 22:45:43 +00:00
GlobalAnnEnabled bool ` xml:"globalAnnounceEnabled" json:"globalAnnounceEnabled" default:"true" `
LocalAnnEnabled bool ` xml:"localAnnounceEnabled" json:"localAnnounceEnabled" default:"true" `
2015-08-27 14:04:13 +00:00
LocalAnnPort int ` xml:"localAnnouncePort" json:"localAnnouncePort" default:"21027" `
2015-08-23 12:59:38 +00:00
LocalAnnMCAddr string ` xml:"localAnnounceMCAddr" json:"localAnnounceMCAddr" default:"[ff12::8384]:21027" `
2015-07-23 19:39:44 +00:00
RelayServers [ ] string ` xml:"relayServer" json:"relayServers" default:"dynamic+https://relays.syncthing.net" `
2015-03-10 22:45:43 +00:00
MaxSendKbps int ` xml:"maxSendKbps" json:"maxSendKbps" `
MaxRecvKbps int ` xml:"maxRecvKbps" json:"maxRecvKbps" `
ReconnectIntervalS int ` xml:"reconnectionIntervalS" json:"reconnectionIntervalS" default:"60" `
2015-07-24 19:07:26 +00:00
RelaysEnabled bool ` xml:"relaysEnabled" json:"relaysEnabled" default:"true" `
2015-06-28 20:09:03 +00:00
RelayReconnectIntervalM int ` xml:"relayReconnectIntervalM" json:"relayReconnectIntervalM" default:"10" `
2015-07-24 18:55:52 +00:00
RelayWithoutGlobalAnn bool ` xml:"relayWithoutGlobalAnn" json:"relayWithoutGlobalAnn" default:"false" `
2015-03-10 22:45:43 +00:00
StartBrowser bool ` xml:"startBrowser" json:"startBrowser" default:"true" `
UPnPEnabled bool ` xml:"upnpEnabled" json:"upnpEnabled" default:"true" `
2015-05-09 20:17:53 +00:00
UPnPLeaseM int ` xml:"upnpLeaseMinutes" json:"upnpLeaseMinutes" default:"60" `
2015-04-15 23:36:27 +00:00
UPnPRenewalM int ` xml:"upnpRenewalMinutes" json:"upnpRenewalMinutes" default:"30" `
2015-04-25 11:52:07 +00:00
UPnPTimeoutS int ` xml:"upnpTimeoutSeconds" json:"upnpTimeoutSeconds" default:"10" `
2015-03-10 22:45:43 +00:00
URAccepted int ` xml:"urAccepted" json:"urAccepted" ` // Accepted usage reporting version; 0 for off (undecided), -1 for off (permanently)
URUniqueID string ` xml:"urUniqueID" json:"urUniqueId" ` // Unique ID for reporting purposes, regenerated when UR is turned on.
2015-09-10 12:08:40 +00:00
URURL string ` xml:"urURL" json:"urURL" default:"https://data.syncthing.net/newdata" `
URPostInsecurely bool ` xml:"urPostInsecurely" json:"urPostInsecurely" default:"false" ` // For testing
URInitialDelayS int ` xml:"urInitialDelayS" json:"urInitialDelayS" default:"1800" `
2015-03-10 22:45:43 +00:00
RestartOnWakeup bool ` xml:"restartOnWakeup" json:"restartOnWakeup" default:"true" `
AutoUpgradeIntervalH int ` xml:"autoUpgradeIntervalH" json:"autoUpgradeIntervalH" default:"12" ` // 0 for off
KeepTemporariesH int ` xml:"keepTemporariesH" json:"keepTemporariesH" default:"24" ` // 0 for off
CacheIgnoredFiles bool ` xml:"cacheIgnoredFiles" json:"cacheIgnoredFiles" default:"true" `
ProgressUpdateIntervalS int ` xml:"progressUpdateIntervalS" json:"progressUpdateIntervalS" default:"5" `
SymlinksEnabled bool ` xml:"symlinksEnabled" json:"symlinksEnabled" default:"true" `
LimitBandwidthInLan bool ` xml:"limitBandwidthInLan" json:"limitBandwidthInLan" default:"false" `
2015-05-11 07:01:09 +00:00
DatabaseBlockCacheMiB int ` xml:"databaseBlockCacheMiB" json:"databaseBlockCacheMiB" default:"0" `
2015-09-05 07:43:07 +00:00
MinHomeDiskFreePct float64 ` xml:"minHomeDiskFreePct" json:"minHomeDiskFreePct" default:"1" `
2015-09-10 12:16:44 +00:00
ReleasesURL string ` xml:"releasesURL" json:"releasesURL" default:"https://api.github.com/repos/syncthing/syncthing/releases?per_page=30" `
2015-09-11 13:07:38 +00:00
AlwaysLocalNets [ ] string ` xml:"alwaysLocalNet" json:"alwaysLocalNets" `
2014-04-08 13:56:12 +00:00
}
2015-04-05 15:36:52 +00:00
func ( orig OptionsConfiguration ) Copy ( ) OptionsConfiguration {
c := orig
c . ListenAddress = make ( [ ] string , len ( orig . ListenAddress ) )
copy ( c . ListenAddress , orig . ListenAddress )
c . GlobalAnnServers = make ( [ ] string , len ( orig . GlobalAnnServers ) )
copy ( c . GlobalAnnServers , orig . GlobalAnnServers )
return c
}
2014-04-08 13:56:12 +00:00
type GUIConfiguration struct {
2015-10-12 13:27:57 +00:00
Enabled bool ` xml:"enabled,attr" json:"enabled" default:"true" `
RawAddress string ` xml:"address" json:"address" default:"127.0.0.1:8384" `
User string ` xml:"user,omitempty" json:"user" `
Password string ` xml:"password,omitempty" json:"password" `
RawUseTLS bool ` xml:"tls,attr" json:"useTLS" `
RawAPIKey string ` xml:"apikey,omitempty" json:"apiKey" `
}
func ( c GUIConfiguration ) Address ( ) string {
if override := os . Getenv ( "STGUIADDRESS" ) ; override != "" {
// This value may be of the form "scheme://address:port" or just
// "address:port". We need to chop off the scheme. We try to parse it as
// an URL if it contains a slash. If that fails, return it as is and let
// some other error handling handle it.
if strings . Contains ( override , "/" ) {
url , err := url . Parse ( override )
if err != nil {
return override
}
return url . Host
}
return override
}
return c . RawAddress
}
func ( c GUIConfiguration ) UseTLS ( ) bool {
if override := os . Getenv ( "STGUIADDRESS" ) ; override != "" {
return strings . HasPrefix ( override , "https:" )
}
return c . RawUseTLS
}
func ( c GUIConfiguration ) URL ( ) string {
u := url . URL {
Scheme : "http" ,
Host : c . Address ( ) ,
Path : "/" ,
}
if c . UseTLS ( ) {
u . Scheme = "https"
}
if strings . HasPrefix ( u . Host , ":" ) {
// Empty host, i.e. ":port", use IPv4 localhost
u . Host = "127.0.0.1" + u . Host
} else if strings . HasPrefix ( u . Host , "0.0.0.0:" ) {
// IPv4 all zeroes host, convert to IPv4 localhost
u . Host = "127.0.0.1" + u . Host [ 7 : ]
} else if strings . HasPrefix ( u . Host , "[::]:" ) {
// IPv6 all zeroes host, convert to IPv6 localhost
u . Host = "[::1]" + u . Host [ 4 : ]
}
return u . String ( )
}
func ( c GUIConfiguration ) APIKey ( ) string {
if override := os . Getenv ( "STGUIAPIKEY" ) ; override != "" {
return override
}
return c . RawAPIKey
2014-03-02 22:58:14 +00:00
}
2014-10-06 07:25:45 +00:00
func New ( myID protocol . DeviceID ) Configuration {
var cfg Configuration
2014-10-08 11:52:05 +00:00
cfg . Version = CurrentVersion
cfg . OriginalVersion = CurrentVersion
2014-10-06 07:25:45 +00:00
setDefaults ( & cfg )
setDefaults ( & cfg . Options )
setDefaults ( & cfg . GUI )
cfg . prepare ( myID )
return cfg
}
func ReadXML ( r io . Reader , myID protocol . DeviceID ) ( Configuration , error ) {
var cfg Configuration
setDefaults ( & cfg )
setDefaults ( & cfg . Options )
setDefaults ( & cfg . GUI )
err := xml . NewDecoder ( r ) . Decode ( & cfg )
2014-10-08 11:52:05 +00:00
cfg . OriginalVersion = cfg . Version
2014-10-06 07:25:45 +00:00
cfg . prepare ( myID )
return cfg , err
}
func ( cfg * Configuration ) WriteXML ( w io . Writer ) error {
e := xml . NewEncoder ( w )
e . Indent ( "" , " " )
err := e . Encode ( cfg )
if err != nil {
return err
}
_ , err = w . Write ( [ ] byte ( "\n" ) )
return err
}
2014-09-28 11:00:38 +00:00
func ( cfg * Configuration ) prepare ( myID protocol . DeviceID ) {
2014-03-04 21:29:43 +00:00
fillNilSlices ( & cfg . Options )
2014-03-04 10:25:10 +00:00
2014-12-27 23:12:12 +00:00
// Initialize an empty slices
2014-09-28 11:00:38 +00:00
if cfg . Folders == nil {
cfg . Folders = [ ] FolderConfiguration { }
2014-05-13 02:57:38 +00:00
}
2014-12-27 23:12:12 +00:00
if cfg . IgnoredDevices == nil {
cfg . IgnoredDevices = [ ] protocol . DeviceID { }
}
2014-05-13 02:57:38 +00:00
2014-09-28 11:00:38 +00:00
// Check for missing, bad or duplicate folder ID:s
var seenFolders = map [ string ] * FolderConfiguration { }
for i := range cfg . Folders {
folder := & cfg . Folders [ i ]
2014-04-27 19:53:27 +00:00
2015-04-05 20:52:22 +00:00
if len ( folder . RawPath ) == 0 {
2014-09-28 11:00:38 +00:00
folder . Invalid = "no directory configured"
2014-05-13 00:30:04 +00:00
continue
}
2015-02-09 23:56:16 +00:00
// The reason it's done like this:
// C: -> C:\ -> C:\ (issue that this is trying to fix)
// C:\somedir -> C:\somedir\ -> C:\somedir
// C:\somedir\ -> C:\somedir\\ -> C:\somedir
// This way in the tests, we get away without OS specific separators
// in the test configs.
2015-04-05 20:52:22 +00:00
folder . RawPath = filepath . Dir ( folder . RawPath + string ( filepath . Separator ) )
2015-10-03 09:58:55 +00:00
if folder . RawPath [ len ( folder . RawPath ) - 1 ] != filepath . Separator {
folder . RawPath = folder . RawPath + string ( filepath . Separator )
}
2015-01-22 23:22:30 +00:00
2014-09-28 11:00:38 +00:00
if folder . ID == "" {
folder . ID = "default"
2014-03-29 17:53:48 +00:00
}
2015-06-20 18:21:05 +00:00
if folder . RescanIntervalS > MaxRescanIntervalS {
folder . RescanIntervalS = MaxRescanIntervalS
} else if folder . RescanIntervalS < 0 {
folder . RescanIntervalS = 0
}
2014-09-28 11:00:38 +00:00
if seen , ok := seenFolders [ folder . ID ] ; ok {
l . Warnf ( "Multiple folders with ID %q; disabling" , folder . ID )
seen . Invalid = "duplicate folder ID"
folder . Invalid = "duplicate folder ID"
2014-04-27 19:53:27 +00:00
} else {
2014-09-28 11:00:38 +00:00
seenFolders [ folder . ID ] = folder
2014-03-29 17:53:48 +00:00
}
}
2015-06-23 12:55:30 +00:00
cfg . Options . ListenAddress = uniqueStrings ( cfg . Options . ListenAddress )
cfg . Options . GlobalAnnServers = uniqueStrings ( cfg . Options . GlobalAnnServers )
2015-04-04 19:49:15 +00:00
if cfg . Version < OldestHandledVersion {
l . Warnf ( "Configuration version %d is deprecated. Attempting best effort conversion, but please verify manually." , cfg . Version )
2014-06-17 21:22:19 +00:00
}
2015-03-07 19:48:53 +00:00
// Upgrade configuration versions as appropriate
2015-04-04 19:49:15 +00:00
if cfg . Version <= 5 {
2014-10-11 15:55:11 +00:00
convertV5V6 ( cfg )
}
2014-11-20 21:24:11 +00:00
if cfg . Version == 6 {
convertV6V7 ( cfg )
}
2015-03-07 19:48:53 +00:00
if cfg . Version == 7 {
convertV7V8 ( cfg )
}
2015-02-23 08:44:10 +00:00
if cfg . Version == 8 {
convertV8V9 ( cfg )
}
2015-03-21 14:33:31 +00:00
if cfg . Version == 9 {
convertV9V10 ( cfg )
}
2015-07-16 10:52:36 +00:00
if cfg . Version == 10 {
convertV10V11 ( cfg )
}
2015-06-23 12:55:30 +00:00
if cfg . Version == 11 {
convertV11V12 ( cfg )
}
2014-11-20 21:24:11 +00:00
2014-04-22 09:46:08 +00:00
// Hash old cleartext passwords
2014-04-19 11:33:51 +00:00
if len ( cfg . GUI . Password ) > 0 && cfg . GUI . Password [ 0 ] != '$' {
hash , err := bcrypt . GenerateFromPassword ( [ ] byte ( cfg . GUI . Password ) , 0 )
if err != nil {
2014-08-17 08:28:36 +00:00
l . Warnln ( "bcrypting password:" , err )
2014-04-19 11:33:51 +00:00
} else {
cfg . GUI . Password = string ( hash )
}
}
2014-09-28 11:00:38 +00:00
// Build a list of available devices
existingDevices := make ( map [ protocol . DeviceID ] bool )
for _ , device := range cfg . Devices {
existingDevices [ device . DeviceID ] = true
2014-07-22 21:27:00 +00:00
}
2014-10-06 19:43:38 +00:00
// Ensure this device is present in the config
if ! existingDevices [ myID ] {
2014-08-16 22:20:21 +00:00
myName , _ := os . Hostname ( )
2014-09-28 11:00:38 +00:00
cfg . Devices = append ( cfg . Devices , DeviceConfiguration {
DeviceID : myID ,
2014-09-28 11:05:25 +00:00
Name : myName ,
2014-08-16 22:20:21 +00:00
} )
2014-10-06 19:43:38 +00:00
existingDevices [ myID ] = true
2014-08-16 22:20:21 +00:00
}
2014-10-06 19:28:16 +00:00
2014-09-28 11:00:38 +00:00
sort . Sort ( DeviceConfigurationList ( cfg . Devices ) )
// Ensure that any loose devices are not present in the wrong places
// Ensure that there are no duplicate devices
2014-11-23 18:43:49 +00:00
// Ensure that puller settings are sane
2014-09-28 11:00:38 +00:00
for i := range cfg . Folders {
cfg . Folders [ i ] . Devices = ensureDevicePresent ( cfg . Folders [ i ] . Devices , myID )
cfg . Folders [ i ] . Devices = ensureExistingDevices ( cfg . Folders [ i ] . Devices , existingDevices )
cfg . Folders [ i ] . Devices = ensureNoDuplicates ( cfg . Folders [ i ] . Devices )
sort . Sort ( FolderDeviceConfigurationList ( cfg . Folders [ i ] . Devices ) )
2014-04-22 09:46:08 +00:00
}
// An empty address list is equivalent to a single "dynamic" entry
2014-09-28 11:00:38 +00:00
for i := range cfg . Devices {
n := & cfg . Devices [ i ]
2014-04-22 09:46:08 +00:00
if len ( n . Addresses ) == 0 || len ( n . Addresses ) == 1 && n . Addresses [ 0 ] == "" {
n . Addresses = [ ] string { "dynamic" }
}
}
2014-11-18 22:57:21 +00:00
2015-03-26 12:57:27 +00:00
// Very short reconnection intervals are annoying
if cfg . Options . ReconnectIntervalS < 5 {
cfg . Options . ReconnectIntervalS = 5
}
2015-10-12 13:27:57 +00:00
if cfg . GUI . RawAPIKey == "" {
cfg . GUI . RawAPIKey = randomString ( 32 )
2014-12-29 12:48:26 +00:00
}
2014-09-06 12:11:18 +00:00
}
2014-09-20 16:42:09 +00:00
// ChangeRequiresRestart returns true if updating the configuration requires a
// complete restart.
func ChangeRequiresRestart ( from , to Configuration ) bool {
2014-09-28 11:00:38 +00:00
// Adding, removing or changing folders requires restart
2014-10-06 19:28:16 +00:00
if ! reflect . DeepEqual ( from . Folders , to . Folders ) {
2014-09-20 16:42:09 +00:00
return true
}
2014-10-06 19:28:16 +00:00
// Removing a device requres restart
toDevs := make ( map [ protocol . DeviceID ] bool , len ( from . Devices ) )
for _ , dev := range to . Devices {
toDevs [ dev . DeviceID ] = true
}
for _ , dev := range from . Devices {
if _ , ok := toDevs [ dev . DeviceID ] ; ! ok {
2014-09-20 16:42:09 +00:00
return true
}
}
2014-11-27 09:00:07 +00:00
// Changing usage reporting to on or off does not require a restart.
to . Options . URAccepted = from . Options . URAccepted
to . Options . URUniqueID = from . Options . URUniqueID
2014-09-20 16:42:09 +00:00
// All of the generic options require restart
if ! reflect . DeepEqual ( from . Options , to . Options ) || ! reflect . DeepEqual ( from . GUI , to . GUI ) {
return true
}
return false
}
2015-06-23 12:55:30 +00:00
func convertV11V12 ( cfg * Configuration ) {
// Change listen address schema
for i , addr := range cfg . Options . ListenAddress {
if len ( addr ) > 0 && ! strings . HasPrefix ( addr , "tcp://" ) {
cfg . Options . ListenAddress [ i ] = fmt . Sprintf ( "tcp://%s" , addr )
}
}
for i , device := range cfg . Devices {
for j , addr := range device . Addresses {
if addr != "dynamic" && addr != "" {
cfg . Devices [ i ] . Addresses [ j ] = fmt . Sprintf ( "tcp://%s" , addr )
}
}
}
// Use new discovery server
2015-09-20 13:30:25 +00:00
var newDiscoServers [ ] string
var useDefault bool
for _ , addr := range cfg . Options . GlobalAnnServers {
2015-06-23 12:55:30 +00:00
if addr == "udp4://announce.syncthing.net:22026" {
2015-09-20 13:30:25 +00:00
useDefault = true
2015-06-23 12:55:30 +00:00
} else if addr == "udp6://announce-v6.syncthing.net:22026" {
2015-09-20 13:30:25 +00:00
useDefault = true
} else {
newDiscoServers = append ( newDiscoServers , addr )
2015-06-23 12:55:30 +00:00
}
}
2015-09-20 13:30:25 +00:00
if useDefault {
newDiscoServers = append ( newDiscoServers , "default" )
}
cfg . Options . GlobalAnnServers = newDiscoServers
2015-06-23 12:55:30 +00:00
2015-08-23 12:59:38 +00:00
// Use new multicast group
if cfg . Options . LocalAnnMCAddr == "[ff32::5222]:21026" {
cfg . Options . LocalAnnMCAddr = "[ff12::8384]:21027"
}
2015-08-27 14:04:13 +00:00
// Use new local discovery port
if cfg . Options . LocalAnnPort == 21025 {
cfg . Options . LocalAnnPort = 21027
}
2015-10-13 19:50:58 +00:00
// Set MaxConflicts to unlimited
for i := range cfg . Folders {
cfg . Folders [ i ] . MaxConflicts = - 1
}
2015-06-23 12:55:30 +00:00
cfg . Version = 12
}
2015-10-13 19:50:58 +00:00
func convertV10V11 ( cfg * Configuration ) {
// Set minimum disk free of existing folders to 1%
for i := range cfg . Folders {
cfg . Folders [ i ] . MinDiskFreePct = 1
}
cfg . Version = 11
}
2015-03-21 14:33:31 +00:00
func convertV9V10 ( cfg * Configuration ) {
// Enable auto normalization on existing folders.
for i := range cfg . Folders {
cfg . Folders [ i ] . AutoNormalize = true
}
cfg . Version = 10
}
2015-02-23 08:44:10 +00:00
func convertV8V9 ( cfg * Configuration ) {
// Compression is interpreted and serialized differently, but no enforced
// changes. Still need a new version number since the compression stuff
// isn't understandable by earlier versions.
cfg . Version = 9
}
2015-03-07 19:48:53 +00:00
func convertV7V8 ( cfg * Configuration ) {
// Add IPv6 announce server
if len ( cfg . Options . GlobalAnnServers ) == 1 && cfg . Options . GlobalAnnServers [ 0 ] == "udp4://announce.syncthing.net:22026" {
cfg . Options . GlobalAnnServers = append ( cfg . Options . GlobalAnnServers , "udp6://announce-v6.syncthing.net:22026" )
}
cfg . Version = 8
}
2014-11-20 21:24:11 +00:00
func convertV6V7 ( cfg * Configuration ) {
// Migrate announce server addresses to the new URL based format
for i := range cfg . Options . GlobalAnnServers {
cfg . Options . GlobalAnnServers [ i ] = "udp4://" + cfg . Options . GlobalAnnServers [ i ]
}
cfg . Version = 7
}
2014-10-11 15:55:11 +00:00
func convertV5V6 ( cfg * Configuration ) {
// Added ".stfolder" file at folder roots to identify mount issues
// Doesn't affect the config itself, but uses config migrations to identify
// the migration point.
2014-10-12 13:09:25 +00:00
for _ , folder := range Wrap ( "" , * cfg ) . Folders ( ) {
2014-10-15 09:20:40 +00:00
// Best attempt, if it fails, it fails, the user will have to fix
// it up manually, as the repo will not get started.
folder . CreateMarker ( )
2014-10-11 15:55:11 +00:00
}
cfg . Version = 6
}
2014-10-06 07:25:45 +00:00
func setDefaults ( data interface { } ) error {
s := reflect . ValueOf ( data ) . Elem ( )
t := s . Type ( )
2014-04-08 11:45:18 +00:00
2014-10-06 07:25:45 +00:00
for i := 0 ; i < s . NumField ( ) ; i ++ {
f := s . Field ( i )
tag := t . Field ( i ) . Tag
2014-03-02 22:58:14 +00:00
2014-10-06 07:25:45 +00:00
v := tag . Get ( "default" )
if len ( v ) > 0 {
switch f . Interface ( ) . ( type ) {
case string :
f . SetString ( v )
2014-03-02 22:58:14 +00:00
2014-10-06 07:25:45 +00:00
case int :
i , err := strconv . ParseInt ( v , 10 , 64 )
if err != nil {
return err
}
f . SetInt ( i )
2014-08-16 22:20:21 +00:00
2015-09-05 07:43:07 +00:00
case float64 :
i , err := strconv . ParseFloat ( v , 64 )
if err != nil {
return err
}
f . SetFloat ( i )
2014-10-06 07:25:45 +00:00
case bool :
f . SetBool ( v == "true" )
case [ ] string :
// We don't do anything with string slices here. Any default
// we set will be appended to by the XML decoder, so we fill
// those after decoding.
default :
panic ( f . Type ( ) )
}
}
}
return nil
2014-08-16 22:20:21 +00:00
}
2014-10-06 07:25:45 +00:00
// fillNilSlices sets default value on slices that are still nil.
func fillNilSlices ( data interface { } ) error {
s := reflect . ValueOf ( data ) . Elem ( )
t := s . Type ( )
for i := 0 ; i < s . NumField ( ) ; i ++ {
f := s . Field ( i )
tag := t . Field ( i ) . Tag
v := tag . Get ( "default" )
if len ( v ) > 0 {
switch f . Interface ( ) . ( type ) {
case [ ] string :
if f . IsNil ( ) {
2015-03-07 19:48:53 +00:00
// Treat the default as a comma separated slice
vs := strings . Split ( v , "," )
for i := range vs {
vs [ i ] = strings . TrimSpace ( vs [ i ] )
}
rv := reflect . MakeSlice ( reflect . TypeOf ( [ ] string { } ) , len ( vs ) , len ( vs ) )
for i , v := range vs {
rv . Index ( i ) . SetString ( v )
}
2014-10-06 07:25:45 +00:00
f . Set ( rv )
}
}
}
}
return nil
2014-08-16 22:20:21 +00:00
}
2014-10-06 07:25:45 +00:00
func uniqueStrings ( ss [ ] string ) [ ] string {
var m = make ( map [ string ] bool , len ( ss ) )
for _ , s := range ss {
2015-06-25 15:48:41 +00:00
m [ strings . Trim ( s , " " ) ] = true
2014-10-06 07:25:45 +00:00
}
var us = make ( [ ] string , 0 , len ( m ) )
for k := range m {
us = append ( us , k )
}
2015-03-07 20:05:30 +00:00
sort . Strings ( us )
2014-10-06 07:25:45 +00:00
return us
2014-08-16 22:20:21 +00:00
}
2014-09-28 11:00:38 +00:00
func ensureDevicePresent ( devices [ ] FolderDeviceConfiguration , myID protocol . DeviceID ) [ ] FolderDeviceConfiguration {
for _ , device := range devices {
if device . DeviceID . Equals ( myID ) {
return devices
2014-03-02 22:58:14 +00:00
}
}
2014-09-28 11:00:38 +00:00
devices = append ( devices , FolderDeviceConfiguration {
DeviceID : myID ,
2014-07-27 23:20:36 +00:00
} )
2014-03-02 22:58:14 +00:00
2014-09-28 11:00:38 +00:00
return devices
2014-03-02 22:58:14 +00:00
}
2014-07-22 21:27:00 +00:00
2014-09-28 11:00:38 +00:00
func ensureExistingDevices ( devices [ ] FolderDeviceConfiguration , existingDevices map [ protocol . DeviceID ] bool ) [ ] FolderDeviceConfiguration {
count := len ( devices )
2014-07-22 21:27:00 +00:00
i := 0
2014-07-27 23:08:15 +00:00
loop :
for i < count {
2014-09-28 11:00:38 +00:00
if _ , ok := existingDevices [ devices [ i ] . DeviceID ] ; ! ok {
devices [ i ] = devices [ count - 1 ]
2014-07-27 23:08:15 +00:00
count --
continue loop
2014-07-22 21:27:00 +00:00
}
2014-07-27 23:08:15 +00:00
i ++
2014-07-22 21:27:00 +00:00
}
2014-09-28 11:00:38 +00:00
return devices [ 0 : count ]
2014-07-22 21:27:00 +00:00
}
2014-07-27 23:15:16 +00:00
2014-09-28 11:00:38 +00:00
func ensureNoDuplicates ( devices [ ] FolderDeviceConfiguration ) [ ] FolderDeviceConfiguration {
count := len ( devices )
2014-07-27 23:15:16 +00:00
i := 0
2014-09-28 11:00:38 +00:00
seenDevices := make ( map [ protocol . DeviceID ] bool )
2014-07-27 23:15:16 +00:00
loop :
for i < count {
2014-09-28 11:00:38 +00:00
id := devices [ i ] . DeviceID
if _ , ok := seenDevices [ id ] ; ok {
devices [ i ] = devices [ count - 1 ]
2014-07-27 23:15:16 +00:00
count --
continue loop
}
2014-09-28 11:00:38 +00:00
seenDevices [ id ] = true
2014-07-27 23:15:16 +00:00
i ++
}
2014-09-28 11:00:38 +00:00
return devices [ 0 : count ]
2014-07-27 23:15:16 +00:00
}
2014-10-06 07:25:45 +00:00
type DeviceConfigurationList [ ] DeviceConfiguration
func ( l DeviceConfigurationList ) Less ( a , b int ) bool {
return l [ a ] . DeviceID . Compare ( l [ b ] . DeviceID ) == - 1
}
func ( l DeviceConfigurationList ) Swap ( a , b int ) {
l [ a ] , l [ b ] = l [ b ] , l [ a ]
}
func ( l DeviceConfigurationList ) Len ( ) int {
return len ( l )
}
type FolderDeviceConfigurationList [ ] FolderDeviceConfiguration
func ( l FolderDeviceConfigurationList ) Less ( a , b int ) bool {
return l [ a ] . DeviceID . Compare ( l [ b ] . DeviceID ) == - 1
}
func ( l FolderDeviceConfigurationList ) Swap ( a , b int ) {
l [ a ] , l [ b ] = l [ b ] , l [ a ]
}
func ( l FolderDeviceConfigurationList ) Len ( ) int {
return len ( l )
}
2014-12-29 12:48:26 +00:00
// randomCharset contains the characters that can make up a randomString().
const randomCharset = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-"
// randomString returns a string of random characters (taken from
// randomCharset) of the specified length.
func randomString ( l int ) string {
bs := make ( [ ] byte , l )
for i := range bs {
bs [ i ] = randomCharset [ rand . Intn ( len ( randomCharset ) ) ]
}
return string ( bs )
}
2015-04-25 05:13:53 +00:00
type PullOrder int
const (
OrderRandom PullOrder = iota // default is random
OrderAlphabetic
OrderSmallestFirst
OrderLargestFirst
OrderOldestFirst
OrderNewestFirst
)
func ( o PullOrder ) String ( ) string {
switch o {
case OrderRandom :
return "random"
case OrderAlphabetic :
return "alphabetic"
case OrderSmallestFirst :
return "smallestFirst"
case OrderLargestFirst :
return "largestFirst"
case OrderOldestFirst :
return "oldestFirst"
case OrderNewestFirst :
return "newestFirst"
default :
return "unknown"
}
}
func ( o PullOrder ) MarshalText ( ) ( [ ] byte , error ) {
return [ ] byte ( o . String ( ) ) , nil
}
func ( o * PullOrder ) UnmarshalText ( bs [ ] byte ) error {
switch string ( bs ) {
case "random" :
* o = OrderRandom
case "alphabetic" :
* o = OrderAlphabetic
case "smallestFirst" :
* o = OrderSmallestFirst
case "largestFirst" :
* o = OrderLargestFirst
case "oldestFirst" :
* o = OrderOldestFirst
case "newestFirst" :
* o = OrderNewestFirst
default :
* o = OrderRandom
}
return nil
}