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)