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  }