cmd/syncthing: Refactor command line parsing (#7330)

This commit is contained in:
Jakob Borg 2021-02-10 20:35:37 +01:00 committed by GitHub
parent 0471daf771
commit 4f20c900d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 278 additions and 321 deletions

View File

@ -15,19 +15,17 @@ import (
"time"
)
func init() {
if innerProcess && os.Getenv("STBLOCKPROFILE") != "" {
profiler := pprof.Lookup("block")
if profiler == nil {
panic("Couldn't find block profiler")
}
l.Debugln("Starting block profiling")
go func() {
err := saveBlockingProfiles(profiler) // Only returns on error
l.Warnln("Block profiler failed:", err)
panic("Block profiler failed")
}()
func startBlockProfiler() {
profiler := pprof.Lookup("block")
if profiler == nil {
panic("Couldn't find block profiler")
}
l.Debugln("Starting block profiling")
go func() {
err := saveBlockingProfiles(profiler) // Only returns on error
l.Warnln("Block profiler failed:", err)
panic("Block profiler failed")
}()
}
func saveBlockingProfiles(profiler *pprof.Profile) error {

View File

@ -11,24 +11,17 @@ import (
"os"
"runtime"
"runtime/pprof"
"strconv"
"syscall"
"time"
)
func init() {
if innerProcess && os.Getenv("STHEAPPROFILE") != "" {
rate := 1
if i, err := strconv.Atoi(os.Getenv("STHEAPPROFILE")); err == nil {
rate = i
}
l.Debugln("Starting heap profiling")
go func() {
err := saveHeapProfiles(rate) // Only returns on error
l.Warnln("Heap profiler failed:", err)
panic("Heap profiler failed")
}()
}
func startHeapProfiler() {
l.Debugln("Starting heap profiling")
go func() {
err := saveHeapProfiles(1) // Only returns on error
l.Warnln("Heap profiler failed:", err)
panic("Heap profiler failed")
}()
}
func saveHeapProfiles(rate int) error {

View File

@ -10,7 +10,6 @@ import (
"bytes"
"context"
"crypto/tls"
"flag"
"fmt"
"io"
"io/ioutil"
@ -22,13 +21,16 @@ import (
"os/signal"
"path"
"path/filepath"
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"strings"
"syscall"
"time"
"github.com/alecthomas/kong"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
@ -55,9 +57,8 @@ const (
)
const (
usage = "syncthing [options]"
extraUsage = `
The -logflags value is a sum of the following:
The --logflags value is a sum of the following:
1 Date
2 Time
@ -80,31 +81,11 @@ Development Settings
--------------------
The following environment variables modify Syncthing's behavior in ways that
are mostly useful for developers. Use with care.
STNODEFAULTFOLDER Don't create a default folder when starting for the first
time. This variable will be ignored anytime after the first
run.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in
assets.
are mostly useful for developers. Use with care. See also the --debug-* options
above.
STTRACE A comma separated string of facilities to trace. The valid
facility strings listed below.
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start
the profiler with HTTP access.
STCPUPROFILE Write a CPU profile to cpu-$pid.pprof on exit.
STHEAPPROFILE Write heap profiles to heap-$pid-$timestamp.pprof each time
heap usage increases.
STBLOCKPROFILE Write block profiles to block-$pid-$timestamp.pprof every 20
seconds.
STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
supported on Windows.
facility strings are listed below.
STDEADLOCKTIMEOUT Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
@ -112,24 +93,11 @@ are mostly useful for developers. Use with care.
STLOCKTHRESHOLD Used for debugging internal deadlocks; sets debug
sensitivity. Use only under direction of a developer.
STNORESTART Equivalent to the -no-restart argument.
STNOUPGRADE Disable automatic upgrades.
STHASHING Select the SHA256 hashing package to use. Possible values
are "standard" for the Go standard library implementation,
"minio" for the github.com/minio/sha256-simd implementation,
and blank (the default) for auto detection.
STRECHECKDBEVERY Set to a time interval to override the default database
check interval of 30 days (720h). The interval understands
"h", "m" and "s" abbreviations for hours minutes and seconds.
Valid values are like "720h", "30s", etc.
STGCINDIRECTEVERY Set to a time interval to override the default database
indirection GC interval of 13 hours. Same format as the
STRECHECKDBEVERY variable.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.
@ -143,75 +111,83 @@ Debugging Facilities
The following are valid values for the STTRACE variable:
%s`
%s
`
)
var (
// Environment options
innerProcess = os.Getenv("STMONITORED") != ""
noDefaultFolder = os.Getenv("STNODEFAULTFOLDER") != ""
upgradeCheckInterval = 5 * time.Minute
upgradeRetryInterval = time.Hour
upgradeCheckKey = "lastUpgradeCheck"
upgradeTimeKey = "lastUpgradeTime"
upgradeVersionKey = "lastUpgradeVersion"
errConcurrentUpgrade = errors.New("upgrade prevented by other running Syncthing instance")
errTooEarlyUpgradeCheck = fmt.Errorf("last upgrade check happened less than %v ago, skipping", upgradeCheckInterval)
errTooEarlyUpgrade = fmt.Errorf("last upgrade happened less than %v ago, skipping", upgradeRetryInterval)
)
type RuntimeOptions struct {
syncthing.Options
homeDir string
confDir string
dataDir string
resetDatabase bool
showVersion bool
showPaths bool
showDeviceId bool
doUpgrade bool
doUpgradeCheck bool
upgradeTo string
noBrowser bool
browserOnly bool
hideConsole bool
logFile string
logMaxSize int
logMaxFiles int
auditEnabled bool
auditFile string
paused bool
unpaused bool
guiAddress string
guiAPIKey string
generateDir string
noRestart bool
cpuProfile bool
stRestarting bool
logFlags int
showHelp bool
allowNewerConfig bool
// The cli struct is the main entry point for the command line parser. The
// commands and options here are top level commands to syncthing.
var cli struct {
Serve serveOptions `cmd:"" help:"Run Syncthing"`
}
func defaultRuntimeOptions() RuntimeOptions {
options := RuntimeOptions{
Options: syncthing.Options{
AssetDir: os.Getenv("STGUIASSETS"),
NoUpgrade: os.Getenv("STNOUPGRADE") != "",
ProfilerURL: os.Getenv("STPROFILER"),
},
noRestart: os.Getenv("STNORESTART") != "",
cpuProfile: os.Getenv("STCPUPROFILE") != "",
stRestarting: os.Getenv("STRESTART") != "",
logFlags: log.Ltime,
logMaxSize: 10 << 20, // 10 MiB
logMaxFiles: 3, // plus the current one
}
// serveOptions are the options for the `syncthing serve` command.
type serveOptions struct {
AllowNewerConfig bool `help:"Allow loading newer than current config version"`
Audit bool `help:"Write events to audit file"`
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
BrowserOnly bool `help:"Open GUI in browser"`
ConfDir string `name:"conf" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
DataDir string `name:"data" placeholder:"PATH" help:"Set data directory (database and logs)"`
DeviceID bool `help:"Show the device ID"`
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"`
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
HideConsole bool `help:"Hide console window (Windows only)"`
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
LogFile string `placeholder:"PATH" help:"Log file name (see below)"`
LogFlags int `placeholder:"BITS" help:"Select information in log line prefix (see below)"`
LogMaxFiles int `placeholder:"N" name:"log-max-old-files" help:"Number of old files to keep (zero to keep only current)"`
LogMaxSize int `placeholder:"BYTES" help:"Maximum size of any file (zero to disable log rotation)"`
NoBrowser bool `help:"Do not start browser"`
NoRestart bool `env:"STNORESTART" help:"Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash"`
NoDefaultFolder bool `env:"STNODEFAULTFOLDER" help:"Don't create the \"default\" folder on first startup"`
NoUpgrade bool `env:"STNOUPGRADE" help:"Disable automatic upgrades"`
Paths bool `help:"Show configuration paths"`
Paused bool `help:"Start with all devices and folders paused"`
Unpaused bool `help:"Start with all devices and folders unpaused"`
Upgrade bool `help:"Perform upgrade"`
UpgradeCheck bool `help:"Check for available upgrade"`
UpgradeTo string `placeholder:"URL" help:"Force upgrade directly from specified URL"`
Verbose bool `help:"Print verbose log output"`
Version bool `help:"Show version"`
// Debug options below
DebugDBIndirectGCInterval time.Duration `env:"STGCINDIRECTEVERY" help:"Database indirection GC interval"`
DebugDBRecheckInterval time.Duration `env:"STRECHECKDBEVERY" help:"Database metadata recalculation interval"`
DebugDeadlockTimeout int `placeholder:"SECONDS" env:"STDEADLOCKTIMEOUT" help:"Used for debugging internal deadlocks"`
DebugGUIAssetsDir string `placeholder:"PATH" help:"Directory to load GUI assets from" env:"STGUIASSETS"`
DebugPerfStats bool `env:"STPERFSTATS" help:"Write running performance statistics to perf-$pid.csv (Unix only)"`
DebugProfileBlock bool `env:"STBLOCKPROFILE" help:"Write block profiles to block-$pid-$timestamp.pprof every 20 seconds"`
DebugProfileCPU bool `help:"Write a CPU profile to cpu-$pid.pprof on exit" env:"CPUPROFILE"`
DebugProfileHeap bool `env:"STHEAPPROFILE" help:"Write heap profiles to heap-$pid-$timestamp.pprof each time heap usage increases"`
DebugProfilerListen string `placeholder:"ADDR" env:"STPROFILER" help:"Network profiler listen address"`
DebugResetDatabase bool `name:"reset-database" help:"Reset the database, forcing a full rescan and resync"`
DebugResetDeltaIdxs bool `name:"reset-deltas" help:"Reset delta index IDs, forcing a full index exchange"`
// Internal options, not shown to users
InternalRestarting bool `env:"STRESTART" hidden:"1"`
InternalInnerProcess bool `env:"STMONITORED" hidden:"1"`
}
func (options *serveOptions) setDefaults() {
options.LogFlags = log.Ltime
options.LogMaxSize = 10 << 20 // 10 MiB
options.LogMaxFiles = 3 // plus the current one
if os.Getenv("STTRACE") != "" {
options.logFlags = logger.DebugFlags
options.LogFlags = logger.DebugFlags
}
// On non-Windows, we explicitly default to "-" which means stdout. On
@ -219,107 +195,97 @@ func defaultRuntimeOptions() RuntimeOptions {
// default path, unless the user has manually specified "-" or
// something else.
if runtime.GOOS == "windows" {
options.logFile = "default"
options.LogFile = "default"
} else {
options.logFile = "-"
options.LogFile = "-"
}
return options
}
func parseCommandLineOptions() RuntimeOptions {
options := defaultRuntimeOptions()
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.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")
flag.BoolVar(&options.noRestart, "no-restart", options.noRestart, "Do not restart Syncthing when exiting due to API/GUI command, upgrade, or crash")
flag.BoolVar(&options.resetDatabase, "reset-database", false, "Reset the database, forcing a full rescan and resync")
flag.BoolVar(&options.ResetDeltaIdxs, "reset-deltas", false, "Reset delta index IDs, forcing a full index exchange")
flag.BoolVar(&options.doUpgrade, "upgrade", false, "Perform upgrade")
flag.BoolVar(&options.doUpgradeCheck, "upgrade-check", false, "Check for available upgrade")
flag.BoolVar(&options.showVersion, "version", false, "Show version")
flag.BoolVar(&options.showHelp, "help", false, "Show this help")
flag.BoolVar(&options.showPaths, "paths", false, "Show configuration paths")
flag.BoolVar(&options.showDeviceId, "device-id", false, "Show the device ID")
flag.StringVar(&options.upgradeTo, "upgrade-to", options.upgradeTo, "Force upgrade directly from specified URL")
flag.BoolVar(&options.auditEnabled, "audit", false, "Write events to audit file")
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 (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)")
flag.BoolVar(&options.allowNewerConfig, "allow-newer-config", false, "Allow loading newer than current config version")
if runtime.GOOS == "windows" {
// Allow user to hide the console window
flag.BoolVar(&options.hideConsole, "no-console", false, "Hide console window")
}
longUsage := fmt.Sprintf(extraUsage, debugFacilities())
flag.Usage = usageFor(flag.CommandLine, usage, longUsage)
flag.Parse()
if len(flag.Args()) > 0 {
flag.Usage()
os.Exit(2)
}
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)
// First some massaging of the raw command line to fit the new model.
// Basically this means adding the default command at the front, and
// converting -options to --options.
if options.guiAddress != "" {
// The config picks this up from the environment.
os.Setenv("STGUIADDRESS", options.guiAddress)
}
if options.guiAPIKey != "" {
// The config picks this up from the environment.
os.Setenv("STGUIAPIKEY", options.guiAPIKey)
args := os.Args[1:]
switch {
case len(args) == 0:
// Empty command line is equivalent to just calling serve
args = []string{"serve"}
case args[0] == "-help":
// For consistency, we consider this equivalent with --help even
// though kong would otherwise consider it a bad flag.
args[0] = "--help"
case args[0] == "-h", args[0] == "--help":
// Top level request for help, let it pass as-is to be handled by
// kong to list commands.
case strings.HasPrefix(args[0], "-"):
// There are flags not preceded by a command, so we tack on the
// "serve" command and convert the old style arguments (single dash)
// to new style (double dash).
args = append([]string{"serve"}, convertLegacyArgs(args)...)
}
if options.hideConsole {
cli.Serve.setDefaults()
// Create a parser with an overridden help function to print our extra
// help info.
parser, err := kong.New(&cli, kong.Help(extraHelpPrinter))
if err != nil {
log.Fatal(err)
}
ctx, err := parser.Parse(args)
parser.FatalIfErrorf(err)
err = ctx.Run()
parser.FatalIfErrorf(err)
}
func extraHelpPrinter(options kong.HelpOptions, ctx *kong.Context) error {
if err := kong.DefaultHelpPrinter(options, ctx); err != nil {
return err
}
if ctx.Command() == "serve" {
// Help was requested for `syncthing serve`, so we add our extra
// usage info afte the normal options output.
fmt.Printf(extraUsage, debugFacilities())
}
return nil
}
// serveOptions.Run() is the entrypoint for `syncthing serve`
func (options serveOptions) Run() error {
l.SetFlags(options.LogFlags)
if options.GUIAddress != "" {
// The config picks this up from the environment.
os.Setenv("STGUIADDRESS", options.GUIAddress)
}
if options.GUIAPIKey != "" {
// The config picks this up from the environment.
os.Setenv("STGUIAPIKEY", options.GUIAPIKey)
}
if options.HideConsole {
osutil.HideConsole()
}
// Not set as default above because the strings can be really long.
var err error
homeSet := options.homeDir != ""
confSet := options.confDir != ""
dataSet := options.dataDir != ""
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)
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 = setLocation(locations.ConfigBaseDir, options.ConfDir); err == nil {
err = setLocation(locations.DataBaseDir, options.DataDir)
}
}
if err != nil {
@ -327,34 +293,29 @@ func main() {
os.Exit(svcutil.ExitError.AsInt())
}
if options.logFile == "default" || options.logFile == "" {
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)
options.LogFile = locations.Get(locations.LogFile)
}
if options.AssetDir == "" {
if options.DebugGUIAssetsDir == "" {
// The asset dir is blank if STGUIASSETS wasn't set, in which case we
// should look for extra assets in the default place.
options.AssetDir = locations.Get(locations.GUIAssets)
options.DebugGUIAssetsDir = locations.Get(locations.GUIAssets)
}
if options.showVersion {
if options.Version {
fmt.Println(build.LongVersion)
return
return nil
}
if options.showHelp {
flag.Usage()
return
}
if options.showPaths {
if options.Paths {
showPaths(options)
return
return nil
}
if options.showDeviceId {
if options.DeviceID {
cert, err := tls.LoadX509KeyPair(
locations.Get(locations.CertFile),
locations.Get(locations.KeyFile),
@ -365,23 +326,23 @@ func main() {
}
fmt.Println(protocol.NewDeviceID(cert.Certificate[0]))
return
return nil
}
if options.browserOnly {
if options.BrowserOnly {
if err := openGUI(protocol.EmptyDeviceID); err != nil {
l.Warnln("Failed to open web UI:", err)
os.Exit(svcutil.ExitError.AsInt())
}
return
return nil
}
if options.generateDir != "" {
if err := generate(options.generateDir); err != nil {
if options.GenerateDir != "" {
if err := generate(options.GenerateDir, options.NoDefaultFolder); err != nil {
l.Warnln("Failed to generate config and keys:", err)
os.Exit(svcutil.ExitError.AsInt())
}
return
return nil
}
// Ensure that our home directory exists.
@ -390,25 +351,25 @@ func main() {
os.Exit(svcutil.ExitError.AsInt())
}
if options.upgradeTo != "" {
err := upgrade.ToURL(options.upgradeTo)
if options.UpgradeTo != "" {
err := upgrade.ToURL(options.UpgradeTo)
if err != nil {
l.Warnln("Error while Upgrading:", err)
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Upgraded from", options.upgradeTo)
return
l.Infoln("Upgraded from", options.UpgradeTo)
return nil
}
if options.doUpgradeCheck {
if options.UpgradeCheck {
if _, err := checkUpgrade(); err != nil {
l.Warnln("Checking for upgrade:", err)
os.Exit(exitCodeForUpgrade(err))
}
return
return nil
}
if options.doUpgrade {
if options.Upgrade {
release, err := checkUpgrade()
if err == nil {
// Use leveldb database locks to protect against concurrent upgrades
@ -428,24 +389,25 @@ func main() {
os.Exit(svcutil.ExitUpgrade.AsInt())
}
if options.resetDatabase {
if options.DebugResetDatabase {
if err := resetDB(); err != nil {
l.Warnln("Resetting database:", err)
os.Exit(svcutil.ExitError.AsInt())
}
l.Infoln("Successfully reset database - it will be rebuilt after next start.")
return
return nil
}
if innerProcess {
if options.InternalInnerProcess {
syncthingMain(options)
} else {
monitorMain(options)
}
return nil
}
func openGUI(myID protocol.DeviceID) error {
cfg, err := loadOrDefaultConfig(myID, events.NoopLogger)
cfg, err := loadOrDefaultConfig(myID, events.NoopLogger, true)
if err != nil {
return err
}
@ -459,7 +421,7 @@ func openGUI(myID protocol.DeviceID) error {
return nil
}
func generate(generateDir string) error {
func generate(generateDir string, noDefaultFolder bool) error {
dir, err := fs.ExpandTilde(generateDir)
if err != nil {
return err
@ -530,7 +492,7 @@ func (e *errNoUpgrade) Error() string {
}
func checkUpgrade() (upgrade.Release, error) {
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger, true)
if err != nil {
return upgrade.Release{}, err
}
@ -549,7 +511,7 @@ func checkUpgrade() (upgrade.Release, error) {
}
func upgradeViaRest() error {
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
cfg, _ := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger, true)
u, err := url.Parse(cfg.GUI().URL())
if err != nil {
return err
@ -584,7 +546,17 @@ func upgradeViaRest() error {
return err
}
func syncthingMain(runtimeOptions RuntimeOptions) {
func syncthingMain(options serveOptions) {
if options.DebugProfileBlock {
startBlockProfiler()
}
if options.DebugProfileHeap {
startHeapProfiler()
}
if options.DebugPerfStats {
startPerfStats()
}
// Set a log prefix similar to the ID we will have later on, or early log
// lines look ugly.
l.SetPrefix("[start] ")
@ -615,7 +587,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
evLogger := events.NewLogger()
earlyService.Add(evLogger)
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, runtimeOptions.allowNewerConfig, noDefaultFolder)
cfgWrapper, err := syncthing.LoadConfigAtStartup(locations.Get(locations.ConfigFile), cert, evLogger, options.AllowNewerConfig, options.NoDefaultFolder)
if err != nil {
l.Warnln("Failed to initialize config:", err)
os.Exit(svcutil.ExitError.AsInt())
@ -628,7 +600,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
// unless we are in a build where it's disabled or the STNOUPGRADE
// environment variable is set.
if build.IsCandidate && !upgrade.DisabledByCompilation && !runtimeOptions.NoUpgrade {
if build.IsCandidate && !upgrade.DisabledByCompilation && !options.NoUpgrade {
cfgWrapper.Modify(func(cfg *config.Configuration) {
l.Infoln("Automatic upgrade is always enabled for candidate releases.")
if cfg.Options.AutoUpgradeIntervalH == 0 || cfg.Options.AutoUpgradeIntervalH > 24 {
@ -652,7 +624,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
// upgrade immedately. The auto-upgrade routine can only be started
// later after App is initialised.
autoUpgradePossible := autoUpgradePossible(runtimeOptions)
autoUpgradePossible := autoUpgradePossible(options)
if autoUpgradePossible && cfgWrapper.Options().AutoUpgradeEnabled() {
// try to do upgrade directly and log the error if relevant.
release, err := initialAutoUpgradeCheck(db.NewMiscDataNamespace(ldb))
@ -671,15 +643,24 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
if runtimeOptions.unpaused {
if options.Unpaused {
setPauseState(cfgWrapper, false)
} else if runtimeOptions.paused {
} else if options.Paused {
setPauseState(cfgWrapper, true)
}
appOpts := runtimeOptions.Options
if runtimeOptions.auditEnabled {
appOpts.AuditWriter = auditWriter(runtimeOptions.auditFile)
appOpts := syncthing.Options{
AssetDir: options.DebugGUIAssetsDir,
DeadlockTimeoutS: options.DebugDeadlockTimeout,
NoUpgrade: options.NoUpgrade,
ProfilerAddr: options.DebugProfilerListen,
ResetDeltaIdxs: options.DebugResetDeltaIdxs,
Verbose: options.Verbose,
DBRecheckInterval: options.DebugDBRecheckInterval,
DBIndirectGCInterval: options.DebugDBIndirectGCInterval,
}
if options.Audit {
appOpts.AuditWriter = auditWriter(options.AuditFile)
}
if t := os.Getenv("STDEADLOCKTIMEOUT"); t != "" {
secs, _ := strconv.Atoi(t)
@ -708,7 +689,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
runtime.GOMAXPROCS(runtime.NumCPU())
}
if runtimeOptions.cpuProfile {
if options.DebugProfileCPU {
f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid()))
if err != nil {
l.Warnln("Creating profile:", err)
@ -728,7 +709,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
cleanConfigDirectory()
if cfgWrapper.Options().StartBrowser && !runtimeOptions.noBrowser && !runtimeOptions.stRestarting {
if cfgWrapper.Options().StartBrowser && !options.NoBrowser && !options.InternalRestarting {
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in its own routine.
go func() { _ = openURL(cfgWrapper.GUI().URL()) }()
@ -740,7 +721,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
l.Warnln("Syncthing stopped with error:", app.Error())
}
if runtimeOptions.cpuProfile {
if options.DebugProfileCPU {
pprof.StopCPUProfile()
}
@ -768,7 +749,7 @@ func setupSignalHandling(app *syncthing.App) {
}()
}
func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger) (config.Wrapper, error) {
func loadOrDefaultConfig(myID protocol.DeviceID, evLogger events.Logger, noDefaultFolder bool) (config.Wrapper, error) {
cfgFile := locations.Get(locations.ConfigFile)
cfg, _, err := config.Load(cfgFile, myID, evLogger)
@ -858,11 +839,11 @@ func standbyMonitor(app *syncthing.App, cfg config.Wrapper) {
}
}
func autoUpgradePossible(runtimeOptions RuntimeOptions) bool {
func autoUpgradePossible(options serveOptions) bool {
if upgrade.DisabledByCompilation {
return false
}
if runtimeOptions.NoUpgrade {
if options.NoUpgrade {
l.Infof("No automatic upgrades; STNOUPGRADE environment variable defined.")
return false
}
@ -989,13 +970,13 @@ func cleanConfigDirectory() {
}
}
func showPaths(options RuntimeOptions) {
func showPaths(options serveOptions) {
fmt.Printf("Configuration file:\n\t%s\n\n", locations.Get(locations.ConfigFile))
fmt.Printf("Database directory:\n\t%s\n\n", locations.Get(locations.Database))
fmt.Printf("Device private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.KeyFile), locations.Get(locations.CertFile))
fmt.Printf("HTTPS private key & certificate files:\n\t%s\n\t%s\n\n", locations.Get(locations.HTTPSKeyFile), locations.Get(locations.HTTPSCertFile))
fmt.Printf("Log file:\n\t%s\n\n", options.logFile)
fmt.Printf("GUI override directory:\n\t%s\n\n", options.AssetDir)
fmt.Printf("Log file:\n\t%s\n\n", options.LogFile)
fmt.Printf("GUI override directory:\n\t%s\n\n", options.DebugGUIAssetsDir)
fmt.Printf("Default sync folder directory:\n\t%s\n\n", locations.Get(locations.DefFolder))
}
@ -1020,3 +1001,32 @@ func exitCodeForUpgrade(err error) int {
}
return svcutil.ExitError.AsInt()
}
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)
}
// convertLegacyArgs returns the slice of arguments with single dash long
// flags converted to double dash long flags.
func convertLegacyArgs(args []string) []string {
// Legacy args begin with a single dash, followed by two or more characters.
legacyExp := regexp.MustCompile(`^-\w{2,}`)
res := make([]string, len(args))
for i, arg := range args {
if legacyExp.MatchString(arg) {
res[i] = "-" + arg
} else {
res[i] = arg
}
}
return res
}

View File

@ -44,12 +44,12 @@ const (
panicUploadNoticeWait = 10 * time.Second
)
func monitorMain(runtimeOptions RuntimeOptions) {
func monitorMain(options serveOptions) {
l.SetPrefix("[monitor] ")
var dst io.Writer = os.Stdout
logFile := runtimeOptions.logFile
logFile := options.LogFile
if logFile != "-" {
if expanded, err := fs.ExpandTilde(logFile); err == nil {
logFile = expanded
@ -59,8 +59,8 @@ func monitorMain(runtimeOptions RuntimeOptions) {
open := func(name string) (io.WriteCloser, error) {
return newAutoclosedFile(name, logFileAutoCloseDelay, logFileMaxOpenTime)
}
if runtimeOptions.logMaxSize > 0 {
fileDst, err = newRotatedFile(logFile, open, int64(runtimeOptions.logMaxSize), runtimeOptions.logMaxFiles)
if options.LogMaxSize > 0 {
fileDst, err = newRotatedFile(logFile, open, int64(options.LogMaxSize), options.LogMaxFiles)
} else {
fileDst, err = open(logFile)
}
@ -174,7 +174,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
if exiterr, ok := err.(*exec.ExitError); ok {
exitCode := exiterr.ExitCode()
if stopped || runtimeOptions.noRestart {
if stopped || options.NoRestart {
os.Exit(exitCode)
}
if exitCode == svcutil.ExitUpgrade.AsInt() {
@ -188,7 +188,7 @@ func monitorMain(runtimeOptions RuntimeOptions) {
}
}
if runtimeOptions.noRestart {
if options.NoRestart {
os.Exit(svcutil.ExitError.AsInt())
}
@ -563,7 +563,7 @@ func childEnv() []string {
// panicUploadMaxWait uploading panics...
func maybeReportPanics() {
// Try to get a config to see if/where panics should be reported.
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger)
cfg, err := loadOrDefaultConfig(protocol.EmptyDeviceID, events.NoopLogger, true)
if err != nil {
l.Warnln("Couldn't load config; not reporting crash")
return

View File

@ -18,10 +18,8 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
)
func init() {
if innerProcess && os.Getenv("STPERFSTATS") != "" {
go savePerfStats(fmt.Sprintf("perfstats-%d.csv", syscall.Getpid()))
}
func startPerfStats() {
go savePerfStats(fmt.Sprintf("perfstats-%d.csv", syscall.Getpid()))
}
func savePerfStats(file string) {

View File

@ -0,0 +1,12 @@
// Copyright (C) 2021 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/.
// +build solaris windows
package main
func startPerfStats() {
}

View File

@ -1,57 +0,0 @@
// Copyright (C) 2014 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 main
import (
"bytes"
"flag"
"fmt"
"io"
"text/tabwriter"
)
func optionTable(w io.Writer, rows [][]string) {
tw := tabwriter.NewWriter(w, 2, 4, 2, ' ', 0)
for _, row := range rows {
for i, cell := range row {
if i > 0 {
tw.Write([]byte("\t"))
}
tw.Write([]byte(cell))
}
tw.Write([]byte("\n"))
}
tw.Flush()
}
func usageFor(fs *flag.FlagSet, usage string, extra string) func() {
return func() {
var b bytes.Buffer
b.WriteString("Usage:\n " + usage + "\n")
var options [][]string
fs.VisitAll(func(f *flag.Flag) {
var opt = " -" + f.Name
if f.DefValue != "false" {
opt += "=" + fmt.Sprintf(`"%s"`, f.DefValue)
}
options = append(options, []string{opt, f.Usage})
})
if len(options) > 0 {
b.WriteString("\nOptions:\n")
optionTable(&b, options)
}
fmt.Println(b.String())
if len(extra) > 0 {
fmt.Println(extra)
}
}
}

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/syncthing/syncthing
require (
github.com/AudriusButkevicius/pfilter v0.0.0-20190627213056-c55ef6137fc6
github.com/AudriusButkevicius/recli v0.0.5
github.com/alecthomas/kong v0.2.12
github.com/bkaradzic/go-lz4 v0.0.0-20160924222819-7224d8d8f27e
github.com/calmh/xdr v1.1.0
github.com/ccding/go-stun v0.1.2

2
go.sum
View File

@ -25,6 +25,8 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUW
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/kong v0.2.12 h1:X3kkCOXGUNzLmiu+nQtoxWqj4U2a39MpSJR3QdQXOwI=
github.com/alecthomas/kong v0.2.12/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=

View File

@ -57,7 +57,7 @@ type Options struct {
AuditWriter io.Writer
DeadlockTimeoutS int
NoUpgrade bool
ProfilerURL string
ProfilerAddr string
ResetDeltaIdxs bool
Verbose bool
// null duration means use default value
@ -169,11 +169,11 @@ func (a *App) startup() error {
return err
}
if len(a.opts.ProfilerURL) > 0 {
if len(a.opts.ProfilerAddr) > 0 {
go func() {
l.Debugln("Starting profiler on", a.opts.ProfilerURL)
l.Debugln("Starting profiler on", a.opts.ProfilerAddr)
runtime.SetBlockProfileRate(1)
err := http.ListenAndServe(a.opts.ProfilerURL, nil)
err := http.ListenAndServe(a.opts.ProfilerAddr, nil)
if err != nil {
l.Warnln(err)
return