gitlab.com/jokerrs1/Sia@v1.3.2/persist/json.go (about)

     1  package persist
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io/ioutil"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/NebulousLabs/Sia/build"
    11  	"github.com/NebulousLabs/Sia/crypto"
    12  )
    13  
    14  // readJSON will try to read a persisted json object from a file.
    15  func readJSON(meta Metadata, object interface{}, filename string) error {
    16  	// Open the file.
    17  	file, err := os.Open(filename)
    18  	if os.IsNotExist(err) {
    19  		return err
    20  	}
    21  	if err != nil {
    22  		return build.ExtendErr("unable to open persisted json object file", err)
    23  	}
    24  	defer file.Close()
    25  
    26  	// Read the metadata from the file.
    27  	var header, version string
    28  	dec := json.NewDecoder(file)
    29  	if err := dec.Decode(&header); err != nil {
    30  		return build.ExtendErr("unable to read header from persisted json object file", err)
    31  	}
    32  	if header != meta.Header {
    33  		return ErrBadHeader
    34  	}
    35  	if err := dec.Decode(&version); err != nil {
    36  		return build.ExtendErr("unable to read version from persisted json object file", err)
    37  	}
    38  	if version != meta.Version {
    39  		return ErrBadVersion
    40  	}
    41  
    42  	// Read everything else.
    43  	remainingBytes, err := ioutil.ReadAll(dec.Buffered())
    44  	if err != nil {
    45  		return build.ExtendErr("unable to read persisted json object data", err)
    46  	}
    47  	// The buffer may or may not have read the rest of the file, read the rest
    48  	// of the file to be certain.
    49  	remainingBytesExtra, err := ioutil.ReadAll(file)
    50  	if err != nil {
    51  		return build.ExtendErr("unable to read persisted json object data", err)
    52  	}
    53  	remainingBytes = append(remainingBytes, remainingBytesExtra...)
    54  
    55  	// Determine whether the leading bytes contain a checksum. A proper checksum
    56  	// will be 67 bytes (quote, 64 byte checksum, quote, newline). A manual
    57  	// checksum will be the characters "manual\n" (9 characters). If neither
    58  	// decode correctly, it is assumed that there is no checksum at all.
    59  	var checksum crypto.Hash
    60  	err = json.Unmarshal(remainingBytes[:67], &checksum)
    61  	if err == nil && checksum == crypto.HashBytes(remainingBytes[68:]) {
    62  		// Checksum is proper, and matches the data. Update the data portion to
    63  		// exclude the checksum.
    64  		remainingBytes = remainingBytes[68:]
    65  	} else {
    66  		// Cryptographic checksum failed, try interpreting a manual checksum.
    67  		var manualChecksum string
    68  		err := json.Unmarshal(remainingBytes[:8], &manualChecksum)
    69  		if err == nil && manualChecksum == "manual" {
    70  			// Manual checksum is proper. Update the remaining data to exclude
    71  			// the manual checksum.
    72  			remainingBytes = remainingBytes[9:]
    73  		}
    74  	}
    75  
    76  	// Any valid checksum has been stripped off. There is also the case that no
    77  	// checksum was written at all, which is ignored as a case - it's needed to
    78  	// preserve compatibility with previous persist files.
    79  
    80  	// Parse the json object.
    81  	return json.Unmarshal(remainingBytes, &object)
    82  }
    83  
    84  // LoadJSON will load a persisted json object from disk.
    85  func LoadJSON(meta Metadata, object interface{}, filename string) error {
    86  	// Verify that the filename does not have the persist temp suffix.
    87  	if strings.HasSuffix(filename, tempSuffix) {
    88  		return ErrBadFilenameSuffix
    89  	}
    90  
    91  	// Verify that no other thread is using this filename.
    92  	err := func() error {
    93  		activeFilesMu.Lock()
    94  		defer activeFilesMu.Unlock()
    95  
    96  		_, exists := activeFiles[filename]
    97  		if exists {
    98  			build.Critical(ErrFileInUse, filename)
    99  			return ErrFileInUse
   100  		}
   101  		activeFiles[filename] = struct{}{}
   102  		return nil
   103  	}()
   104  	if err != nil {
   105  		return err
   106  	}
   107  	// Release the lock at the end of the function.
   108  	defer func() {
   109  		activeFilesMu.Lock()
   110  		delete(activeFiles, filename)
   111  		activeFilesMu.Unlock()
   112  	}()
   113  
   114  	// Try opening the primary file.
   115  	err = readJSON(meta, object, filename)
   116  	if err == ErrBadHeader || err == ErrBadVersion || os.IsNotExist(err) {
   117  		return err
   118  	}
   119  	if err != nil {
   120  		// Try opening the temp file.
   121  		err := readJSON(meta, object, filename+tempSuffix)
   122  		if err != nil {
   123  			return build.ExtendErr("unable to read persisted json object from disk", err)
   124  		}
   125  	}
   126  
   127  	// Success.
   128  	return nil
   129  }
   130  
   131  // SaveJSON will save a json object to disk in a durable, atomic way. The
   132  // resulting file will have a checksum of the data as the third line. If
   133  // manually editing files, the checksum line can be replaced with the 8
   134  // characters "manual". This will cause the reader to accept the checksum even
   135  // though the file has been changed.
   136  func SaveJSON(meta Metadata, object interface{}, filename string) error {
   137  	// Verify that the filename does not have the persist temp suffix.
   138  	if strings.HasSuffix(filename, tempSuffix) {
   139  		return ErrBadFilenameSuffix
   140  	}
   141  
   142  	// Verify that no other thread is using this filename.
   143  	err := func() error {
   144  		activeFilesMu.Lock()
   145  		defer activeFilesMu.Unlock()
   146  
   147  		_, exists := activeFiles[filename]
   148  		if exists {
   149  			build.Critical(ErrFileInUse, filename)
   150  			return ErrFileInUse
   151  		}
   152  		activeFiles[filename] = struct{}{}
   153  		return nil
   154  	}()
   155  	if err != nil {
   156  		return err
   157  	}
   158  	// Release the lock at the end of the function.
   159  	defer func() {
   160  		activeFilesMu.Lock()
   161  		delete(activeFiles, filename)
   162  		activeFilesMu.Unlock()
   163  	}()
   164  
   165  	// Write the metadata to the buffer.
   166  	buf := new(bytes.Buffer)
   167  	enc := json.NewEncoder(buf)
   168  	if err := enc.Encode(meta.Header); err != nil {
   169  		return build.ExtendErr("unable to encode metadata header", err)
   170  	}
   171  	if err := enc.Encode(meta.Version); err != nil {
   172  		return build.ExtendErr("unable to encode metadata version", err)
   173  	}
   174  
   175  	// Marshal the object into json and write the checksum + result to the
   176  	// buffer.
   177  	objBytes, err := json.MarshalIndent(object, "", "\t")
   178  	if err != nil {
   179  		return build.ExtendErr("unable to marshal the provided object", err)
   180  	}
   181  	checksum := crypto.HashBytes(objBytes)
   182  	if err := enc.Encode(checksum); err != nil {
   183  		return build.ExtendErr("unable to encode checksum", err)
   184  	}
   185  	buf.Write(objBytes)
   186  
   187  	// Write out the data to the temp file, with a sync.
   188  	data := buf.Bytes()
   189  	err = func() (err error) {
   190  		file, err := os.OpenFile(filename+tempSuffix, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0600)
   191  		if err != nil {
   192  			return build.ExtendErr("unable to open temp file", err)
   193  		}
   194  		defer func() {
   195  			err = build.ComposeErrors(err, file.Close())
   196  		}()
   197  
   198  		// Write and sync.
   199  		_, err = file.Write(data)
   200  		if err != nil {
   201  			return build.ExtendErr("unable to write temp file", err)
   202  		}
   203  		err = file.Sync()
   204  		if err != nil {
   205  			return build.ExtendErr("unable to sync temp file", err)
   206  		}
   207  		return nil
   208  	}()
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	// Write out the data to the real file, with a sync.
   214  	err = func() (err error) {
   215  		file, err := os.OpenFile(filename, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0600)
   216  		if err != nil {
   217  			return build.ExtendErr("unable to open file", err)
   218  		}
   219  		defer func() {
   220  			err = build.ComposeErrors(err, file.Close())
   221  		}()
   222  
   223  		// Write and sync.
   224  		_, err = file.Write(data)
   225  		if err != nil {
   226  			return build.ExtendErr("unable to write file", err)
   227  		}
   228  		err = file.Sync()
   229  		if err != nil {
   230  			return build.ExtendErr("unable to sync temp file", err)
   231  		}
   232  		return nil
   233  	}()
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	// Success
   239  	return nil
   240  }