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  }