Handle flags explicitly set to defaults.

This prevents treating a flag that was explicitly set to the default
as unchanged, since the explicit set is recorded in the new
*cfgutil.ExplicitString flag type.
This commit is contained in:
Josh Rickmar 2016-05-05 10:29:29 -04:00
parent 39fd9bd0f3
commit f3df6c8bc9
5 changed files with 109 additions and 72 deletions

View file

@ -61,7 +61,7 @@ func walletMain() error {
}()
}
dbDir := networkDir(cfg.AppDataDir, activeNet.Params)
dbDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
loader := wallet.NewLoader(activeNet.Params, dbDir)
// Create and start HTTP server to serve wallet client connections.
@ -197,7 +197,7 @@ func readCAFile() []byte {
var certs []byte
if !cfg.DisableClientTLS {
var err error
certs, err = ioutil.ReadFile(cfg.CAFile)
certs, err = ioutil.ReadFile(cfg.CAFile.Value)
if err != nil {
log.Warnf("Cannot open CA file: %v", err)
// If there's an error reading the CA file, continue

119
config.go
View file

@ -45,30 +45,30 @@ var (
type config struct {
// General application behavior
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
Create bool `long:"create" description:"Create the wallet if it does not exist"`
CreateTemp bool `long:"createtemp" description:"Create a temporary simulation wallet (pass=password) in the data directory indicated; must call with --datadir"`
AppDataDir string `short:"A" long:"appdata" description:"Application data directory for wallet config, databases and logs"`
TestNet3 bool `long:"testnet" description:"Use the test Bitcoin network (version 3) (default mainnet)"`
SimNet bool `long:"simnet" description:"Use the simulation test network (default mainnet)"`
NoInitialLoad bool `long:"noinitialload" description:"Defer wallet creation/opening on startup and enable loading wallets over RPC"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
LogDir string `long:"logdir" description:"Directory to log output."`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
ConfigFile *cfgutil.ExplicitString `short:"C" long:"configfile" description:"Path to configuration file"`
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
Create bool `long:"create" description:"Create the wallet if it does not exist"`
CreateTemp bool `long:"createtemp" description:"Create a temporary simulation wallet (pass=password) in the data directory indicated; must call with --datadir"`
AppDataDir *cfgutil.ExplicitString `short:"A" long:"appdata" description:"Application data directory for wallet config, databases and logs"`
TestNet3 bool `long:"testnet" description:"Use the test Bitcoin network (version 3) (default mainnet)"`
SimNet bool `long:"simnet" description:"Use the simulation test network (default mainnet)"`
NoInitialLoad bool `long:"noinitialload" description:"Defer wallet creation/opening on startup and enable loading wallets over RPC"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
LogDir string `long:"logdir" description:"Directory to log output."`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
// Wallet options
WalletPass string `long:"walletpass" default-mask:"-" description:"The public wallet password -- Only required if the wallet was created with one"`
// RPC client options
RPCConnect string `short:"c" long:"rpcconnect" description:"Hostname/IP and port of btcd RPC server to connect to (default localhost:8334, testnet: localhost:18334, simnet: localhost:18556)"`
CAFile string `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with btcd"`
DisableClientTLS bool `long:"noclienttls" description:"Disable TLS for the RPC client -- NOTE: This is only allowed if the RPC client is connecting to localhost"`
BtcdUsername string `long:"btcdusername" description:"Username for btcd authentication"`
BtcdPassword string `long:"btcdpassword" default-mask:"-" description:"Password for btcd authentication"`
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
RPCConnect string `short:"c" long:"rpcconnect" description:"Hostname/IP and port of btcd RPC server to connect to (default localhost:8334, testnet: localhost:18334, simnet: localhost:18556)"`
CAFile *cfgutil.ExplicitString `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with btcd"`
DisableClientTLS bool `long:"noclienttls" description:"Disable TLS for the RPC client -- NOTE: This is only allowed if the RPC client is connecting to localhost"`
BtcdUsername string `long:"btcdusername" description:"Username for btcd authentication"`
BtcdPassword string `long:"btcdpassword" default-mask:"-" description:"Password for btcd authentication"`
Proxy string `long:"proxy" description:"Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)"`
ProxyUser string `long:"proxyuser" description:"Username for proxy server"`
ProxyPass string `long:"proxypass" default-mask:"-" description:"Password for proxy server"`
// RPC server options
//
@ -78,15 +78,15 @@ type config struct {
//
// Usernames can also be used for the consensus RPC client, so they
// aren't considered legacy.
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"`
OneTimeTLSKey bool `long:"onetimetlskey" description:"Generate a new TLS certpair at startup, but only write the certificate to disk"`
DisableServerTLS bool `long:"noservertls" description:"Disable TLS for the RPC server -- NOTE: This is only allowed if the RPC server is bound to localhost"`
LegacyRPCListeners []string `long:"rpclisten" description:"Listen for legacy RPC connections on this interface/port (default port: 8332, testnet: 18332, simnet: 18554)"`
LegacyRPCMaxClients int64 `long:"rpcmaxclients" description:"Max number of legacy RPC clients for standard connections"`
LegacyRPCMaxWebsockets int64 `long:"rpcmaxwebsockets" description:"Max number of legacy RPC websocket connections"`
Username string `short:"u" long:"username" description:"Username for legacy RPC and btcd authentication (if btcdusername is unset)"`
Password string `short:"P" long:"password" default-mask:"-" description:"Password for legacy RPC and btcd authentication (if btcdpassword is unset)"`
RPCCert *cfgutil.ExplicitString `long:"rpccert" description:"File containing the certificate file"`
RPCKey *cfgutil.ExplicitString `long:"rpckey" description:"File containing the certificate key"`
OneTimeTLSKey bool `long:"onetimetlskey" description:"Generate a new TLS certpair at startup, but only write the certificate to disk"`
DisableServerTLS bool `long:"noservertls" description:"Disable TLS for the RPC server -- NOTE: This is only allowed if the RPC server is bound to localhost"`
LegacyRPCListeners []string `long:"rpclisten" description:"Listen for legacy RPC connections on this interface/port (default port: 8332, testnet: 18332, simnet: 18554)"`
LegacyRPCMaxClients int64 `long:"rpcmaxclients" description:"Max number of legacy RPC clients for standard connections"`
LegacyRPCMaxWebsockets int64 `long:"rpcmaxwebsockets" description:"Max number of legacy RPC websocket connections"`
Username string `short:"u" long:"username" description:"Username for legacy RPC and btcd authentication (if btcdusername is unset)"`
Password string `short:"P" long:"password" default-mask:"-" description:"Password for legacy RPC and btcd authentication (if btcdpassword is unset)"`
// EXPERIMENTAL RPC server options
//
@ -95,7 +95,7 @@ type config struct {
ExperimentalRPCListeners []string `long:"experimentalrpclisten" description:"Listen for RPC connections on this interface/port"`
// Deprecated options
DataDir string `short:"b" long:"datadir" default-mask:"-" description:"DEPRECATED -- use appdata instead"`
DataDir *cfgutil.ExplicitString `short:"b" long:"datadir" default-mask:"-" description:"DEPRECATED -- use appdata instead"`
}
// cleanAndExpandPath expands environement variables and leading ~ in the
@ -247,15 +247,16 @@ func loadConfig() (*config, []string, error) {
// Default config.
cfg := config{
DebugLevel: defaultLogLevel,
ConfigFile: defaultConfigFile,
AppDataDir: defaultAppDataDir,
ConfigFile: cfgutil.NewExplicitString(defaultConfigFile),
AppDataDir: cfgutil.NewExplicitString(defaultAppDataDir),
LogDir: defaultLogDir,
WalletPass: wallet.InsecurePubPassphrase,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
CAFile: cfgutil.NewExplicitString(""),
RPCKey: cfgutil.NewExplicitString(defaultRPCKeyFile),
RPCCert: cfgutil.NewExplicitString(defaultRPCCertFile),
LegacyRPCMaxClients: defaultRPCMaxClients,
LegacyRPCMaxWebsockets: defaultRPCMaxWebsockets,
DataDir: defaultAppDataDir,
DataDir: cfgutil.NewExplicitString(defaultAppDataDir),
}
// Pre-parse the command line options to see if an alternative config
@ -283,17 +284,17 @@ func loadConfig() (*config, []string, error) {
// Load additional config from file.
var configFileError error
parser := flags.NewParser(&cfg, flags.Default)
configFilePath := preCfg.ConfigFile
if configFilePath == defaultConfigFile {
appDataDir := preCfg.AppDataDir
if appDataDir == defaultAppDataDir && preCfg.DataDir != defaultAppDataDir {
appDataDir = cleanAndExpandPath(preCfg.DataDir)
configFilePath := preCfg.ConfigFile.Value
if preCfg.ConfigFile.ExplicitlySet() {
configFilePath = cleanAndExpandPath(configFilePath)
} else {
appDataDir := preCfg.AppDataDir.Value
if !preCfg.AppDataDir.ExplicitlySet() && preCfg.DataDir.ExplicitlySet() {
appDataDir = cleanAndExpandPath(preCfg.DataDir.Value)
}
if appDataDir != defaultAppDataDir {
configFilePath = filepath.Join(appDataDir, defaultConfigFilename)
}
} else {
configFilePath = cleanAndExpandPath(configFilePath)
}
err = flags.NewIniParser(parser).ParseFile(configFilePath)
if err != nil {
@ -323,24 +324,24 @@ func loadConfig() (*config, []string, error) {
// Check deprecated aliases. The new options receive priority when both
// are changed from the default.
if cfg.DataDir != defaultAppDataDir {
if cfg.DataDir.ExplicitlySet() {
fmt.Fprintln(os.Stderr, "datadir option has been replaced by "+
"appdata -- please update your config")
if cfg.AppDataDir == defaultAppDataDir {
cfg.AppDataDir = cfg.DataDir
if !cfg.AppDataDir.ExplicitlySet() {
cfg.AppDataDir.Value = cfg.DataDir.Value
}
}
// If an alternate data directory was specified, and paths with defaults
// relative to the data dir are unchanged, modify each path to be
// relative to the new data dir.
if cfg.AppDataDir != defaultAppDataDir {
cfg.AppDataDir = cleanAndExpandPath(cfg.AppDataDir)
if cfg.RPCKey == defaultRPCKeyFile {
cfg.RPCKey = filepath.Join(cfg.AppDataDir, "rpc.key")
if cfg.AppDataDir.ExplicitlySet() {
cfg.AppDataDir.Value = cleanAndExpandPath(cfg.AppDataDir.Value)
if !cfg.RPCKey.ExplicitlySet() {
cfg.RPCKey.Value = filepath.Join(cfg.AppDataDir.Value, "rpc.key")
}
if cfg.RPCCert == defaultRPCCertFile {
cfg.RPCCert = filepath.Join(cfg.AppDataDir, "rpc.cert")
if !cfg.RPCCert.ExplicitlySet() {
cfg.RPCCert.Value = filepath.Join(cfg.AppDataDir.Value, "rpc.cert")
}
}
@ -389,7 +390,7 @@ func loadConfig() (*config, []string, error) {
// Exit if you try to use a simulation wallet with a standard
// data directory.
if cfg.AppDataDir == defaultAppDataDir && cfg.CreateTemp {
if (!cfg.AppDataDir.ExplicitlySet() || !cfg.DataDir.ExplicitlySet()) && cfg.CreateTemp {
fmt.Fprintln(os.Stderr, "Tried to create a temporary simulation "+
"wallet, but failed to specify data directory!")
os.Exit(0)
@ -404,7 +405,7 @@ func loadConfig() (*config, []string, error) {
}
// Ensure the wallet exists or create it when the create flag is set.
netDir := networkDir(cfg.AppDataDir, activeNet.Params)
netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
dbPath := filepath.Join(netDir, walletDbName)
if cfg.CreateTemp && cfg.Create {
@ -519,12 +520,12 @@ func loadConfig() (*config, []string, error) {
}
} else {
// If CAFile is unset, choose either the copy or local btcd cert.
if cfg.CAFile == "" {
cfg.CAFile = filepath.Join(cfg.AppDataDir, defaultCAFilename)
if !cfg.CAFile.ExplicitlySet() {
cfg.CAFile.Value = filepath.Join(cfg.AppDataDir.Value, defaultCAFilename)
// If the CA copy does not exist, check if we're connecting to
// a local btcd and switch to its RPC cert if it exists.
certExists, err := cfgutil.FileExists(cfg.CAFile)
certExists, err := cfgutil.FileExists(cfg.CAFile.Value)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
@ -538,7 +539,7 @@ func loadConfig() (*config, []string, error) {
return nil, nil, err
}
if btcdCertExists {
cfg.CAFile = btcdDefaultCAFile
cfg.CAFile.Value = btcdDefaultCAFile
}
}
}
@ -625,9 +626,9 @@ func loadConfig() (*config, []string, error) {
}
// Expand environment variable and leading ~ for filepaths.
cfg.CAFile = cleanAndExpandPath(cfg.CAFile)
cfg.RPCCert = cleanAndExpandPath(cfg.RPCCert)
cfg.RPCKey = cleanAndExpandPath(cfg.RPCKey)
cfg.CAFile.Value = cleanAndExpandPath(cfg.CAFile.Value)
cfg.RPCCert.Value = cleanAndExpandPath(cfg.RPCCert.Value)
cfg.RPCKey.Value = cleanAndExpandPath(cfg.RPCKey.Value)
// If the btcd username or password are unset, use the same auth as for
// the client. The two settings were previously shared for btcd and

View file

@ -0,0 +1,36 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package cfgutil
// ExplicitString is a string value implementing the flags.Marshaler and
// flags.Unmarshaler interfaces so it may be used as a config struct field. It
// records whether the value was explicitly set by the flags package. This is
// useful when behavior must be modified depending on whether a flag was set by
// the user or left as a default. Without recording this, it would be
// impossible to determine whether flag with a default value was unmodified or
// explicitly set to the default.
type ExplicitString struct {
Value string
explicitlySet bool
}
// NewExplicitString creates a string flag with the provided default value.
func NewExplicitString(defaultValue string) *ExplicitString {
return &ExplicitString{Value: defaultValue, explicitlySet: false}
}
// ExplicitlySet returns whether the flag was explicitly set through the
// flags.Unmarshaler interface.
func (e *ExplicitString) ExplicitlySet() bool { return e.explicitlySet }
// MarshalFlag implements the flags.Marshaler interface.
func (e *ExplicitString) MarshalFlag() (string, error) { return e.Value, nil }
// UnmarshalFlag implements the flags.Unmarshaler interface.
func (e *ExplicitString) UnmarshalFlag(value string) error {
e.Value = value
e.explicitlySet = true
return nil
}

View file

@ -35,19 +35,19 @@ func openRPCKeyPair() (tls.Certificate, error) {
// acceptable if the previous execution used a one time TLS key.
// Otherwise, both the cert and key should be read from disk. If the
// cert is missing, the read error will occur in LoadX509KeyPair.
_, e := os.Stat(cfg.RPCKey)
_, e := os.Stat(cfg.RPCKey.Value)
keyExists := !os.IsNotExist(e)
switch {
case cfg.OneTimeTLSKey && keyExists:
err := fmt.Errorf("one time TLS keys are enabled, but TLS key "+
"`%s` already exists", cfg.RPCKey)
"`%s` already exists", cfg.RPCKey.Value)
return tls.Certificate{}, err
case cfg.OneTimeTLSKey:
return generateRPCKeyPair(false)
case !keyExists:
return generateRPCKeyPair(true)
default:
return tls.LoadX509KeyPair(cfg.RPCCert, cfg.RPCKey)
return tls.LoadX509KeyPair(cfg.RPCCert.Value, cfg.RPCKey.Value)
}
}
@ -58,8 +58,8 @@ func generateRPCKeyPair(writeKey bool) (tls.Certificate, error) {
log.Infof("Generating TLS certificates...")
// Create directories for cert and key files if they do not yet exist.
certDir, _ := filepath.Split(cfg.RPCCert)
keyDir, _ := filepath.Split(cfg.RPCKey)
certDir, _ := filepath.Split(cfg.RPCCert.Value)
keyDir, _ := filepath.Split(cfg.RPCKey.Value)
err := os.MkdirAll(certDir, 0700)
if err != nil {
return tls.Certificate{}, err
@ -82,14 +82,14 @@ func generateRPCKeyPair(writeKey bool) (tls.Certificate, error) {
}
// Write cert and (potentially) the key files.
err = ioutil.WriteFile(cfg.RPCCert, cert, 0600)
err = ioutil.WriteFile(cfg.RPCCert.Value, cert, 0600)
if err != nil {
return tls.Certificate{}, err
}
if writeKey {
err = ioutil.WriteFile(cfg.RPCKey, key, 0600)
err = ioutil.WriteFile(cfg.RPCKey.Value, key, 0600)
if err != nil {
rmErr := os.Remove(cfg.RPCCert)
rmErr := os.Remove(cfg.RPCCert.Value)
if rmErr != nil {
log.Warnf("Cannot remove written certificates: %v",
rmErr)

View file

@ -99,13 +99,13 @@ func convertLegacyKeystore(legacyKeyStore *keystore.Store, manager *waddrmgr.Man
// and generates the wallet accordingly. The new wallet will reside at the
// provided path.
func createWallet(cfg *config) error {
dbDir := networkDir(cfg.AppDataDir, activeNet.Params)
dbDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
loader := wallet.NewLoader(activeNet.Params, dbDir)
// When there is a legacy keystore, open it now to ensure any errors
// don't end up exiting the process after the user has spent time
// entering a bunch of information.
netDir := networkDir(cfg.AppDataDir, activeNet.Params)
netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
keystorePath := filepath.Join(netDir, keystore.Filename)
var legacyKeyStore *keystore.Store
_, err := os.Stat(keystorePath)
@ -207,7 +207,7 @@ func createSimulationWallet(cfg *config) error {
// Public passphrase is the default.
pubPass := []byte(wallet.InsecurePubPassphrase)
netDir := networkDir(cfg.AppDataDir, activeNet.Params)
netDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
// Create the wallet.
dbPath := filepath.Join(netDir, walletDbName)