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