1// Copyright 2015 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4package libkb
5
6import (
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
21type jsonFileTransaction struct {
22	f       *JSONFile
23	tmpname string
24}
25
26var _ ConfigWriterTransacter = (*jsonFileTransaction)(nil)
27
28type 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
40func 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
49func (f *JSONFile) GetWrapper() *jsonw.Wrapper {
50	return f.jw
51}
52func (f *JSONFile) Exists() bool { return f.exists }
53
54func (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
70func (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
110func (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
117func (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
128func (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
138func (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
159func 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
169func (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
177func (f *JSONFile) DeleteAtPath(p string) {
178	_ = f.jw.DeleteValueAtPath(p)
179	_ = f.Save()
180}
181
182func (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
215func (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
336func (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.
351func (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
364func (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
380type valueGetter func(*jsonw.Wrapper) (interface{}, error)
381
382func (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
391func getString(w *jsonw.Wrapper) (interface{}, error) {
392	return w.GetString()
393}
394
395func getBool(w *jsonw.Wrapper) (interface{}, error) {
396	return w.GetBool()
397}
398
399func getInt(w *jsonw.Wrapper) (interface{}, error) {
400	return w.GetInt()
401}
402
403func getFloat(w *jsonw.Wrapper) (interface{}, error) {
404	return w.GetFloat()
405}
406
407func (f *JSONFile) GetFilename() string {
408	return f.filename
409}
410
411func (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
417func (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
427func (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
437func (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
447func (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
457func (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
465func (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
477func (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
483func (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
489func (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
495func (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
501func (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
507func (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
520func isJSONNoSuchKeyError(err error) bool {
521	_, isJSONError := err.(*jsonw.Error)
522	return err != nil && isJSONError && strings.Contains(err.Error(), "no such key")
523}
524