github.com/nebulouslabs/sia@v1.3.7/modules/renter/persist.go (about) 1 package renter 2 3 import ( 4 "bytes" 5 "compress/gzip" 6 "encoding/base64" 7 "errors" 8 "io" 9 "os" 10 "path/filepath" 11 "strconv" 12 13 "github.com/NebulousLabs/Sia/build" 14 "github.com/NebulousLabs/Sia/encoding" 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/persist" 17 "github.com/NebulousLabs/Sia/types" 18 ) 19 20 const ( 21 logFile = modules.RenterDir + ".log" 22 // PersistFilename is the filename to be used when persisting renter information to a JSON file 23 PersistFilename = "renter.json" 24 // ShareExtension is the extension to be used 25 ShareExtension = ".sia" 26 ) 27 28 var ( 29 //ErrBadFile is an error when a file does not qualify as .sia file 30 ErrBadFile = errors.New("not a .sia file") 31 // ErrIncompatible is an error when file is not compatible with current version 32 ErrIncompatible = errors.New("file is not compatible with current version") 33 // ErrNoNicknames is an error when no nickname is given 34 ErrNoNicknames = errors.New("at least one nickname must be supplied") 35 // ErrNonShareSuffix is an error when the suffix of a file does not match the defined share extension 36 ErrNonShareSuffix = errors.New("suffix of file must be " + ShareExtension) 37 38 settingsMetadata = persist.Metadata{ 39 Header: "Renter Persistence", 40 Version: persistVersion, 41 } 42 43 shareHeader = [15]byte{'S', 'i', 'a', ' ', 'S', 'h', 'a', 'r', 'e', 'd', ' ', 'F', 'i', 'l', 'e'} 44 shareVersion = "0.4" 45 46 // Persist Version Numbers 47 persistVersion040 = "0.4" 48 persistVersion133 = "1.3.3" 49 ) 50 51 type ( 52 // persist contains all of the persistent renter data. 53 persistence struct { 54 MaxDownloadSpeed int64 55 MaxUploadSpeed int64 56 StreamCacheSize uint64 57 Tracking map[string]trackedFile 58 } 59 ) 60 61 // MarshalSia implements the encoding.SiaMarshaller interface, writing the 62 // file data to w. 63 func (f *file) MarshalSia(w io.Writer) error { 64 enc := encoding.NewEncoder(w) 65 66 // encode easy fields 67 err := enc.EncodeAll( 68 f.name, 69 f.size, 70 f.masterKey, 71 f.pieceSize, 72 f.mode, 73 ) 74 if err != nil { 75 return err 76 } 77 // COMPATv0.4.3 - encode the bytesUploaded and chunksUploaded fields 78 // TODO: the resulting .sia file may confuse old clients. 79 err = enc.EncodeAll(f.pieceSize*f.numChunks()*uint64(f.erasureCode.NumPieces()), f.numChunks()) 80 if err != nil { 81 return err 82 } 83 84 // encode erasureCode 85 switch code := f.erasureCode.(type) { 86 case *rsCode: 87 err = enc.EncodeAll( 88 "Reed-Solomon", 89 uint64(code.dataPieces), 90 uint64(code.numPieces-code.dataPieces), 91 ) 92 if err != nil { 93 return err 94 } 95 default: 96 if build.DEBUG { 97 panic("unknown erasure code") 98 } 99 return errors.New("unknown erasure code") 100 } 101 // encode contracts 102 if err := enc.Encode(uint64(len(f.contracts))); err != nil { 103 return err 104 } 105 for _, c := range f.contracts { 106 if err := enc.Encode(c); err != nil { 107 return err 108 } 109 } 110 return nil 111 } 112 113 // UnmarshalSia implements the encoding.SiaUnmarshaller interface, 114 // reconstructing a file from the encoded bytes read from r. 115 func (f *file) UnmarshalSia(r io.Reader) error { 116 dec := encoding.NewDecoder(r) 117 118 // COMPATv0.4.3 - decode bytesUploaded and chunksUploaded into dummy vars. 119 var bytesUploaded, chunksUploaded uint64 120 121 // Decode easy fields. 122 err := dec.DecodeAll( 123 &f.name, 124 &f.size, 125 &f.masterKey, 126 &f.pieceSize, 127 &f.mode, 128 &bytesUploaded, 129 &chunksUploaded, 130 ) 131 if err != nil { 132 return err 133 } 134 f.staticUID = persist.RandomSuffix() 135 136 // Decode erasure coder. 137 var codeType string 138 if err := dec.Decode(&codeType); err != nil { 139 return err 140 } 141 switch codeType { 142 case "Reed-Solomon": 143 var nData, nParity uint64 144 err = dec.DecodeAll( 145 &nData, 146 &nParity, 147 ) 148 if err != nil { 149 return err 150 } 151 rsc, err := NewRSCode(int(nData), int(nParity)) 152 if err != nil { 153 return err 154 } 155 f.erasureCode = rsc 156 default: 157 return errors.New("unrecognized erasure code type: " + codeType) 158 } 159 160 // Decode contracts. 161 var nContracts uint64 162 if err := dec.Decode(&nContracts); err != nil { 163 return err 164 } 165 f.contracts = make(map[types.FileContractID]fileContract) 166 var contract fileContract 167 for i := uint64(0); i < nContracts; i++ { 168 if err := dec.Decode(&contract); err != nil { 169 return err 170 } 171 f.contracts[contract.ID] = contract 172 } 173 return nil 174 } 175 176 // saveFile saves a file to the renter directory. 177 func (r *Renter) saveFile(f *file) error { 178 if f.deleted { 179 return errors.New("can't save deleted file") 180 } 181 // Create directory structure specified in nickname. 182 fullPath := filepath.Join(r.persistDir, f.name+ShareExtension) 183 err := os.MkdirAll(filepath.Dir(fullPath), 0700) 184 if err != nil { 185 return err 186 } 187 188 // Open SafeFile handle. 189 handle, err := persist.NewSafeFile(filepath.Join(r.persistDir, f.name+ShareExtension)) 190 if err != nil { 191 return err 192 } 193 defer handle.Close() 194 195 // Write file data. 196 err = shareFiles([]*file{f}, handle) 197 if err != nil { 198 return err 199 } 200 201 // Commit the SafeFile. 202 return handle.CommitSync() 203 } 204 205 // saveSync stores the current renter data to disk and then syncs to disk. 206 func (r *Renter) saveSync() error { 207 return persist.SaveJSON(settingsMetadata, r.persist, filepath.Join(r.persistDir, PersistFilename)) 208 } 209 210 // loadSiaFiles walks through the directory searching for siafiles and loading 211 // them into memory. 212 func (r *Renter) loadSiaFiles() error { 213 // Recursively load all files found in renter directory. Errors 214 // encountered during loading are logged, but are not considered fatal. 215 return filepath.Walk(r.persistDir, func(path string, info os.FileInfo, err error) error { 216 // This error is non-nil if filepath.Walk couldn't stat a file or 217 // folder. 218 if err != nil { 219 r.log.Println("WARN: could not stat file or folder during walk:", err) 220 return nil 221 } 222 223 // Skip folders and non-sia files. 224 if info.IsDir() || filepath.Ext(path) != ShareExtension { 225 return nil 226 } 227 228 // Open the file. 229 file, err := os.Open(path) 230 if err != nil { 231 r.log.Println("ERROR: could not open .sia file:", err) 232 return nil 233 } 234 defer file.Close() 235 236 // Load the file contents into the renter. 237 _, err = r.loadSharedFiles(file) 238 if err != nil { 239 r.log.Println("ERROR: could not load .sia file:", err) 240 return nil 241 } 242 return nil 243 }) 244 } 245 246 // load fetches the saved renter data from disk. 247 func (r *Renter) loadSettings() error { 248 r.persist = persistence{ 249 Tracking: make(map[string]trackedFile), 250 } 251 err := persist.LoadJSON(settingsMetadata, &r.persist, filepath.Join(r.persistDir, PersistFilename)) 252 if os.IsNotExist(err) { 253 // No persistence yet, set the defaults and continue. 254 r.persist.MaxDownloadSpeed = DefaultMaxDownloadSpeed 255 r.persist.MaxUploadSpeed = DefaultMaxUploadSpeed 256 r.persist.StreamCacheSize = DefaultStreamCacheSize 257 err = r.saveSync() 258 if err != nil { 259 return err 260 } 261 } else if err == persist.ErrBadVersion { 262 // Outdated version, try the 040 to 133 upgrade. 263 err = convertPersistVersionFrom040To133(filepath.Join(r.persistDir, PersistFilename)) 264 if err != nil { 265 // Nothing left to try. 266 return err 267 } 268 // Re-load the settings now that the file has been upgraded. 269 return r.loadSettings() 270 } else if err != nil { 271 return err 272 } 273 274 // Set the bandwidth limits on the contractor, which was already initialized 275 // without bandwidth limits. 276 return r.setBandwidthLimits(r.persist.MaxDownloadSpeed, r.persist.MaxUploadSpeed) 277 } 278 279 // shareFiles writes the specified files to w. First a header is written, 280 // followed by the gzipped concatenation of each file. 281 func shareFiles(files []*file, w io.Writer) error { 282 // Write header. 283 err := encoding.NewEncoder(w).EncodeAll( 284 shareHeader, 285 shareVersion, 286 uint64(len(files)), 287 ) 288 if err != nil { 289 return err 290 } 291 292 // Create compressor. 293 zip, _ := gzip.NewWriterLevel(w, gzip.BestSpeed) 294 enc := encoding.NewEncoder(zip) 295 296 // Encode each file. 297 for _, f := range files { 298 err = enc.Encode(f) 299 if err != nil { 300 return err 301 } 302 } 303 304 return zip.Close() 305 } 306 307 // ShareFiles saves the specified files to shareDest. 308 func (r *Renter) ShareFiles(nicknames []string, shareDest string) error { 309 lockID := r.mu.RLock() 310 defer r.mu.RUnlock(lockID) 311 312 // TODO: consider just appending the proper extension. 313 if filepath.Ext(shareDest) != ShareExtension { 314 return ErrNonShareSuffix 315 } 316 317 handle, err := os.Create(shareDest) 318 if err != nil { 319 return err 320 } 321 defer handle.Close() 322 323 // Load files from renter. 324 files := make([]*file, len(nicknames)) 325 for i, name := range nicknames { 326 f, exists := r.files[name] 327 if !exists { 328 return ErrUnknownPath 329 } 330 files[i] = f 331 } 332 333 err = shareFiles(files, handle) 334 if err != nil { 335 os.Remove(shareDest) 336 return err 337 } 338 339 return nil 340 } 341 342 // ShareFilesASCII returns the specified files in ASCII format. 343 func (r *Renter) ShareFilesASCII(nicknames []string) (string, error) { 344 lockID := r.mu.RLock() 345 defer r.mu.RUnlock(lockID) 346 347 // Load files from renter. 348 files := make([]*file, len(nicknames)) 349 for i, name := range nicknames { 350 f, exists := r.files[name] 351 if !exists { 352 return "", ErrUnknownPath 353 } 354 files[i] = f 355 } 356 357 buf := new(bytes.Buffer) 358 err := shareFiles(files, base64.NewEncoder(base64.URLEncoding, buf)) 359 if err != nil { 360 return "", err 361 } 362 363 return buf.String(), nil 364 } 365 366 // loadSharedFiles reads .sia data from reader and registers the contained 367 // files in the renter. It returns the nicknames of the loaded files. 368 func (r *Renter) loadSharedFiles(reader io.Reader) ([]string, error) { 369 // read header 370 var header [15]byte 371 var version string 372 var numFiles uint64 373 err := encoding.NewDecoder(reader).DecodeAll( 374 &header, 375 &version, 376 &numFiles, 377 ) 378 if err != nil { 379 return nil, err 380 } else if header != shareHeader { 381 return nil, ErrBadFile 382 } else if version != shareVersion { 383 return nil, ErrIncompatible 384 } 385 386 // Create decompressor. 387 unzip, err := gzip.NewReader(reader) 388 if err != nil { 389 return nil, err 390 } 391 dec := encoding.NewDecoder(unzip) 392 393 // Read each file. 394 files := make([]*file, numFiles) 395 for i := range files { 396 files[i] = new(file) 397 err := dec.Decode(files[i]) 398 if err != nil { 399 return nil, err 400 } 401 402 // Make sure the file's name does not conflict with existing files. 403 dupCount := 0 404 origName := files[i].name 405 for { 406 _, exists := r.files[files[i].name] 407 if !exists { 408 break 409 } 410 dupCount++ 411 files[i].name = origName + "_" + strconv.Itoa(dupCount) 412 } 413 } 414 415 // Add files to renter. 416 names := make([]string, numFiles) 417 for i, f := range files { 418 r.files[f.name] = f 419 names[i] = f.name 420 } 421 // Save the files. 422 for _, f := range files { 423 r.saveFile(f) 424 } 425 426 return names, nil 427 } 428 429 // initPersist handles all of the persistence initialization, such as creating 430 // the persistence directory and starting the logger. 431 func (r *Renter) initPersist() error { 432 // Create the perist directory if it does not yet exist. 433 err := os.MkdirAll(r.persistDir, 0700) 434 if err != nil { 435 return err 436 } 437 438 // Initialize the logger. 439 r.log, err = persist.NewFileLogger(filepath.Join(r.persistDir, logFile)) 440 if err != nil { 441 return err 442 } 443 444 // Load the prior persistence structures. 445 err = r.loadSettings() 446 if err != nil { 447 return err 448 } 449 450 // Load the siafiles into memory. 451 return r.loadSiaFiles() 452 } 453 454 // LoadSharedFiles loads a .sia file into the renter. It returns the nicknames 455 // of the loaded files. 456 func (r *Renter) LoadSharedFiles(filename string) ([]string, error) { 457 lockID := r.mu.Lock() 458 defer r.mu.Unlock(lockID) 459 460 file, err := os.Open(filename) 461 if err != nil { 462 return nil, err 463 } 464 defer file.Close() 465 return r.loadSharedFiles(file) 466 } 467 468 // LoadSharedFilesASCII loads an ASCII-encoded .sia file into the renter. It 469 // returns the nicknames of the loaded files. 470 func (r *Renter) LoadSharedFilesASCII(asciiSia string) ([]string, error) { 471 lockID := r.mu.Lock() 472 defer r.mu.Unlock(lockID) 473 474 dec := base64.NewDecoder(base64.URLEncoding, bytes.NewBufferString(asciiSia)) 475 return r.loadSharedFiles(dec) 476 } 477 478 // convertPersistVersionFrom040to133 upgrades a legacy persist file to the next 479 // version, adding new fields with their default values. 480 func convertPersistVersionFrom040To133(path string) error { 481 metadata := persist.Metadata{ 482 Header: settingsMetadata.Header, 483 Version: persistVersion040, 484 } 485 p := persistence{ 486 Tracking: make(map[string]trackedFile), 487 } 488 489 err := persist.LoadJSON(metadata, &p, path) 490 if err != nil { 491 return err 492 } 493 metadata.Version = persistVersion133 494 p.MaxDownloadSpeed = DefaultMaxDownloadSpeed 495 p.MaxUploadSpeed = DefaultMaxUploadSpeed 496 p.StreamCacheSize = DefaultStreamCacheSize 497 return persist.SaveJSON(metadata, p, path) 498 }