github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/config/config.go (about)

     1  // Package config reads, writes and edits the config file and deals with command line flags
     2  package config
     3  
     4  import (
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"log"
    10  	mathrand "math/rand"
    11  	"os"
    12  	"path/filepath"
    13  	"regexp"
    14  	"runtime"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/mitchellh/go-homedir"
    19  
    20  	"github.com/rclone/rclone/fs"
    21  	"github.com/rclone/rclone/fs/cache"
    22  	"github.com/rclone/rclone/fs/config/configmap"
    23  	"github.com/rclone/rclone/fs/config/obscure"
    24  	"github.com/rclone/rclone/fs/fspath"
    25  	"github.com/rclone/rclone/fs/rc"
    26  	"github.com/rclone/rclone/lib/file"
    27  	"github.com/rclone/rclone/lib/random"
    28  )
    29  
    30  const (
    31  	configFileName       = "rclone.conf"
    32  	hiddenConfigFileName = "." + configFileName
    33  	noConfigFile         = "notfound"
    34  
    35  	// ConfigToken is the key used to store the token under
    36  	ConfigToken = "token"
    37  
    38  	// ConfigClientID is the config key used to store the client id
    39  	ConfigClientID = "client_id"
    40  
    41  	// ConfigClientSecret is the config key used to store the client secret
    42  	ConfigClientSecret = "client_secret"
    43  
    44  	// ConfigAuthURL is the config key used to store the auth server endpoint
    45  	ConfigAuthURL = "auth_url"
    46  
    47  	// ConfigTokenURL is the config key used to store the token server endpoint
    48  	ConfigTokenURL = "token_url"
    49  
    50  	// ConfigEncoding is the config key to change the encoding for a backend
    51  	ConfigEncoding = "encoding"
    52  
    53  	// ConfigEncodingHelp is the help for ConfigEncoding
    54  	ConfigEncodingHelp = "The encoding for the backend.\n\nSee the [encoding section in the overview](/overview/#encoding) for more info."
    55  
    56  	// ConfigAuthorize indicates that we just want "rclone authorize"
    57  	ConfigAuthorize = "config_authorize"
    58  
    59  	// ConfigAuthNoBrowser indicates that we do not want to open browser
    60  	ConfigAuthNoBrowser = "config_auth_no_browser"
    61  
    62  	// ConfigTemplate is the template content to be used in the authorization webserver
    63  	ConfigTemplate = "config_template"
    64  
    65  	// ConfigTemplateFile is the path to a template file to read into the value of `ConfigTemplate` above
    66  	ConfigTemplateFile = "config_template_file"
    67  )
    68  
    69  // Storage defines an interface for loading and saving config to
    70  // persistent storage. Rclone provides a default implementation to
    71  // load and save to a config file when this is imported
    72  //
    73  // import "github.com/rclone/rclone/fs/config/configfile"
    74  // configfile.Install()
    75  type Storage interface {
    76  	// GetSectionList returns a slice of strings with names for all the
    77  	// sections
    78  	GetSectionList() []string
    79  
    80  	// HasSection returns true if section exists in the config file
    81  	HasSection(section string) bool
    82  
    83  	// DeleteSection removes the named section and all config from the
    84  	// config file
    85  	DeleteSection(section string)
    86  
    87  	// GetKeyList returns the keys in this section
    88  	GetKeyList(section string) []string
    89  
    90  	// GetValue returns the key in section with a found flag
    91  	GetValue(section string, key string) (value string, found bool)
    92  
    93  	// SetValue sets the value under key in section
    94  	SetValue(section string, key string, value string)
    95  
    96  	// DeleteKey removes the key under section
    97  	DeleteKey(section string, key string) bool
    98  
    99  	// Load the config from permanent storage
   100  	Load() error
   101  
   102  	// Save the config to permanent storage
   103  	Save() error
   104  
   105  	// Serialize the config into a string
   106  	Serialize() (string, error)
   107  }
   108  
   109  // Global
   110  var (
   111  	// Password can be used to configure the random password generator
   112  	Password = random.Password
   113  )
   114  
   115  var (
   116  	configPath string
   117  	cacheDir   string
   118  	data       Storage
   119  	dataLoaded bool
   120  )
   121  
   122  func init() {
   123  	// Set the function pointers up in fs
   124  	fs.ConfigFileGet = FileGetFlag
   125  	fs.ConfigFileSet = SetValueAndSave
   126  	fs.ConfigFileHasSection = func(section string) bool {
   127  		return LoadedData().HasSection(section)
   128  	}
   129  	configPath = makeConfigPath()
   130  	cacheDir = makeCacheDir() // Has fallback to tempDir, so set that first
   131  	data = newDefaultStorage()
   132  }
   133  
   134  // Join directory with filename, and check if exists
   135  func findFile(dir string, name string) string {
   136  	path := filepath.Join(dir, name)
   137  	if _, err := os.Stat(path); err != nil {
   138  		return ""
   139  	}
   140  	return path
   141  }
   142  
   143  // Find current user's home directory
   144  func findHomeDir() (string, error) {
   145  	path, err := homedir.Dir()
   146  	if err != nil {
   147  		fs.Debugf(nil, "Home directory lookup failed and cannot be used as configuration location: %v", err)
   148  	} else if path == "" {
   149  		// On Unix homedir return success but empty string for user with empty home configured in passwd file
   150  		fs.Debugf(nil, "Home directory not defined and cannot be used as configuration location")
   151  	}
   152  	return path, err
   153  }
   154  
   155  // Find rclone executable directory and look for existing rclone.conf there
   156  // (<rclone_exe_dir>/rclone.conf)
   157  func findLocalConfig() (configDir string, configFile string) {
   158  	if exePath, err := os.Executable(); err == nil {
   159  		configDir = filepath.Dir(exePath)
   160  		configFile = findFile(configDir, configFileName)
   161  	}
   162  	return
   163  }
   164  
   165  // Get path to Windows AppData config subdirectory for rclone and look for existing rclone.conf there
   166  // ($AppData/rclone/rclone.conf)
   167  func findAppDataConfig() (configDir string, configFile string) {
   168  	if appDataDir := os.Getenv("APPDATA"); appDataDir != "" {
   169  		configDir = filepath.Join(appDataDir, "rclone")
   170  		configFile = findFile(configDir, configFileName)
   171  	} else {
   172  		fs.Debugf(nil, "Environment variable APPDATA is not defined and cannot be used as configuration location")
   173  	}
   174  	return
   175  }
   176  
   177  // Get path to XDG config subdirectory for rclone and look for existing rclone.conf there
   178  // (see XDG Base Directory specification: https://specifications.freedesktop.org/basedir-spec/latest/).
   179  // ($XDG_CONFIG_HOME\rclone\rclone.conf)
   180  func findXDGConfig() (configDir string, configFile string) {
   181  	if xdgConfigDir := os.Getenv("XDG_CONFIG_HOME"); xdgConfigDir != "" {
   182  		configDir = filepath.Join(xdgConfigDir, "rclone")
   183  		configFile = findFile(configDir, configFileName)
   184  	}
   185  	return
   186  }
   187  
   188  // Get path to .config subdirectory for rclone and look for existing rclone.conf there
   189  // (~/.config/rclone/rclone.conf)
   190  func findDotConfigConfig(home string) (configDir string, configFile string) {
   191  	if home != "" {
   192  		configDir = filepath.Join(home, ".config", "rclone")
   193  		configFile = findFile(configDir, configFileName)
   194  	}
   195  	return
   196  }
   197  
   198  // Look for existing .rclone.conf (legacy hidden filename) in root of user's home directory
   199  // (~/.rclone.conf)
   200  func findOldHomeConfig(home string) (configDir string, configFile string) {
   201  	if home != "" {
   202  		configDir = home
   203  		configFile = findFile(home, hiddenConfigFileName)
   204  	}
   205  	return
   206  }
   207  
   208  // Return the path to the configuration file
   209  func makeConfigPath() string {
   210  	// Look for existing rclone.conf in prioritized list of known locations
   211  	// Also get configuration directory to use for new config file when no existing is found.
   212  	var (
   213  		configFile        string
   214  		configDir         string
   215  		primaryConfigDir  string
   216  		fallbackConfigDir string
   217  	)
   218  	// <rclone_exe_dir>/rclone.conf
   219  	if _, configFile = findLocalConfig(); configFile != "" {
   220  		return configFile
   221  	}
   222  	// Windows: $AppData/rclone/rclone.conf
   223  	// This is also the default location for new config when no existing is found
   224  	if runtime.GOOS == "windows" {
   225  		if primaryConfigDir, configFile = findAppDataConfig(); configFile != "" {
   226  			return configFile
   227  		}
   228  	}
   229  	// $XDG_CONFIG_HOME/rclone/rclone.conf
   230  	// Also looking for this on Windows, for backwards compatibility reasons.
   231  	if configDir, configFile = findXDGConfig(); configFile != "" {
   232  		return configFile
   233  	}
   234  	if runtime.GOOS != "windows" {
   235  		// On Unix this is also the default location for new config when no existing is found
   236  		primaryConfigDir = configDir
   237  	}
   238  	// ~/.config/rclone/rclone.conf
   239  	// This is also the fallback location for new config
   240  	// (when $AppData on Windows and $XDG_CONFIG_HOME on Unix is not defined)
   241  	homeDir, homeDirErr := findHomeDir()
   242  	if fallbackConfigDir, configFile = findDotConfigConfig(homeDir); configFile != "" {
   243  		return configFile
   244  	}
   245  	// ~/.rclone.conf
   246  	if _, configFile = findOldHomeConfig(homeDir); configFile != "" {
   247  		return configFile
   248  	}
   249  
   250  	// No existing config file found, prepare proper default for a new one.
   251  	// But first check if user supplied a --config variable or environment
   252  	// variable, since then we skip actually trying to create the default
   253  	// and report any errors related to it (we can't use pflag for this because
   254  	// it isn't initialised yet so we search the command line manually).
   255  	_, configSupplied := os.LookupEnv("RCLONE_CONFIG")
   256  	if !configSupplied {
   257  		for _, item := range os.Args {
   258  			if item == "--config" || strings.HasPrefix(item, "--config=") {
   259  				configSupplied = true
   260  				break
   261  			}
   262  		}
   263  	}
   264  	// If we found a configuration directory to be used for new config during search
   265  	// above, then create it to be ready for rclone.conf file to be written into it
   266  	// later, and also as a test of permissions to use fallback if not even able to
   267  	// create the directory.
   268  	if primaryConfigDir != "" {
   269  		configDir = primaryConfigDir
   270  	} else if fallbackConfigDir != "" {
   271  		configDir = fallbackConfigDir
   272  	} else {
   273  		configDir = ""
   274  	}
   275  	if configDir != "" {
   276  		configFile = filepath.Join(configDir, configFileName)
   277  		if configSupplied {
   278  			// User supplied custom config option, just return the default path
   279  			// as is without creating any directories, since it will not be used
   280  			// anyway and we don't want to unnecessarily create empty directory.
   281  			return configFile
   282  		}
   283  		var mkdirErr error
   284  		if mkdirErr = file.MkdirAll(configDir, os.ModePerm); mkdirErr == nil {
   285  			return configFile
   286  		}
   287  		// Problem: Try a fallback location. If we did find a home directory then
   288  		// just assume file .rclone.conf (legacy hidden filename) can be written in
   289  		// its root (~/.rclone.conf).
   290  		if homeDir != "" {
   291  			fs.Debugf(nil, "Configuration directory could not be created and will not be used: %v", mkdirErr)
   292  			return filepath.Join(homeDir, hiddenConfigFileName)
   293  		}
   294  		if !configSupplied {
   295  			fs.Errorf(nil, "Couldn't find home directory nor create configuration directory: %v", mkdirErr)
   296  		}
   297  	} else if !configSupplied {
   298  		if homeDirErr != nil {
   299  			fs.Errorf(nil, "Couldn't find configuration directory nor home directory: %v", homeDirErr)
   300  		} else {
   301  			fs.Errorf(nil, "Couldn't find configuration directory nor home directory")
   302  		}
   303  	}
   304  	// No known location that can be used: Did possibly find a configDir
   305  	// (XDG_CONFIG_HOME or APPDATA) which couldn't be created, but in any case
   306  	// did not find a home directory!
   307  	// Report it as an error, and return as last resort the path relative to current
   308  	// working directory, of .rclone.conf (legacy hidden filename).
   309  	if !configSupplied {
   310  		fs.Errorf(nil, "Defaulting to storing config in current directory.")
   311  		fs.Errorf(nil, "Use --config flag to workaround.")
   312  	}
   313  	return hiddenConfigFileName
   314  }
   315  
   316  // GetConfigPath returns the current config file path
   317  func GetConfigPath() string {
   318  	return configPath
   319  }
   320  
   321  // SetConfigPath sets new config file path
   322  //
   323  // Checks for empty string, os null device, or special path, all of which indicates in-memory config.
   324  func SetConfigPath(path string) (err error) {
   325  	var cfgPath string
   326  	if path == "" || path == os.DevNull {
   327  		cfgPath = ""
   328  	} else if filepath.Base(path) == noConfigFile {
   329  		cfgPath = ""
   330  	} else if err = file.IsReserved(path); err != nil {
   331  		return err
   332  	} else if cfgPath, err = filepath.Abs(path); err != nil {
   333  		return err
   334  	}
   335  	configPath = cfgPath
   336  	return nil
   337  }
   338  
   339  // SetData sets new config file storage
   340  func SetData(newData Storage) {
   341  	// If no config file, use in-memory config (which is the default)
   342  	if configPath == "" {
   343  		return
   344  	}
   345  	data = newData
   346  	dataLoaded = false
   347  }
   348  
   349  // Data returns current config file storage
   350  func Data() Storage {
   351  	return data
   352  }
   353  
   354  // LoadedData ensures the config file storage is loaded and returns it
   355  func LoadedData() Storage {
   356  	if !dataLoaded {
   357  		// Set RCLONE_CONFIG_DIR for backend config and subprocesses
   358  		// If empty configPath (in-memory only) the value will be "."
   359  		_ = os.Setenv("RCLONE_CONFIG_DIR", filepath.Dir(configPath))
   360  		// Load configuration from file (or initialize sensible default if no file or error)
   361  		if err := data.Load(); err == nil {
   362  			fs.Debugf(nil, "Using config file from %q", configPath)
   363  			dataLoaded = true
   364  		} else if err == ErrorConfigFileNotFound {
   365  			if configPath == "" {
   366  				fs.Debugf(nil, "Config is memory-only - using defaults")
   367  			} else {
   368  				fs.Logf(nil, "Config file %q not found - using defaults", configPath)
   369  			}
   370  			dataLoaded = true
   371  		} else {
   372  			log.Fatalf("Failed to load config file %q: %v", configPath, err)
   373  		}
   374  	}
   375  	return data
   376  }
   377  
   378  // ErrorConfigFileNotFound is returned when the config file is not found
   379  var ErrorConfigFileNotFound = errors.New("config file not found")
   380  
   381  // SaveConfig calling function which saves configuration file.
   382  // if SaveConfig returns error trying again after sleep.
   383  func SaveConfig() {
   384  	ctx := context.Background()
   385  	ci := fs.GetConfig(ctx)
   386  	var err error
   387  	for i := 0; i < ci.LowLevelRetries+1; i++ {
   388  		if err = LoadedData().Save(); err == nil {
   389  			return
   390  		}
   391  		waitingTimeMs := mathrand.Intn(1000)
   392  		time.Sleep(time.Duration(waitingTimeMs) * time.Millisecond)
   393  	}
   394  	fs.Errorf(nil, "Failed to save config after %d tries: %v", ci.LowLevelRetries, err)
   395  }
   396  
   397  // SetValueAndSave sets the key to the value and saves just that
   398  // value in the config file.  It loads the old config file in from
   399  // disk first and overwrites the given value only.
   400  func SetValueAndSave(name, key, value string) error {
   401  	// Set the value in config in case we fail to reload it
   402  	LoadedData().SetValue(name, key, value)
   403  	// Save it again
   404  	SaveConfig()
   405  	return nil
   406  }
   407  
   408  // getWithDefault gets key out of section name returning defaultValue if not
   409  // found.
   410  func getWithDefault(name, key, defaultValue string) string {
   411  	value, found := LoadedData().GetValue(name, key)
   412  	if !found {
   413  		return defaultValue
   414  	}
   415  	return value
   416  }
   417  
   418  // UpdateRemoteOpt configures the remote update
   419  type UpdateRemoteOpt struct {
   420  	// Treat all passwords as plain that need obscuring
   421  	Obscure bool `json:"obscure"`
   422  	// Treat all passwords as obscured
   423  	NoObscure bool `json:"noObscure"`
   424  	// Don't interact with the user - return questions
   425  	NonInteractive bool `json:"nonInteractive"`
   426  	// If set then supply state and result parameters to continue the process
   427  	Continue bool `json:"continue"`
   428  	// If set then ask all the questions, not just the post config questions
   429  	All bool `json:"all"`
   430  	// State to restart with - used with Continue
   431  	State string `json:"state"`
   432  	// Result to return - used with Continue
   433  	Result string `json:"result"`
   434  	// If set then edit existing values
   435  	Edit bool `json:"edit"`
   436  }
   437  
   438  func updateRemote(ctx context.Context, name string, keyValues rc.Params, opt UpdateRemoteOpt) (out *fs.ConfigOut, err error) {
   439  	if opt.Obscure && opt.NoObscure {
   440  		return nil, errors.New("can't use --obscure and --no-obscure together")
   441  	}
   442  
   443  	err = fspath.CheckConfigName(name)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	interactive := !(opt.NonInteractive || opt.Continue)
   448  	if interactive && !opt.All {
   449  		ctx = suppressConfirm(ctx)
   450  	}
   451  
   452  	fsType := FileGet(name, "type")
   453  	if fsType == "" {
   454  		return nil, errors.New("couldn't find type field in config")
   455  	}
   456  
   457  	ri, err := fs.Find(fsType)
   458  	if err != nil {
   459  		return nil, fmt.Errorf("couldn't find backend for type %q", fsType)
   460  	}
   461  
   462  	// Work out which options need to be obscured
   463  	needsObscure := map[string]struct{}{}
   464  	if !opt.NoObscure {
   465  		for _, option := range ri.Options {
   466  			if option.IsPassword {
   467  				needsObscure[option.Name] = struct{}{}
   468  			}
   469  		}
   470  	}
   471  
   472  	choices := configmap.Simple{}
   473  	m := fs.ConfigMap(ri, name, nil)
   474  
   475  	// Set the config
   476  	for k, v := range keyValues {
   477  		vStr := fmt.Sprint(v)
   478  		if strings.ContainsAny(k, "\n\r") || strings.ContainsAny(vStr, "\n\r") {
   479  			return nil, fmt.Errorf("update remote: invalid key or value contains \\n or \\r")
   480  		}
   481  		// Obscure parameter if necessary
   482  		if _, ok := needsObscure[k]; ok {
   483  			_, err := obscure.Reveal(vStr)
   484  			if err != nil || opt.Obscure {
   485  				// If error => not already obscured, so obscure it
   486  				// or we are forced to obscure
   487  				vStr, err = obscure.Obscure(vStr)
   488  				if err != nil {
   489  					return nil, fmt.Errorf("update remote: obscure failed: %w", err)
   490  				}
   491  			}
   492  		}
   493  		choices.Set(k, vStr)
   494  		if !strings.HasPrefix(k, fs.ConfigKeyEphemeralPrefix) {
   495  			m.Set(k, vStr)
   496  		}
   497  	}
   498  	if opt.Edit {
   499  		choices[fs.ConfigEdit] = "true"
   500  	}
   501  
   502  	if interactive {
   503  		var state = ""
   504  		if opt.All {
   505  			state = fs.ConfigAll
   506  		}
   507  		err = backendConfig(ctx, name, m, ri, choices, state)
   508  	} else {
   509  		// Start the config state machine
   510  		in := fs.ConfigIn{
   511  			State:  opt.State,
   512  			Result: opt.Result,
   513  		}
   514  		if in.State == "" && opt.All {
   515  			in.State = fs.ConfigAll
   516  		}
   517  		out, err = fs.BackendConfig(ctx, name, m, ri, choices, in)
   518  	}
   519  	if err != nil {
   520  		return nil, err
   521  	}
   522  	SaveConfig()
   523  	cache.ClearConfig(name) // remove any remotes based on this config from the cache
   524  	return out, nil
   525  }
   526  
   527  // UpdateRemote adds the keyValues passed in to the remote of name.
   528  // keyValues should be key, value pairs.
   529  func UpdateRemote(ctx context.Context, name string, keyValues rc.Params, opt UpdateRemoteOpt) (out *fs.ConfigOut, err error) {
   530  	opt.Edit = true
   531  	return updateRemote(ctx, name, keyValues, opt)
   532  }
   533  
   534  // CreateRemote creates a new remote with name, type and a list of
   535  // parameters which are key, value pairs.  If update is set then it
   536  // adds the new keys rather than replacing all of them.
   537  func CreateRemote(ctx context.Context, name string, Type string, keyValues rc.Params, opts UpdateRemoteOpt) (out *fs.ConfigOut, err error) {
   538  	err = fspath.CheckConfigName(name)
   539  	if err != nil {
   540  		return nil, err
   541  	}
   542  	if !opts.Continue {
   543  		// Delete the old config if it exists
   544  		LoadedData().DeleteSection(name)
   545  		// Set the type
   546  		LoadedData().SetValue(name, "type", Type)
   547  	}
   548  	// Set the remaining values
   549  	return UpdateRemote(ctx, name, keyValues, opts)
   550  }
   551  
   552  // PasswordRemote adds the keyValues passed in to the remote of name.
   553  // keyValues should be key, value pairs.
   554  func PasswordRemote(ctx context.Context, name string, keyValues rc.Params) error {
   555  	ctx = suppressConfirm(ctx)
   556  	err := fspath.CheckConfigName(name)
   557  	if err != nil {
   558  		return err
   559  	}
   560  	for k, v := range keyValues {
   561  		keyValues[k] = obscure.MustObscure(fmt.Sprint(v))
   562  	}
   563  	_, err = UpdateRemote(ctx, name, keyValues, UpdateRemoteOpt{
   564  		NoObscure: true,
   565  	})
   566  	return err
   567  }
   568  
   569  // JSONListProviders prints all the providers and options in JSON format
   570  func JSONListProviders() error {
   571  	b, err := json.MarshalIndent(fs.Registry, "", "    ")
   572  	if err != nil {
   573  		return fmt.Errorf("failed to marshal examples: %w", err)
   574  	}
   575  	_, err = os.Stdout.Write(b)
   576  	if err != nil {
   577  		return fmt.Errorf("failed to write providers list: %w", err)
   578  	}
   579  	return nil
   580  }
   581  
   582  // fsOption returns an Option describing the possible remotes
   583  func fsOption() *fs.Option {
   584  	o := &fs.Option{
   585  		Name:     "Storage",
   586  		Help:     "Type of storage to configure.",
   587  		Default:  "",
   588  		Required: true,
   589  	}
   590  	for _, item := range fs.Registry {
   591  		if item.Hide {
   592  			continue
   593  		}
   594  		example := fs.OptionExample{
   595  			Value: item.Name,
   596  			Help:  item.Description,
   597  		}
   598  		o.Examples = append(o.Examples, example)
   599  	}
   600  	o.Examples.Sort()
   601  	return o
   602  }
   603  
   604  // FileGetFlag gets the config key under section returning the
   605  // the value and true if found and or ("", false) otherwise
   606  func FileGetFlag(section, key string) (string, bool) {
   607  	return LoadedData().GetValue(section, key)
   608  }
   609  
   610  // FileGet gets the config key under section returning the default if not set.
   611  //
   612  // It looks up defaults in the environment if they are present
   613  func FileGet(section, key string) string {
   614  	var defaultVal string
   615  	envKey := fs.ConfigToEnv(section, key)
   616  	newValue, found := os.LookupEnv(envKey)
   617  	if found {
   618  		defaultVal = newValue
   619  	}
   620  	return getWithDefault(section, key, defaultVal)
   621  }
   622  
   623  // FileSet sets the key in section to value.  It doesn't save
   624  // the config file.
   625  func FileSet(section, key, value string) {
   626  	if value != "" {
   627  		LoadedData().SetValue(section, key, value)
   628  	} else {
   629  		FileDeleteKey(section, key)
   630  	}
   631  }
   632  
   633  // FileDeleteKey deletes the config key in the config file.
   634  // It returns true if the key was deleted,
   635  // or returns false if the section or key didn't exist.
   636  func FileDeleteKey(section, key string) bool {
   637  	return LoadedData().DeleteKey(section, key)
   638  }
   639  
   640  var matchEnv = regexp.MustCompile(`^RCLONE_CONFIG_(.*?)_TYPE=.*$`)
   641  
   642  // FileSections returns the sections in the config file
   643  // including any defined by environment variables.
   644  func FileSections() []string {
   645  	sections := LoadedData().GetSectionList()
   646  	for _, item := range os.Environ() {
   647  		matches := matchEnv.FindStringSubmatch(item)
   648  		if len(matches) == 2 {
   649  			sections = append(sections, strings.ToLower(matches[1]))
   650  		}
   651  	}
   652  	return sections
   653  }
   654  
   655  // DumpRcRemote dumps the config for a single remote
   656  func DumpRcRemote(name string) (dump rc.Params) {
   657  	params := rc.Params{}
   658  	for _, key := range LoadedData().GetKeyList(name) {
   659  		params[key] = FileGet(name, key)
   660  	}
   661  	return params
   662  }
   663  
   664  // DumpRcBlob dumps all the config as an unstructured blob suitable
   665  // for the rc
   666  func DumpRcBlob() (dump rc.Params) {
   667  	dump = rc.Params{}
   668  	for _, name := range LoadedData().GetSectionList() {
   669  		dump[name] = DumpRcRemote(name)
   670  	}
   671  	return dump
   672  }
   673  
   674  // Dump dumps all the config as a JSON file
   675  func Dump() error {
   676  	dump := DumpRcBlob()
   677  	b, err := json.MarshalIndent(dump, "", "    ")
   678  	if err != nil {
   679  		return fmt.Errorf("failed to marshal config dump: %w", err)
   680  	}
   681  	_, err = os.Stdout.Write(b)
   682  	if err != nil {
   683  		return fmt.Errorf("failed to write config dump: %w", err)
   684  	}
   685  	return nil
   686  }
   687  
   688  // makeCacheDir returns a directory to use for caching.
   689  func makeCacheDir() (dir string) {
   690  	dir, err := os.UserCacheDir()
   691  	if err != nil || dir == "" {
   692  		fs.Debugf(nil, "Failed to find user cache dir, using temporary directory: %v", err)
   693  		// if no dir found then use TempDir - we will have a cachedir!
   694  		dir = os.TempDir()
   695  	}
   696  	return filepath.Join(dir, "rclone")
   697  }
   698  
   699  // GetCacheDir returns the default directory for cache
   700  //
   701  // The directory is neither guaranteed to exist nor have accessible permissions.
   702  // Users of this should make a subdirectory and use MkdirAll() to create it
   703  // and any parents.
   704  func GetCacheDir() string {
   705  	return cacheDir
   706  }
   707  
   708  // SetCacheDir sets new default directory for cache
   709  func SetCacheDir(path string) (err error) {
   710  	cacheDir, err = filepath.Abs(path)
   711  	return
   712  }
   713  
   714  // SetTempDir sets new default directory to use for temporary files.
   715  //
   716  // Assuming golang's os.TempDir is used to get the directory:
   717  // "On Unix systems, it returns $TMPDIR if non-empty, else /tmp. On Windows,
   718  // it uses GetTempPath, returning the first non-empty value from %TMP%, %TEMP%,
   719  // %USERPROFILE%, or the Windows directory."
   720  //
   721  // To override the default we therefore set environment variable TMPDIR
   722  // on Unix systems, and both TMP and TEMP on Windows (they are almost exclusively
   723  // aliases for the same path, and programs may refer to to either of them).
   724  // This should make all libraries and forked processes use the same.
   725  func SetTempDir(path string) (err error) {
   726  	var tempDir string
   727  	if tempDir, err = filepath.Abs(path); err != nil {
   728  		return err
   729  	}
   730  	if runtime.GOOS == "windows" {
   731  		if err = os.Setenv("TMP", tempDir); err != nil {
   732  			return err
   733  		}
   734  		if err = os.Setenv("TEMP", tempDir); err != nil {
   735  			return err
   736  		}
   737  	} else {
   738  		return os.Setenv("TMPDIR", tempDir)
   739  	}
   740  	return nil
   741  }