diff --git a/ipn/store.go b/ipn/store.go index 3bef012ba..a771784ad 100644 --- a/ipn/store.go +++ b/ipn/store.go @@ -68,8 +68,9 @@ func CurrentProfileKey(userID string) StateKey { // StateStore persists state, and produces it back on request. type StateStore interface { - // ReadState returns the bytes associated with ID. Returns (nil, - // ErrStateNotExist) if the ID doesn't have associated state. + // ReadState returns the bytes associated with ID. + // It returns (nil, ErrStateNotExist) if the ID doesn't have associated state. + // The returned value must not be mutated. ReadState(id StateKey) ([]byte, error) // WriteState saves bs as the state associated with ID. // diff --git a/ipn/store/stores.go b/ipn/store/stores.go index 8bf3a24b0..9cef1eaf6 100644 --- a/ipn/store/stores.go +++ b/ipn/store/stores.go @@ -173,16 +173,22 @@ func (s *FileStore) ReadState(id ipn.StateKey) ([]byte, error) { } // WriteState implements the StateStore interface. -func (s *FileStore) WriteState(id ipn.StateKey, bs []byte) error { +func (s *FileStore) WriteState(id ipn.StateKey, bs []byte) (err error) { s.mu.Lock() defer s.mu.Unlock() - if bytes.Equal(s.cache[id], bs) { + bs0 := s.cache[id] + if bytes.Equal(bs0, bs) { return nil } + defer func() { + if err != nil { + s.cache[id] = bs0 + } + }() s.cache[id] = bytes.Clone(bs) - bs, err := json.MarshalIndent(s.cache, "", " ") + b, err := json.MarshalIndent(s.cache, "", " ") if err != nil { return err } - return atomicfile.WriteFile(s.path, bs, 0600) + return atomicfile.WriteFile(s.path, b, 0600) }