github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/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/Synthesix/Sia/build" 14 "github.com/Synthesix/Sia/encoding" 15 "github.com/Synthesix/Sia/modules" 16 "github.com/Synthesix/Sia/persist" 17 "github.com/Synthesix/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 if f.deleted { 165 return errors.New("can't save deleted file") 166 } 167 // Create directory structure specified in nickname. 168 fullPath := filepath.Join(r.persistDir, f.name+ShareExtension) 169 err := os.MkdirAll(filepath.Dir(fullPath), 0700) 170 if err != nil { 171 return err 172 } 173 174 // Open SafeFile handle. 175 handle, err := persist.NewSafeFile(filepath.Join(r.persistDir, f.name+ShareExtension)) 176 if err != nil { 177 return err 178 } 179 defer handle.Close() 180 181 // Write file data. 182 err = shareFiles([]*file{f}, handle) 183 if err != nil { 184 return err 185 } 186 187 // Commit the SafeFile. 188 return handle.CommitSync() 189 } 190 191 // saveSync stores the current renter data to disk and then syncs to disk. 192 func (r *Renter) saveSync() error { 193 data := struct { 194 Tracking map[string]trackedFile 195 }{r.tracking} 196 197 return persist.SaveJSON(saveMetadata, data, filepath.Join(r.persistDir, PersistFilename)) 198 } 199 200 // load fetches the saved renter data from disk. 201 func (r *Renter) load() error { 202 // Recursively load all files found in renter directory. Errors 203 // encountered during loading are logged, but are not considered fatal. 204 err := filepath.Walk(r.persistDir, func(path string, info os.FileInfo, err error) error { 205 // This error is non-nil if filepath.Walk couldn't stat a file or 206 // folder. 207 if err != nil { 208 r.log.Println("WARN: could not stat file or folder during walk:", err) 209 return nil 210 } 211 212 // Skip folders and non-sia files. 213 if info.IsDir() || filepath.Ext(path) != ShareExtension { 214 return nil 215 } 216 217 // Open the file. 218 file, err := os.Open(path) 219 if err != nil { 220 r.log.Println("ERROR: could not open .sia file:", err) 221 return nil 222 } 223 defer file.Close() 224 225 // Load the file contents into the renter. 226 _, err = r.loadSharedFiles(file) 227 if err != nil { 228 r.log.Println("ERROR: could not load .sia file:", err) 229 return nil 230 } 231 return nil 232 }) 233 if err != nil { 234 return err 235 } 236 237 // Load contracts, repair set, and entropy. 238 data := struct { 239 Tracking map[string]trackedFile 240 Repairing map[string]string // COMPATv0.4.8 241 }{} 242 err = persist.LoadJSON(saveMetadata, &data, filepath.Join(r.persistDir, PersistFilename)) 243 if err != nil { 244 return err 245 } 246 if data.Tracking != nil { 247 r.tracking = data.Tracking 248 } 249 250 return nil 251 } 252 253 // shareFiles writes the specified files to w. First a header is written, 254 // followed by the gzipped concatenation of each file. 255 func shareFiles(files []*file, w io.Writer) error { 256 // Write header. 257 err := encoding.NewEncoder(w).EncodeAll( 258 shareHeader, 259 shareVersion, 260 uint64(len(files)), 261 ) 262 if err != nil { 263 return err 264 } 265 266 // Create compressor. 267 zip, _ := gzip.NewWriterLevel(w, gzip.BestSpeed) 268 enc := encoding.NewEncoder(zip) 269 270 // Encode each file. 271 for _, f := range files { 272 err = enc.Encode(f) 273 if err != nil { 274 return err 275 } 276 } 277 278 return zip.Close() 279 } 280 281 // ShareFiles saves the specified files to shareDest. 282 func (r *Renter) ShareFiles(nicknames []string, shareDest string) error { 283 lockID := r.mu.RLock() 284 defer r.mu.RUnlock(lockID) 285 286 // TODO: consider just appending the proper extension. 287 if filepath.Ext(shareDest) != ShareExtension { 288 return ErrNonShareSuffix 289 } 290 291 handle, err := os.Create(shareDest) 292 if err != nil { 293 return err 294 } 295 defer handle.Close() 296 297 // Load files from renter. 298 files := make([]*file, len(nicknames)) 299 for i, name := range nicknames { 300 f, exists := r.files[name] 301 if !exists { 302 return ErrUnknownPath 303 } 304 files[i] = f 305 } 306 307 err = shareFiles(files, handle) 308 if err != nil { 309 os.Remove(shareDest) 310 return err 311 } 312 313 return nil 314 } 315 316 // ShareFilesASCII returns the specified files in ASCII format. 317 func (r *Renter) ShareFilesASCII(nicknames []string) (string, error) { 318 lockID := r.mu.RLock() 319 defer r.mu.RUnlock(lockID) 320 321 // Load files from renter. 322 files := make([]*file, len(nicknames)) 323 for i, name := range nicknames { 324 f, exists := r.files[name] 325 if !exists { 326 return "", ErrUnknownPath 327 } 328 files[i] = f 329 } 330 331 buf := new(bytes.Buffer) 332 err := shareFiles(files, base64.NewEncoder(base64.URLEncoding, buf)) 333 if err != nil { 334 return "", err 335 } 336 337 return buf.String(), nil 338 } 339 340 // loadSharedFiles reads .sia data from reader and registers the contained 341 // files in the renter. It returns the nicknames of the loaded files. 342 func (r *Renter) loadSharedFiles(reader io.Reader) ([]string, error) { 343 // read header 344 var header [15]byte 345 var version string 346 var numFiles uint64 347 err := encoding.NewDecoder(reader).DecodeAll( 348 &header, 349 &version, 350 &numFiles, 351 ) 352 if err != nil { 353 return nil, err 354 } else if header != shareHeader { 355 return nil, ErrBadFile 356 } else if version != shareVersion { 357 return nil, ErrIncompatible 358 } 359 360 // Create decompressor. 361 unzip, err := gzip.NewReader(reader) 362 if err != nil { 363 return nil, err 364 } 365 dec := encoding.NewDecoder(unzip) 366 367 // Read each file. 368 files := make([]*file, numFiles) 369 for i := range files { 370 files[i] = new(file) 371 err := dec.Decode(files[i]) 372 if err != nil { 373 return nil, err 374 } 375 376 // Make sure the file's name does not conflict with existing files. 377 dupCount := 0 378 origName := files[i].name 379 for { 380 _, exists := r.files[files[i].name] 381 if !exists { 382 break 383 } 384 dupCount++ 385 files[i].name = origName + "_" + strconv.Itoa(dupCount) 386 } 387 } 388 389 // Add files to renter. 390 names := make([]string, numFiles) 391 for i, f := range files { 392 r.files[f.name] = f 393 names[i] = f.name 394 } 395 // Save the files. 396 for _, f := range files { 397 r.saveFile(f) 398 } 399 400 return names, nil 401 } 402 403 // initPersist handles all of the persistence initialization, such as creating 404 // the persistence directory and starting the logger. 405 func (r *Renter) initPersist() error { 406 // Create the perist directory if it does not yet exist. 407 err := os.MkdirAll(r.persistDir, 0700) 408 if err != nil { 409 return err 410 } 411 412 // Initialize the logger. 413 r.log, err = persist.NewFileLogger(filepath.Join(r.persistDir, logFile)) 414 if err != nil { 415 return err 416 } 417 418 // Load the prior persistence structures. 419 err = r.load() 420 if err != nil && !os.IsNotExist(err) { 421 return err 422 } 423 return nil 424 } 425 426 // LoadSharedFiles loads a .sia file into the renter. It returns the nicknames 427 // of the loaded files. 428 func (r *Renter) LoadSharedFiles(filename string) ([]string, error) { 429 lockID := r.mu.Lock() 430 defer r.mu.Unlock(lockID) 431 432 file, err := os.Open(filename) 433 if err != nil { 434 return nil, err 435 } 436 defer file.Close() 437 return r.loadSharedFiles(file) 438 } 439 440 // LoadSharedFilesASCII loads an ASCII-encoded .sia file into the renter. It 441 // returns the nicknames of the loaded files. 442 func (r *Renter) LoadSharedFilesASCII(asciiSia string) ([]string, error) { 443 lockID := r.mu.Lock() 444 defer r.mu.Unlock(lockID) 445 446 dec := base64.NewDecoder(base64.URLEncoding, bytes.NewBufferString(asciiSia)) 447 return r.loadSharedFiles(dec) 448 }