// Package configfile implements a config file loader and saver package configfile import ( "bytes" "context" "io/ioutil" "os" "path/filepath" "sync" "github.com/Unknwon/goconfig" "github.com/pkg/errors" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs/config" ) // Special value indicating in memory config file. Empty string works also. const noConfigFile = "/notfound" // LoadConfig installs the config file handler and calls config.LoadConfig func LoadConfig(ctx context.Context) { config.Data = &Storage{} config.LoadConfig(ctx) } // Storage implements config.Storage for saving and loading config // data in a simple INI based file. type Storage struct { gc *goconfig.ConfigFile // config file loaded - thread safe mu sync.Mutex // to protect the following variables fi os.FileInfo // stat of the file when last loaded } // Return whether we have a real config file or not func (s *Storage) noConfig() bool { return config.ConfigPath == "" || config.ConfigPath == noConfigFile } // Check to see if we need to reload the config func (s *Storage) check() { s.mu.Lock() defer s.mu.Unlock() if s.noConfig() { return } // Check to see if config file has changed since it was last loaded fi, err := os.Stat(config.ConfigPath) if err == nil { // check to see if config file has changed and if it has, reload it if s.fi == nil || !fi.ModTime().Equal(s.fi.ModTime()) || fi.Size() != s.fi.Size() { fs.Debugf(nil, "Config file has changed externaly - reloading") err := s._load() if err != nil { fs.Errorf(nil, "Failed to read config file - using previous config: %v", err) } } } } // _load the config from permanent storage, decrypting if necessary // // mu must be held when calling this func (s *Storage) _load() (err error) { // Make sure we have a sensible default even when we error defer func() { if s.gc == nil { s.gc, _ = goconfig.LoadFromReader(bytes.NewReader([]byte{})) } }() if s.noConfig() { return config.ErrorConfigFileNotFound } fd, err := os.Open(config.ConfigPath) if err != nil { if os.IsNotExist(err) { return config.ErrorConfigFileNotFound } return err } defer fs.CheckClose(fd, &err) // Update s.fi with the current file info s.fi, _ = os.Stat(config.ConfigPath) cryptReader, err := config.Decrypt(fd) if err != nil { return err } gc, err := goconfig.LoadFromReader(cryptReader) if err != nil { return err } s.gc = gc return nil } // Load the config from permanent storage, decrypting if necessary func (s *Storage) Load() (err error) { s.mu.Lock() defer s.mu.Unlock() return s._load() } // Save the config to permanent storage, encrypting if necessary func (s *Storage) Save() error { s.mu.Lock() defer s.mu.Unlock() if s.noConfig() { return nil } dir, name := filepath.Split(config.ConfigPath) err := os.MkdirAll(dir, os.ModePerm) if err != nil { return errors.Wrap(err, "failed to create config directory") } f, err := ioutil.TempFile(dir, name) if err != nil { return errors.Errorf("Failed to create temp file for new config: %v", err) } defer func() { _ = f.Close() if err := os.Remove(f.Name()); err != nil && !os.IsNotExist(err) { fs.Errorf(nil, "Failed to remove temp config file: %v", err) } }() var buf bytes.Buffer if err := goconfig.SaveConfigData(s.gc, &buf); err != nil { return errors.Errorf("Failed to save config file: %v", err) } if err := config.Encrypt(&buf, f); err != nil { return err } _ = f.Sync() err = f.Close() if err != nil { return errors.Errorf("Failed to close config file: %v", err) } var fileMode os.FileMode = 0600 info, err := os.Stat(config.ConfigPath) if err != nil { fs.Debugf(nil, "Using default permissions for config file: %v", fileMode) } else if info.Mode() != fileMode { fs.Debugf(nil, "Keeping previous permissions for config file: %v", info.Mode()) fileMode = info.Mode() } attemptCopyGroup(config.ConfigPath, f.Name()) err = os.Chmod(f.Name(), fileMode) if err != nil { fs.Errorf(nil, "Failed to set permissions on config file: %v", err) } if err = os.Rename(config.ConfigPath, config.ConfigPath+".old"); err != nil && !os.IsNotExist(err) { return errors.Errorf("Failed to move previous config to backup location: %v", err) } if err = os.Rename(f.Name(), config.ConfigPath); err != nil { return errors.Errorf("Failed to move newly written config from %s to final location: %v", f.Name(), err) } if err := os.Remove(config.ConfigPath + ".old"); err != nil && !os.IsNotExist(err) { fs.Errorf(nil, "Failed to remove backup config file: %v", err) } // Update s.fi with the newly written file s.fi, _ = os.Stat(config.ConfigPath) return nil } // Serialize the config into a string func (s *Storage) Serialize() (string, error) { s.check() var buf bytes.Buffer if err := goconfig.SaveConfigData(s.gc, &buf); err != nil { return "", errors.Errorf("Failed to save config file: %v", err) } return buf.String(), nil } // HasSection returns true if section exists in the config file func (s *Storage) HasSection(section string) bool { s.check() _, err := s.gc.GetSection(section) if err != nil { return false } return true } // DeleteSection removes the named section and all config from the // config file func (s *Storage) DeleteSection(section string) { s.check() s.gc.DeleteSection(section) } // GetSectionList returns a slice of strings with names for all the // sections func (s *Storage) GetSectionList() []string { s.check() return s.gc.GetSectionList() } // GetKeyList returns the keys in this section func (s *Storage) GetKeyList(section string) []string { s.check() return s.gc.GetKeyList(section) } // GetValue returns the key in section with a found flag func (s *Storage) GetValue(section string, key string) (value string, found bool) { s.check() value, err := s.gc.GetValue(section, key) if err != nil { return "", false } return value, true } // SetValue sets the value under key in section func (s *Storage) SetValue(section string, key string, value string) { s.check() s.gc.SetValue(section, key, value) } // DeleteKey removes the key under section func (s *Storage) DeleteKey(section string, key string) bool { s.check() return s.gc.DeleteKey(section, key) } // Check the interface is satisfied var _ config.Storage = (*Storage)(nil)