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  }