github.com/NebulousLabs/Sia@v1.3.7/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 }