/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/libkb/json.go
523 строки
13 KB
zoom-ua
modernize error handling with errors.As/Is (#29350)
25 июн 2026, 17:41
Не верифицирован
25 июн 2026, 17:41
e09e254
Код
Авторство
О чём код?
// Copyright 2015 Keybase, Inc. All rights reserved. Use of // this source code is governed by the included BSD license. package libkb import ( "bufio" "bytes" "encoding/json" "errors" "fmt" "io" "os" "runtime" "strings" "sync" jsonw "github.com/keybase/go-jsonw" ) type jsonFileTransaction struct { f *JSONFile tmpname string } var _ ConfigWriterTransacter = (*jsonFileTransaction)(nil) type JSONFile struct { Contextified filename string which string jw *jsonw.Wrapper exists bool setMutex sync.RWMutex txMutex sync.Mutex tx *jsonFileTransaction } func NewJSONFile(g *GlobalContext, filename, which string) *JSONFile { return &JSONFile{ filename: filename, which: which, jw: jsonw.NewDictionary(), Contextified: NewContextified(g), } } func (f *JSONFile) GetWrapper() *jsonw.Wrapper { return f.jw } func (f *JSONFile) Exists() bool { return f.exists } func (f *JSONFile) Load(warnOnNotFound bool) error { found, err := f.LoadCheckFound() if err != nil { return err } if !found { msg := fmt.Sprintf("No %q file found; tried %s", f.which, f.filename) if warnOnNotFound { f.G().Log.Warning(msg) } else { f.G().Log.Debug(msg) } } return nil } func (f *JSONFile) LoadCheckFound() (found bool, err error) { f.G().Log.Debug("+ loading %q file: %s", f.which, f.filename) file, err := os.Open(f.filename) if err != nil { if os.IsNotExist(err) { return false, nil } MobilePermissionDeniedCheck(f.G(), err, fmt.Sprintf("%s: %s", f.which, f.filename)) if os.IsPermission(err) { f.G().Log.Warning("Permission denied opening %s file %s", f.which, f.filename) return true, nil } return true, err } f.exists = true defer file.Close() var buf bytes.Buffer fileTee := io.TeeReader(bufio.NewReader(file), &buf) err = jsonw.EnsureMaxDepthDefault(bufio.NewReader(fileTee)) if err != nil { return true, err } decoder := json.NewDecoder(&buf) obj := make(map[string]any) // Treat empty files like an empty dictionary if err = decoder.Decode(&obj); err != nil && !errors.Is(err, io.EOF) { f.G().Log.Errorf("Error decoding %s file %s", f.which, f.filename) return true, err } f.jw = jsonw.NewWrapper(obj) f.G().Log.Debug("- successfully loaded %s file", f.which) return true, nil } func (f *JSONFile) Nuke() error { f.G().Log.Debug("+ nuke file %s", f.filename) err := os.Remove(f.filename) f.G().Log.Debug("- nuke file %s -> %s", f.filename, ErrToOk(err)) return err } func (f *JSONFile) BeginTransaction() (ConfigWriterTransacter, error) { tx, err := newJSONFileTransaction(f) if err != nil { return nil, err } if err = f.setTx(tx); err != nil { return nil, err } return tx, nil } func (f *JSONFile) setTx(tx *jsonFileTransaction) error { f.txMutex.Lock() defer f.txMutex.Unlock() if f.tx != nil && tx != nil { return fmt.Errorf("Provision transaction already in progress") } f.tx = tx return nil } func (f *JSONFile) getOrMakeTx() (*jsonFileTransaction, bool, error) { f.txMutex.Lock() defer f.txMutex.Unlock() // if a transaction exists, use it if f.tx != nil { return f.tx, false, nil } // make a new transaction tx, err := newJSONFileTransaction(f) if err != nil { return nil, false, err } f.tx = tx // return true so caller knows that a transaction was created return f.tx, true, nil } func newJSONFileTransaction(f *JSONFile) (*jsonFileTransaction, error) { ret := &jsonFileTransaction{f: f} sffx, err := RandString("", 15) if err != nil { return nil, err } ret.tmpname = f.filename + "." + sffx return ret, nil } func (f *JSONFile) SetWrapperAtPath(p string, w *jsonw.Wrapper) error { err := f.jw.SetValueAtPath(p, w) if err == nil { err = f.Save() } return err } func (f *JSONFile) DeleteAtPath(p string) { _ = f.jw.DeleteValueAtPath(p) _ = f.Save() } func (f *JSONFile) Save() error { tx, txCreated, err := f.getOrMakeTx() if err != nil { return err } if txCreated { // if Save() created a transaction, then abort it if it // still exists on exit defer func() { if tx != nil { _ = tx.Abort() } }() } if err := f.save(); err != nil { return err } if txCreated { // this Save() call created a transaction, so commit it if err := tx.Commit(); err != nil { return err } // Commit worked, clear the transaction so defer() doesn't // abort it. tx = nil } return nil } func (f *JSONFile) save() (err error) { if f.tx == nil { return errors.New("save() called with nil transaction") } filename := f.tx.tmpname f.G().Log.Debug("+ saving %s file %s", f.which, filename) err = MakeParentDirs(f.G().Log, filename) if err != nil { f.G().Log.Errorf("Failed to make parent dirs for %s", filename) return err } var dat any if f.jw == nil { // Make a default Dictionary if none already exists dat = make(map[string]any) f.G().Log.Warning("No value for %s file; assuming empty value (i.e., {})", f.which) } else { dat, err = f.jw.GetData() if err != nil { f.G().Log.Errorf("Failed to encode data for %s file", f.which) return err } } var writer *os.File flags := (os.O_WRONLY | os.O_CREATE | os.O_TRUNC) writer, err = os.OpenFile(filename, flags, PermFile) if err != nil { f.G().Log.Errorf("Failed to open %s file %s for writing: %s", f.which, filename, err) return err } defer writer.Close() encoded, err := json.MarshalIndent(dat, "", " ") if err != nil { f.G().Log.Errorf("Error marshaling data to %s file %s: %s", f.which, filename, err) return err } n, err := writer.Write(encoded) if err != nil { f.G().Log.Errorf("Error writing encoded data to %s file %s: %s", f.which, filename, err) return err } if n != len(encoded) { f.G().Log.Errorf("Error writing encoded data to %s file %s: wrote %d bytes, expected %d", f.which, filename, n, len(encoded)) return io.ErrShortWrite } err = writer.Sync() if err != nil { f.G().Log.Errorf("Error syncing %s file %s: %s", f.which, filename, err) return err } err = writer.Close() if err != nil { f.G().Log.Errorf("Error closing %s file %s: %s", f.which, filename, err) return err } f.G().Log.Debug("- saved %s file %s", f.which, filename) if runtime.GOOS == "android" { f.G().Log.Debug("| Android extra checks in JSONFile.save") info, err := os.Stat(filename) if err != nil { f.G().Log.Errorf("| Error os.Stat(%s): %s", filename, err) return err } f.G().Log.Debug("| File info: name = %s", info.Name()) f.G().Log.Debug("| File info: size = %d", info.Size()) f.G().Log.Debug("| File info: mode = %s", info.Mode()) f.G().Log.Debug("| File info: mod time = %s", info.ModTime()) if info.Size() != int64(len(encoded)) { f.G().Log.Errorf("| File info size (%d) does not match encoded len (%d)", info.Size(), len(encoded)) return fmt.Errorf("file info size (%d) does not match encoded len (%d)", info.Size(), len(encoded)) } // write out the `dat` that was marshaled into filename encodedForLog, err := json.Marshal(dat) if err != nil { f.G().Log.Debug("error marshaling for log dump: %s", err) } else { f.G().Log.Debug("data written to %s:", filename) f.G().Log.Debug(string(encodedForLog)) } // load the file and dump its contents to the log fc, err := os.Open(filename) if err != nil { f.G().Log.Debug("error opening %s to check its contents: %s", filename, err) } else { defer fc.Close() decoder := json.NewDecoder(fc) obj := make(map[string]any) if err := decoder.Decode(&obj); err != nil { f.G().Log.Debug("error decoding %s: %s", filename, err) } else { // marshal it into json without indents to make it one line out, err := json.Marshal(obj) if err != nil { f.G().Log.Debug("error marshaling decoded obj: %s", err) } else { f.G().Log.Debug("%s contents (marshaled): %s", filename, string(out)) } } } f.G().Log.Debug("| Android extra checks done") } return nil } func (f *jsonFileTransaction) Abort() error { f.f.G().Log.Debug("+ Aborting %s rewrite %s", f.f.which, f.tmpname) err := os.Remove(f.tmpname) setErr := f.f.setTx(nil) if err == nil { err = setErr } f.f.G().Log.Debug("- Abort -> %s\n", ErrToOk(err)) return err } // Rollback reloads config from unchanged config file, bringing its // state back to from before the transaction changes. Note that it // only works for changes that do not affect UserConfig, which caches // values, and has to be reloaded manually. func (f *jsonFileTransaction) Rollback() error { f.f.G().Log.Debug("+ Rolling back %s to state from %s", f.f.which, f.f.filename) err := f.f.Load(false) if !f.f.exists { // Before transaction there was no file, so set in-memory // wrapper to clean state as well. f.f.jw = jsonw.NewDictionary() f.f.G().Log.Debug("+ Rolling back to clean state because f.exists is false") } f.f.G().Log.Debug("- Rollback -> %s", ErrToOk(err)) return err } func (f *jsonFileTransaction) Commit() (err error) { f.f.G().Log.Debug("+ Commit %s rewrite %s", f.f.which, f.tmpname) defer func() { f.f.G().Log.Debug("- Commit %s rewrite %s", f.f.which, ErrToOk(err)) }() f.f.G().Log.Debug("| Commit: making parent directories for %q", f.f.filename) if err = MakeParentDirs(f.f.G().Log, f.f.filename); err != nil { return err } f.f.G().Log.Debug("| Commit : renaming %q => %q", f.tmpname, f.f.filename) err = renameFile(f.f.G(), f.tmpname, f.f.filename) if err != nil { f.f.G().Log.Debug("| Commit: rename %q => %q error: %s", f.tmpname, f.f.filename, err) } return f.f.setTx(nil) } type valueGetter func(*jsonw.Wrapper) (any, error) func (f *JSONFile) getValueAtPath(p string, getter valueGetter) (ret any, isSet bool) { var err error ret, err = getter(f.jw.AtPath(p)) if err == nil { isSet = true } return ret, isSet } func getString(w *jsonw.Wrapper) (any, error) { return w.GetString() } func getBool(w *jsonw.Wrapper) (any, error) { return w.GetBool() } func getInt(w *jsonw.Wrapper) (any, error) { return w.GetInt() } func getFloat(w *jsonw.Wrapper) (any, error) { return w.GetFloat() } func (f *JSONFile) GetFilename() string { return f.filename } func (f *JSONFile) GetInterfaceAtPath(p string) (i any, err error) { f.setMutex.RLock() defer f.setMutex.RUnlock() return f.jw.AtPath(p).GetInterface() } func (f *JSONFile) GetStringAtPath(p string) (ret string, isSet bool) { f.setMutex.RLock() defer f.setMutex.RUnlock() i, isSet := f.getValueAtPath(p, getString) if isSet { ret = i.(string) } return ret, isSet } func (f *JSONFile) GetBoolAtPath(p string) (ret bool, isSet bool) { f.setMutex.RLock() defer f.setMutex.RUnlock() i, isSet := f.getValueAtPath(p, getBool) if isSet { ret = i.(bool) } return ret, isSet } func (f *JSONFile) GetIntAtPath(p string) (ret int, isSet bool) { f.setMutex.RLock() defer f.setMutex.RUnlock() i, isSet := f.getValueAtPath(p, getInt) if isSet { ret = i.(int) } return ret, isSet } func (f *JSONFile) GetFloatAtPath(p string) (ret float64, isSet bool) { f.setMutex.RLock() defer f.setMutex.RUnlock() v, isSet := f.getValueAtPath(p, getFloat) if isSet { ret = v.(float64) } return ret, isSet } func (f *JSONFile) GetNullAtPath(p string) (isSet bool) { f.setMutex.RLock() defer f.setMutex.RUnlock() w := f.jw.AtPath(p) isSet = w.IsNil() && w.Error() == nil return isSet } func (f *JSONFile) setValueAtPath(p string, getter valueGetter, v any) error { existing, err := getter(f.jw.AtPath(p)) if err != nil || existing != v { err = f.jw.SetValueAtPath(p, jsonw.NewWrapper(v)) if err == nil { return f.Save() } } return err } func (f *JSONFile) SetStringAtPath(p string, v string) error { f.setMutex.Lock() defer f.setMutex.Unlock() return f.setValueAtPath(p, getString, v) } func (f *JSONFile) SetBoolAtPath(p string, v bool) error { f.setMutex.Lock() defer f.setMutex.Unlock() return f.setValueAtPath(p, getBool, v) } func (f *JSONFile) SetIntAtPath(p string, v int) error { f.setMutex.Lock() defer f.setMutex.Unlock() return f.setValueAtPath(p, getInt, v) } func (f *JSONFile) SetFloatAtPath(p string, v float64) error { f.setMutex.Lock() defer f.setMutex.Unlock() return f.setValueAtPath(p, getFloat, v) } func (f *JSONFile) SetInt64AtPath(p string, v int64) error { f.setMutex.Lock() defer f.setMutex.Unlock() return f.setValueAtPath(p, getInt, v) } func (f *JSONFile) SetNullAtPath(p string) (err error) { f.setMutex.Lock() defer f.setMutex.Unlock() existing := f.jw.AtPath(p) if !existing.IsNil() || existing.Error() != nil { err = f.jw.SetValueAtPath(p, jsonw.NewNil()) if err == nil { return f.Save() } } return err } func isJSONNoSuchKeyError(err error) bool { var jsonErr *jsonw.Error return errors.As(err, &jsonErr) && strings.Contains(err.Error(), "no such key") }