gitlab.com/jokerrs1/Sia@v1.3.2/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 saveMetadata = persist.Metadata{ 39 Header: "Renter Persistence", 40 Version: "0.4", 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 47 // MarshalSia implements the encoding.SiaMarshaller interface, writing the 48 // file data to w. 49 func (f *file) MarshalSia(w io.Writer) error { 50 enc := encoding.NewEncoder(w) 51 52 // encode easy fields 53 err := enc.EncodeAll( 54 f.name, 55 f.size, 56 f.masterKey, 57 f.pieceSize, 58 f.mode, 59 ) 60 if err != nil { 61 return err 62 } 63 // COMPATv0.4.3 - encode the bytesUploaded and chunksUploaded fields 64 // TODO: the resulting .sia file may confuse old clients. 65 err = enc.EncodeAll(f.pieceSize*f.numChunks()*uint64(f.erasureCode.NumPieces()), f.numChunks()) 66 if err != nil { 67 return err 68 } 69 70 // encode erasureCode 71 switch code := f.erasureCode.(type) { 72 case *rsCode: 73 err = enc.EncodeAll( 74 "Reed-Solomon", 75 uint64(code.dataPieces), 76 uint64(code.numPieces-code.dataPieces), 77 ) 78 if err != nil { 79 return err 80 } 81 default: 82 if build.DEBUG { 83 panic("unknown erasure code") 84 } 85 return errors.New("unknown erasure code") 86 } 87 // encode contracts 88 if err := enc.Encode(uint64(len(f.contracts))); err != nil { 89 return err 90 } 91 for _, c := range f.contracts { 92 if err := enc.Encode(c); err != nil { 93 return err 94 } 95 } 96 return nil 97 } 98 99 // UnmarshalSia implements the encoding.SiaUnmarshaller interface, 100 // reconstructing a file from the encoded bytes read from r. 101 func (f *file) UnmarshalSia(r io.Reader) error { 102 dec := encoding.NewDecoder(r) 103 104 // COMPATv0.4.3 - decode bytesUploaded and chunksUploaded into dummy vars. 105 var bytesUploaded, chunksUploaded uint64 106 107 // Decode easy fields. 108 err := dec.DecodeAll( 109 &f.name, 110 &f.size, 111 &f.masterKey, 112 &f.pieceSize, 113 &f.mode, 114 &bytesUploaded, 115 &chunksUploaded, 116 ) 117 if err != nil { 118 return err 119 } 120 f.staticUID = persist.RandomSuffix() 121 122 // Decode erasure coder. 123 var codeType string 124 if err := dec.Decode(&codeType); err != nil { 125 return err 126 } 127 switch codeType { 128 case "Reed-Solomon": 129 var nData, nParity uint64 130 err = dec.DecodeAll( 131 &nData, 132 &nParity, 133 ) 134 if err != nil { 135 return err 136 } 137 rsc, err := NewRSCode(int(nData), int(nParity)) 138 if err != nil { 139 return err 140 } 141 f.erasureCode = rsc 142 default: 143 return errors.New("unrecognized erasure code type: " + codeType) 144 } 145 146 // Decode contracts. 147 var nContracts uint64 148 if err := dec.Decode(&nContracts); err != nil { 149 return err 150 } 151 f.contracts = make(map[types.FileContractID]fileContract) 152 var contract fileContract 153 for i := uint64(0); i < nContracts; i++ { 154 if err := dec.Decode(&contract); err != nil { 155 return err 156 } 157 f.contracts[contract.ID] = contract 158 } 159 return nil 160 } 161 162 // saveFile saves a file to the renter directory. 163 func (r *Renter) saveFile(f *file) error { 164 // Create directory structure specified in nickname. 165 fullPath := filepath.Join(r.persistDir, f.name+ShareExtension) 166 err := os.MkdirAll(filepath.Dir(fullPath), 0700) 167 if err != nil { 168 return err 169 } 170 171 // Open SafeFile handle. 172 handle, err := persist.NewSafeFile(filepath.Join(r.persistDir, f.name+ShareExtension)) 173 if err != nil { 174 return err 175 } 176 defer handle.Close() 177 178 // Write file data. 179 err = shareFiles([]*file{f}, handle) 180 if err != nil { 181 return err 182 } 183 184 // Commit the SafeFile. 185 return handle.CommitSync() 186 } 187 188 // saveSync stores the current renter data to disk and then syncs to disk. 189 func (r *Renter) saveSync() error { 190 data := struct { 191 Tracking map[string]trackedFile 192 }{r.tracking} 193 194 return persist.SaveJSON(saveMetadata, data, filepath.Join(r.persistDir, PersistFilename)) 195 } 196 197 // load fetches the saved renter data from disk. 198 func (r *Renter) load() error { 199 // Recursively load all files found in renter directory. Errors 200 // encountered during loading are logged, but are not considered fatal. 201 err := filepath.Walk(r.persistDir, func(path string, info os.FileInfo, err error) error { 202 // This error is non-nil if filepath.Walk couldn't stat a file or 203 // folder. 204 if err != nil { 205 r.log.Println("WARN: could not stat file or folder during walk:", err) 206 return nil 207 } 208 209 // Skip folders and non-sia files. 210 if info.IsDir() || filepath.Ext(path) != ShareExtension { 211 return nil 212 } 213 214 // Open the file. 215 file, err := os.Open(path) 216 if err != nil { 217 r.log.Println("ERROR: could not open .sia file:", err) 218 return nil 219 } 220 defer file.Close() 221 222 // Load the file contents into the renter. 223 _, err = r.loadSharedFiles(file) 224 if err != nil { 225 r.log.Println("ERROR: could not load .sia file:", err) 226 return nil 227 } 228 return nil 229 }) 230 if err != nil { 231 return err 232 } 233 234 // Load contracts, repair set, and entropy. 235 data := struct { 236 Tracking map[string]trackedFile 237 Repairing map[string]string // COMPATv0.4.8 238 }{} 239 err = persist.LoadJSON(saveMetadata, &data, filepath.Join(r.persistDir, PersistFilename)) 240 if err != nil { 241 return err 242 } 243 if data.Tracking != nil { 244 r.tracking = data.Tracking 245 } 246 247 return nil 248 } 249 250 // shareFiles writes the specified files to w. First a header is written, 251 // followed by the gzipped concatenation of each file. 252 func shareFiles(files []*file, w io.Writer) error { 253 // Write header. 254 err := encoding.NewEncoder(w).EncodeAll( 255 shareHeader, 256 shareVersion, 257 uint64(len(files)), 258 ) 259 if err != nil { 260 return err 261 } 262 263 // Create compressor. 264 zip, _ := gzip.NewWriterLevel(w, gzip.BestSpeed) 265 enc := encoding.NewEncoder(zip) 266 267 // Encode each file. 268 for _, f := range files { 269 err = enc.Encode(f) 270 if err != nil { 271 return err 272 } 273 } 274 275 return zip.Close() 276 } 277 278 // ShareFiles saves the specified files to shareDest. 279 func (r *Renter) ShareFiles(nicknames []string, shareDest string) error { 280 lockID := r.mu.RLock() 281 defer r.mu.RUnlock(lockID) 282 283 // TODO: consider just appending the proper extension. 284 if filepath.Ext(shareDest) != ShareExtension { 285 return ErrNonShareSuffix 286 } 287 288 handle, err := os.Create(shareDest) 289 if err != nil { 290 return err 291 } 292 defer handle.Close() 293 294 // Load files from renter. 295 files := make([]*file, len(nicknames)) 296 for i, name := range nicknames { 297 f, exists := r.files[name] 298 if !exists { 299 return ErrUnknownPath 300 } 301 files[i] = f 302 } 303 304 err = shareFiles(files, handle) 305 if err != nil { 306 os.Remove(shareDest) 307 return err 308 } 309 310 return nil 311 } 312 313 // ShareFilesASCII returns the specified files in ASCII format. 314 func (r *Renter) ShareFilesASCII(nicknames []string) (string, error) { 315 lockID := r.mu.RLock() 316 defer r.mu.RUnlock(lockID) 317 318 // Load files from renter. 319 files := make([]*file, len(nicknames)) 320 for i, name := range nicknames { 321 f, exists := r.files[name] 322 if !exists { 323 return "", ErrUnknownPath 324 } 325 files[i] = f 326 } 327 328 buf := new(bytes.Buffer) 329 err := shareFiles(files, base64.NewEncoder(base64.URLEncoding, buf)) 330 if err != nil { 331 return "", err 332 } 333 334 return buf.String(), nil 335 } 336 337 // loadSharedFiles reads .sia data from reader and registers the contained 338 // files in the renter. It returns the nicknames of the loaded files. 339 func (r *Renter) loadSharedFiles(reader io.Reader) ([]string, error) { 340 // read header 341 var header [15]byte 342 var version string 343 var numFiles uint64 344 err := encoding.NewDecoder(reader).DecodeAll( 345 &header, 346 &version, 347 &numFiles, 348 ) 349 if err != nil { 350 return nil, err 351 } else if header != shareHeader { 352 return nil, ErrBadFile 353 } else if version != shareVersion { 354 return nil, ErrIncompatible 355 } 356 357 // Create decompressor. 358 unzip, err := gzip.NewReader(reader) 359 if err != nil { 360 return nil, err 361 } 362 dec := encoding.NewDecoder(unzip) 363 364 // Read each file. 365 files := make([]*file, numFiles) 366 for i := range files { 367 files[i] = new(file) 368 err := dec.Decode(files[i]) 369 if err != nil { 370 return nil, err 371 } 372 373 // Make sure the file's name does not conflict with existing files. 374 dupCount := 0 375 origName := files[i].name 376 for { 377 _, exists := r.files[files[i].name] 378 if !exists { 379 break 380 } 381 dupCount++ 382 files[i].name = origName + "_" + strconv.Itoa(dupCount) 383 } 384 } 385 386 // Add files to renter. 387 names := make([]string, numFiles) 388 for i, f := range files { 389 r.files[f.name] = f 390 names[i] = f.name 391 } 392 // Save the files. 393 for _, f := range files { 394 r.saveFile(f) 395 } 396 397 return names, nil 398 } 399 400 // initPersist handles all of the persistence initialization, such as creating 401 // the persistence directory and starting the logger. 402 func (r *Renter) initPersist() error { 403 // Create the perist directory if it does not yet exist. 404 err := os.MkdirAll(r.persistDir, 0700) 405 if err != nil { 406 return err 407 } 408 409 // Initialize the logger. 410 r.log, err = persist.NewFileLogger(filepath.Join(r.persistDir, logFile)) 411 if err != nil { 412 return err 413 } 414 415 // Load the prior persistence structures. 416 err = r.load() 417 if err != nil && !os.IsNotExist(err) { 418 return err 419 } 420 return nil 421 } 422 423 // LoadSharedFiles loads a .sia file into the renter. It returns the nicknames 424 // of the loaded files. 425 func (r *Renter) LoadSharedFiles(filename string) ([]string, error) { 426 lockID := r.mu.Lock() 427 defer r.mu.Unlock(lockID) 428 429 file, err := os.Open(filename) 430 if err != nil { 431 return nil, err 432 } 433 defer file.Close() 434 return r.loadSharedFiles(file) 435 } 436 437 // LoadSharedFilesASCII loads an ASCII-encoded .sia file into the renter. It 438 // returns the nicknames of the loaded files. 439 func (r *Renter) LoadSharedFilesASCII(asciiSia string) ([]string, error) { 440 lockID := r.mu.Lock() 441 defer r.mu.Unlock(lockID) 442 443 dec := base64.NewDecoder(base64.URLEncoding, bytes.NewBufferString(asciiSia)) 444 return r.loadSharedFiles(dec) 445 }