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

     1  // Package configfile implements a config file loader and saver
     2  package configfile
     3  
     4  import (
     5  	"bytes"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/Unknwon/goconfig" //nolint:misspell // Don't include misspell when running golangci-lint
    13  	"github.com/rclone/rclone/fs"
    14  	"github.com/rclone/rclone/fs/config"
    15  	"github.com/rclone/rclone/lib/file"
    16  )
    17  
    18  // Install installs the config file handler
    19  func Install() {
    20  	config.SetData(&Storage{})
    21  }
    22  
    23  // Storage implements config.Storage for saving and loading config
    24  // data in a simple INI based file.
    25  type Storage struct {
    26  	mu sync.Mutex           // to protect the following variables
    27  	gc *goconfig.ConfigFile // config file loaded - not thread safe
    28  	fi os.FileInfo          // stat of the file when last loaded
    29  }
    30  
    31  // Check to see if we need to reload the config
    32  //
    33  // mu must be held when calling this
    34  func (s *Storage) _check() {
    35  	if configPath := config.GetConfigPath(); configPath != "" {
    36  		// Check to see if config file has changed since it was last loaded
    37  		fi, err := os.Stat(configPath)
    38  		if err == nil {
    39  			// check to see if config file has changed and if it has, reload it
    40  			if s.fi == nil || !fi.ModTime().Equal(s.fi.ModTime()) || fi.Size() != s.fi.Size() {
    41  				fs.Debugf(nil, "Config file has changed externally - reloading")
    42  				err := s._load()
    43  				if err != nil {
    44  					fs.Errorf(nil, "Failed to read config file - using previous config: %v", err)
    45  				}
    46  			}
    47  		}
    48  	}
    49  }
    50  
    51  // _load the config from permanent storage, decrypting if necessary
    52  //
    53  // mu must be held when calling this
    54  func (s *Storage) _load() (err error) {
    55  	// Make sure we have a sensible default even when we error
    56  	defer func() {
    57  		if s.gc == nil {
    58  			s.gc, _ = goconfig.LoadFromReader(bytes.NewReader([]byte{}))
    59  		}
    60  	}()
    61  
    62  	configPath := config.GetConfigPath()
    63  	if configPath == "" {
    64  		return config.ErrorConfigFileNotFound
    65  	}
    66  
    67  	fd, err := os.Open(configPath)
    68  	if err != nil {
    69  		if os.IsNotExist(err) {
    70  			return config.ErrorConfigFileNotFound
    71  		}
    72  		return err
    73  	}
    74  	defer fs.CheckClose(fd, &err)
    75  
    76  	// Update s.fi with the current file info
    77  	s.fi, _ = os.Stat(configPath)
    78  
    79  	cryptReader, err := config.Decrypt(fd)
    80  	if err != nil {
    81  		return err
    82  	}
    83  
    84  	gc, err := goconfig.LoadFromReader(cryptReader)
    85  	if err != nil {
    86  		return err
    87  	}
    88  	s.gc = gc
    89  
    90  	return nil
    91  }
    92  
    93  // Load the config from permanent storage, decrypting if necessary
    94  func (s *Storage) Load() (err error) {
    95  	s.mu.Lock()
    96  	defer s.mu.Unlock()
    97  	return s._load()
    98  }
    99  
   100  // Save the config to permanent storage, encrypting if necessary
   101  func (s *Storage) Save() error {
   102  	s.mu.Lock()
   103  	defer s.mu.Unlock()
   104  
   105  	configPath := config.GetConfigPath()
   106  	if configPath == "" {
   107  		return fmt.Errorf("failed to save config file, path is empty")
   108  	}
   109  	configDir, configName := filepath.Split(configPath)
   110  
   111  	info, err := os.Lstat(configPath)
   112  	if err != nil {
   113  		if !os.IsNotExist(err) {
   114  			return fmt.Errorf("failed to resolve config file path: %w", err)
   115  		}
   116  	} else {
   117  		if info.Mode()&os.ModeSymlink != 0 {
   118  			configPath, err = os.Readlink(configPath)
   119  			if err != nil {
   120  				return fmt.Errorf("failed to resolve config file symbolic link: %w", err)
   121  			}
   122  			if !filepath.IsAbs(configPath) {
   123  				configPath = filepath.Join(configDir, configPath)
   124  			}
   125  			configDir = filepath.Dir(configPath)
   126  		}
   127  	}
   128  	err = file.MkdirAll(configDir, os.ModePerm)
   129  	if err != nil {
   130  		return fmt.Errorf("failed to create config directory: %w", err)
   131  	}
   132  	f, err := os.CreateTemp(configDir, configName)
   133  	if err != nil {
   134  		return fmt.Errorf("failed to create temp file for new config: %w", err)
   135  	}
   136  	defer func() {
   137  		_ = f.Close()
   138  		if err := os.Remove(f.Name()); err != nil && !os.IsNotExist(err) {
   139  			fs.Errorf(nil, "Failed to remove temp file for new config: %v", err)
   140  		}
   141  	}()
   142  
   143  	var buf bytes.Buffer
   144  	if err := goconfig.SaveConfigData(s.gc, &buf); err != nil {
   145  		return fmt.Errorf("failed to save config file: %w", err)
   146  	}
   147  
   148  	if err := config.Encrypt(&buf, f); err != nil {
   149  		return err
   150  	}
   151  
   152  	_ = f.Sync()
   153  	err = f.Close()
   154  	if err != nil {
   155  		return fmt.Errorf("failed to close config file: %w", err)
   156  	}
   157  
   158  	var fileMode os.FileMode = 0600
   159  	info, err = os.Stat(configPath)
   160  	if err != nil {
   161  		fs.Debugf(nil, "Using default permissions for config file: %v", fileMode)
   162  	} else if info.Mode() != fileMode {
   163  		fs.Debugf(nil, "Keeping previous permissions for config file: %v", info.Mode())
   164  		fileMode = info.Mode()
   165  	}
   166  
   167  	attemptCopyGroup(configPath, f.Name())
   168  
   169  	err = os.Chmod(f.Name(), fileMode)
   170  	if err != nil {
   171  		fs.Errorf(nil, "Failed to set permissions on config file: %v", err)
   172  	}
   173  
   174  	fbackup, err := os.CreateTemp(configDir, configName+".old")
   175  	if err != nil {
   176  		return fmt.Errorf("failed to create temp file for old config backup: %w", err)
   177  	}
   178  	err = fbackup.Close()
   179  	if err != nil {
   180  		return fmt.Errorf("failed to close temp file for old config backup: %w", err)
   181  	}
   182  	keepBackup := true
   183  	defer func() {
   184  		if !keepBackup {
   185  			if err := os.Remove(fbackup.Name()); err != nil && !os.IsNotExist(err) {
   186  				fs.Errorf(nil, "Failed to remove temp file for old config backup: %v", err)
   187  			}
   188  		}
   189  	}()
   190  
   191  	if err = os.Rename(configPath, fbackup.Name()); err != nil {
   192  		if !os.IsNotExist(err) {
   193  			return fmt.Errorf("failed to move previous config to backup location: %w", err)
   194  		}
   195  		keepBackup = false // no existing file, no need to keep backup even if writing of new file fails
   196  	}
   197  	if err = os.Rename(f.Name(), configPath); err != nil {
   198  		return fmt.Errorf("failed to move newly written config from %s to final location: %v", f.Name(), err)
   199  	}
   200  	keepBackup = false // new file was written, no need to keep backup
   201  
   202  	// Update s.fi with the newly written file
   203  	s.fi, _ = os.Stat(configPath)
   204  
   205  	return nil
   206  }
   207  
   208  // Serialize the config into a string
   209  func (s *Storage) Serialize() (string, error) {
   210  	s.mu.Lock()
   211  	defer s.mu.Unlock()
   212  
   213  	s._check()
   214  	var buf bytes.Buffer
   215  	if err := goconfig.SaveConfigData(s.gc, &buf); err != nil {
   216  		return "", fmt.Errorf("failed to save config file: %w", err)
   217  	}
   218  
   219  	return buf.String(), nil
   220  }
   221  
   222  // HasSection returns true if section exists in the config file
   223  func (s *Storage) HasSection(section string) bool {
   224  	s.mu.Lock()
   225  	defer s.mu.Unlock()
   226  
   227  	s._check()
   228  	_, err := s.gc.GetSection(section)
   229  	return err == nil
   230  }
   231  
   232  // DeleteSection removes the named section and all config from the
   233  // config file
   234  func (s *Storage) DeleteSection(section string) {
   235  	s.mu.Lock()
   236  	defer s.mu.Unlock()
   237  
   238  	s._check()
   239  	s.gc.DeleteSection(section)
   240  }
   241  
   242  // GetSectionList returns a slice of strings with names for all the
   243  // sections
   244  func (s *Storage) GetSectionList() []string {
   245  	s.mu.Lock()
   246  	defer s.mu.Unlock()
   247  
   248  	s._check()
   249  	return s.gc.GetSectionList()
   250  }
   251  
   252  // GetKeyList returns the keys in this section
   253  func (s *Storage) GetKeyList(section string) []string {
   254  	s.mu.Lock()
   255  	defer s.mu.Unlock()
   256  
   257  	s._check()
   258  	return s.gc.GetKeyList(section)
   259  }
   260  
   261  // GetValue returns the key in section with a found flag
   262  func (s *Storage) GetValue(section string, key string) (value string, found bool) {
   263  	s.mu.Lock()
   264  	defer s.mu.Unlock()
   265  
   266  	s._check()
   267  	value, err := s.gc.GetValue(section, key)
   268  	if err != nil {
   269  		return "", false
   270  	}
   271  	return value, true
   272  }
   273  
   274  // SetValue sets the value under key in section
   275  func (s *Storage) SetValue(section string, key string, value string) {
   276  	s.mu.Lock()
   277  	defer s.mu.Unlock()
   278  
   279  	s._check()
   280  	if strings.HasPrefix(section, ":") {
   281  		fs.Logf(nil, "Can't save config %q for on the fly backend %q", key, section)
   282  		return
   283  	}
   284  	s.gc.SetValue(section, key, value)
   285  }
   286  
   287  // DeleteKey removes the key under section
   288  func (s *Storage) DeleteKey(section string, key string) bool {
   289  	s.mu.Lock()
   290  	defer s.mu.Unlock()
   291  
   292  	s._check()
   293  	return s.gc.DeleteKey(section, key)
   294  }
   295  
   296  // Check the interface is satisfied
   297  var _ config.Storage = (*Storage)(nil)