github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/json.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 package libkb 5 6 import ( 7 "bufio" 8 "bytes" 9 "encoding/json" 10 "errors" 11 "fmt" 12 "io" 13 "os" 14 "runtime" 15 "strings" 16 "sync" 17 18 jsonw "github.com/keybase/go-jsonw" 19 ) 20 21 type jsonFileTransaction struct { 22 f *JSONFile 23 tmpname string 24 } 25 26 var _ ConfigWriterTransacter = (*jsonFileTransaction)(nil) 27 28 type JSONFile struct { 29 Contextified 30 filename string 31 which string 32 jw *jsonw.Wrapper 33 exists bool 34 setMutex sync.RWMutex 35 36 txMutex sync.Mutex 37 tx *jsonFileTransaction 38 } 39 40 func NewJSONFile(g *GlobalContext, filename, which string) *JSONFile { 41 return &JSONFile{ 42 filename: filename, 43 which: which, 44 jw: jsonw.NewDictionary(), 45 Contextified: NewContextified(g), 46 } 47 } 48 49 func (f *JSONFile) GetWrapper() *jsonw.Wrapper { 50 return f.jw 51 } 52 func (f *JSONFile) Exists() bool { return f.exists } 53 54 func (f *JSONFile) Load(warnOnNotFound bool) error { 55 found, err := f.LoadCheckFound() 56 if err != nil { 57 return err 58 } 59 if !found { 60 msg := fmt.Sprintf("No %q file found; tried %s", f.which, f.filename) 61 if warnOnNotFound { 62 f.G().Log.Warning(msg) 63 } else { 64 f.G().Log.Debug(msg) 65 } 66 } 67 return nil 68 } 69 70 func (f *JSONFile) LoadCheckFound() (found bool, err error) { 71 f.G().Log.Debug("+ loading %q file: %s", f.which, f.filename) 72 file, err := os.Open(f.filename) 73 if err != nil { 74 if os.IsNotExist(err) { 75 return false, nil 76 } 77 78 MobilePermissionDeniedCheck(f.G(), err, fmt.Sprintf("%s: %s", f.which, f.filename)) 79 80 if os.IsPermission(err) { 81 f.G().Log.Warning("Permission denied opening %s file %s", f.which, f.filename) 82 return true, nil 83 } 84 85 return true, err 86 } 87 f.exists = true 88 defer file.Close() 89 90 var buf bytes.Buffer 91 fileTee := io.TeeReader(bufio.NewReader(file), &buf) 92 err = jsonw.EnsureMaxDepthDefault(bufio.NewReader(fileTee)) 93 if err != nil { 94 return true, err 95 } 96 97 decoder := json.NewDecoder(&buf) 98 obj := make(map[string]interface{}) 99 // Treat empty files like an empty dictionary 100 if err = decoder.Decode(&obj); err != nil && err != io.EOF { 101 f.G().Log.Errorf("Error decoding %s file %s", f.which, f.filename) 102 return true, err 103 } 104 f.jw = jsonw.NewWrapper(obj) 105 106 f.G().Log.Debug("- successfully loaded %s file", f.which) 107 return true, nil 108 } 109 110 func (f *JSONFile) Nuke() error { 111 f.G().Log.Debug("+ nuke file %s", f.filename) 112 err := os.Remove(f.filename) 113 f.G().Log.Debug("- nuke file %s -> %s", f.filename, ErrToOk(err)) 114 return err 115 } 116 117 func (f *JSONFile) BeginTransaction() (ConfigWriterTransacter, error) { 118 tx, err := newJSONFileTransaction(f) 119 if err != nil { 120 return nil, err 121 } 122 if err = f.setTx(tx); err != nil { 123 return nil, err 124 } 125 return tx, nil 126 } 127 128 func (f *JSONFile) setTx(tx *jsonFileTransaction) error { 129 f.txMutex.Lock() 130 defer f.txMutex.Unlock() 131 if f.tx != nil && tx != nil { 132 return fmt.Errorf("Provision transaction already in progress") 133 } 134 f.tx = tx 135 return nil 136 } 137 138 func (f *JSONFile) getOrMakeTx() (*jsonFileTransaction, bool, error) { 139 f.txMutex.Lock() 140 defer f.txMutex.Unlock() 141 142 // if a transaction exists, use it 143 if f.tx != nil { 144 return f.tx, false, nil 145 } 146 147 // make a new transaction 148 tx, err := newJSONFileTransaction(f) 149 if err != nil { 150 return nil, false, err 151 } 152 153 f.tx = tx 154 155 // return true so caller knows that a transaction was created 156 return f.tx, true, nil 157 } 158 159 func newJSONFileTransaction(f *JSONFile) (*jsonFileTransaction, error) { 160 ret := &jsonFileTransaction{f: f} 161 sffx, err := RandString("", 15) 162 if err != nil { 163 return nil, err 164 } 165 ret.tmpname = f.filename + "." + sffx 166 return ret, nil 167 } 168 169 func (f *JSONFile) SetWrapperAtPath(p string, w *jsonw.Wrapper) error { 170 err := f.jw.SetValueAtPath(p, w) 171 if err == nil { 172 err = f.Save() 173 } 174 return err 175 } 176 177 func (f *JSONFile) DeleteAtPath(p string) { 178 _ = f.jw.DeleteValueAtPath(p) 179 _ = f.Save() 180 } 181 182 func (f *JSONFile) Save() error { 183 tx, txCreated, err := f.getOrMakeTx() 184 if err != nil { 185 return err 186 } 187 if txCreated { 188 // if Save() created a transaction, then abort it if it 189 // still exists on exit 190 defer func() { 191 if tx != nil { 192 _ = tx.Abort() 193 } 194 }() 195 } 196 197 if err := f.save(); err != nil { 198 return err 199 } 200 201 if txCreated { 202 // this Save() call created a transaction, so commit it 203 if err := tx.Commit(); err != nil { 204 return err 205 } 206 207 // Commit worked, clear the transaction so defer() doesn't 208 // abort it. 209 tx = nil 210 } 211 212 return nil 213 } 214 215 func (f *JSONFile) save() (err error) { 216 if f.tx == nil { 217 return errors.New("save() called with nil transaction") 218 } 219 filename := f.tx.tmpname 220 f.G().Log.Debug("+ saving %s file %s", f.which, filename) 221 222 err = MakeParentDirs(f.G().Log, filename) 223 if err != nil { 224 f.G().Log.Errorf("Failed to make parent dirs for %s", filename) 225 return err 226 } 227 228 var dat interface{} 229 230 if f.jw == nil { 231 // Make a default Dictionary if none already exists 232 dat = make(map[string]interface{}) 233 f.G().Log.Warning("No value for %s file; assuming empty value (i.e., {})", 234 f.which) 235 } else { 236 dat, err = f.jw.GetData() 237 if err != nil { 238 f.G().Log.Errorf("Failed to encode data for %s file", f.which) 239 return err 240 } 241 } 242 var writer *os.File 243 flags := (os.O_WRONLY | os.O_CREATE | os.O_TRUNC) 244 writer, err = os.OpenFile(filename, flags, PermFile) 245 if err != nil { 246 f.G().Log.Errorf("Failed to open %s file %s for writing: %s", 247 f.which, filename, err) 248 return err 249 } 250 defer writer.Close() 251 252 encoded, err := json.MarshalIndent(dat, "", " ") 253 if err != nil { 254 f.G().Log.Errorf("Error marshaling data to %s file %s: %s", f.which, filename, err) 255 return err 256 } 257 258 n, err := writer.Write(encoded) 259 if err != nil { 260 f.G().Log.Errorf("Error writing encoded data to %s file %s: %s", f.which, filename, err) 261 return err 262 } 263 if n != len(encoded) { 264 f.G().Log.Errorf("Error writing encoded data to %s file %s: wrote %d bytes, expected %d", f.which, filename, n, len(encoded)) 265 return io.ErrShortWrite 266 } 267 268 err = writer.Sync() 269 if err != nil { 270 f.G().Log.Errorf("Error syncing %s file %s: %s", f.which, filename, err) 271 return err 272 } 273 274 err = writer.Close() 275 if err != nil { 276 f.G().Log.Errorf("Error closing %s file %s: %s", f.which, filename, err) 277 return err 278 } 279 280 f.G().Log.Debug("- saved %s file %s", f.which, filename) 281 282 if runtime.GOOS == "android" { 283 f.G().Log.Debug("| Android extra checks in JSONFile.save") 284 info, err := os.Stat(filename) 285 if err != nil { 286 f.G().Log.Errorf("| Error os.Stat(%s): %s", filename, err) 287 return err 288 } 289 f.G().Log.Debug("| File info: name = %s", info.Name()) 290 f.G().Log.Debug("| File info: size = %d", info.Size()) 291 f.G().Log.Debug("| File info: mode = %s", info.Mode()) 292 f.G().Log.Debug("| File info: mod time = %s", info.ModTime()) 293 294 if info.Size() != int64(len(encoded)) { 295 f.G().Log.Errorf("| File info size (%d) does not match encoded len (%d)", info.Size(), len(encoded)) 296 return fmt.Errorf("file info size (%d) does not match encoded len (%d)", info.Size(), len(encoded)) 297 } 298 299 // write out the `dat` that was marshaled into filename 300 encodedForLog, err := json.Marshal(dat) 301 if err != nil { 302 f.G().Log.Debug("error marshaling for log dump: %s", err) 303 } else { 304 f.G().Log.Debug("data written to %s:", filename) 305 f.G().Log.Debug(string(encodedForLog)) 306 } 307 308 // load the file and dump its contents to the log 309 fc, err := os.Open(filename) 310 if err != nil { 311 f.G().Log.Debug("error opening %s to check its contents: %s", filename, err) 312 } else { 313 defer fc.Close() 314 315 decoder := json.NewDecoder(fc) 316 obj := make(map[string]interface{}) 317 if err := decoder.Decode(&obj); err != nil { 318 f.G().Log.Debug("error decoding %s: %s", filename, err) 319 } else { 320 // marshal it into json without indents to make it one line 321 out, err := json.Marshal(obj) 322 if err != nil { 323 f.G().Log.Debug("error marshaling decoded obj: %s", err) 324 } else { 325 f.G().Log.Debug("%s contents (marshaled): %s", filename, string(out)) 326 } 327 } 328 } 329 330 f.G().Log.Debug("| Android extra checks done") 331 } 332 333 return nil 334 } 335 336 func (f *jsonFileTransaction) Abort() error { 337 f.f.G().Log.Debug("+ Aborting %s rewrite %s", f.f.which, f.tmpname) 338 err := os.Remove(f.tmpname) 339 setErr := f.f.setTx(nil) 340 if err == nil { 341 err = setErr 342 } 343 f.f.G().Log.Debug("- Abort -> %s\n", ErrToOk(err)) 344 return err 345 } 346 347 // Rollback reloads config from unchanged config file, bringing its 348 // state back to from before the transaction changes. Note that it 349 // only works for changes that do not affect UserConfig, which caches 350 // values, and has to be reloaded manually. 351 func (f *jsonFileTransaction) Rollback() error { 352 f.f.G().Log.Debug("+ Rolling back %s to state from %s", f.f.which, f.f.filename) 353 err := f.f.Load(false) 354 if !f.f.exists { 355 // Before transaction there was no file, so set in-memory 356 // wrapper to clean state as well. 357 f.f.jw = jsonw.NewDictionary() 358 f.f.G().Log.Debug("+ Rolling back to clean state because f.exists is false") 359 } 360 f.f.G().Log.Debug("- Rollback -> %s", ErrToOk(err)) 361 return err 362 } 363 364 func (f *jsonFileTransaction) Commit() (err error) { 365 f.f.G().Log.Debug("+ Commit %s rewrite %s", f.f.which, f.tmpname) 366 defer func() { f.f.G().Log.Debug("- Commit %s rewrite %s", f.f.which, ErrToOk(err)) }() 367 368 f.f.G().Log.Debug("| Commit: making parent directories for %q", f.f.filename) 369 if err = MakeParentDirs(f.f.G().Log, f.f.filename); err != nil { 370 return err 371 } 372 f.f.G().Log.Debug("| Commit : renaming %q => %q", f.tmpname, f.f.filename) 373 err = renameFile(f.f.G(), f.tmpname, f.f.filename) 374 if err != nil { 375 f.f.G().Log.Debug("| Commit: rename %q => %q error: %s", f.tmpname, f.f.filename, err) 376 } 377 return f.f.setTx(nil) 378 } 379 380 type valueGetter func(*jsonw.Wrapper) (interface{}, error) 381 382 func (f *JSONFile) getValueAtPath(p string, getter valueGetter) (ret interface{}, isSet bool) { 383 var err error 384 ret, err = getter(f.jw.AtPath(p)) 385 if err == nil { 386 isSet = true 387 } 388 return ret, isSet 389 } 390 391 func getString(w *jsonw.Wrapper) (interface{}, error) { 392 return w.GetString() 393 } 394 395 func getBool(w *jsonw.Wrapper) (interface{}, error) { 396 return w.GetBool() 397 } 398 399 func getInt(w *jsonw.Wrapper) (interface{}, error) { 400 return w.GetInt() 401 } 402 403 func getFloat(w *jsonw.Wrapper) (interface{}, error) { 404 return w.GetFloat() 405 } 406 407 func (f *JSONFile) GetFilename() string { 408 return f.filename 409 } 410 411 func (f *JSONFile) GetInterfaceAtPath(p string) (i interface{}, err error) { 412 f.setMutex.RLock() 413 defer f.setMutex.RUnlock() 414 return f.jw.AtPath(p).GetInterface() 415 } 416 417 func (f *JSONFile) GetStringAtPath(p string) (ret string, isSet bool) { 418 f.setMutex.RLock() 419 defer f.setMutex.RUnlock() 420 i, isSet := f.getValueAtPath(p, getString) 421 if isSet { 422 ret = i.(string) 423 } 424 return ret, isSet 425 } 426 427 func (f *JSONFile) GetBoolAtPath(p string) (ret bool, isSet bool) { 428 f.setMutex.RLock() 429 defer f.setMutex.RUnlock() 430 i, isSet := f.getValueAtPath(p, getBool) 431 if isSet { 432 ret = i.(bool) 433 } 434 return ret, isSet 435 } 436 437 func (f *JSONFile) GetIntAtPath(p string) (ret int, isSet bool) { 438 f.setMutex.RLock() 439 defer f.setMutex.RUnlock() 440 i, isSet := f.getValueAtPath(p, getInt) 441 if isSet { 442 ret = i.(int) 443 } 444 return ret, isSet 445 } 446 447 func (f *JSONFile) GetFloatAtPath(p string) (ret float64, isSet bool) { 448 f.setMutex.RLock() 449 defer f.setMutex.RUnlock() 450 v, isSet := f.getValueAtPath(p, getFloat) 451 if isSet { 452 ret = v.(float64) 453 } 454 return ret, isSet 455 } 456 457 func (f *JSONFile) GetNullAtPath(p string) (isSet bool) { 458 f.setMutex.RLock() 459 defer f.setMutex.RUnlock() 460 w := f.jw.AtPath(p) 461 isSet = w.IsNil() && w.Error() == nil 462 return isSet 463 } 464 465 func (f *JSONFile) setValueAtPath(p string, getter valueGetter, v interface{}) error { 466 existing, err := getter(f.jw.AtPath(p)) 467 468 if err != nil || existing != v { 469 err = f.jw.SetValueAtPath(p, jsonw.NewWrapper(v)) 470 if err == nil { 471 return f.Save() 472 } 473 } 474 return err 475 } 476 477 func (f *JSONFile) SetStringAtPath(p string, v string) error { 478 f.setMutex.Lock() 479 defer f.setMutex.Unlock() 480 return f.setValueAtPath(p, getString, v) 481 } 482 483 func (f *JSONFile) SetBoolAtPath(p string, v bool) error { 484 f.setMutex.Lock() 485 defer f.setMutex.Unlock() 486 return f.setValueAtPath(p, getBool, v) 487 } 488 489 func (f *JSONFile) SetIntAtPath(p string, v int) error { 490 f.setMutex.Lock() 491 defer f.setMutex.Unlock() 492 return f.setValueAtPath(p, getInt, v) 493 } 494 495 func (f *JSONFile) SetFloatAtPath(p string, v float64) error { 496 f.setMutex.Lock() 497 defer f.setMutex.Unlock() 498 return f.setValueAtPath(p, getFloat, v) 499 } 500 501 func (f *JSONFile) SetInt64AtPath(p string, v int64) error { 502 f.setMutex.Lock() 503 defer f.setMutex.Unlock() 504 return f.setValueAtPath(p, getInt, v) 505 } 506 507 func (f *JSONFile) SetNullAtPath(p string) (err error) { 508 f.setMutex.Lock() 509 defer f.setMutex.Unlock() 510 existing := f.jw.AtPath(p) 511 if !existing.IsNil() || existing.Error() != nil { 512 err = f.jw.SetValueAtPath(p, jsonw.NewNil()) 513 if err == nil { 514 return f.Save() 515 } 516 } 517 return err 518 } 519 520 func isJSONNoSuchKeyError(err error) bool { 521 _, isJSONError := err.(*jsonw.Error) 522 return err != nil && isJSONError && strings.Contains(err.Error(), "no such key") 523 }