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  }