summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/DataStorage.h
blob: a0e620643917880b952d8745e2251e7624297101 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_DataStorage_h
#define mozilla_DataStorage_h

#include "mozilla/Atomics.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Monitor.h"
#include "mozilla/Mutex.h"
#include "mozilla/StaticPtr.h"
#include "nsCOMPtr.h"
#include "nsTHashMap.h"
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"

class psm_DataStorageTest;

namespace mozilla {
class DataStorageMemoryReporter;
class TaskQueue;

/**
 * DataStorage is a threadsafe, generic, narrow string-based hash map that
 * persists data on disk and additionally handles temporary and private data.
 * However, if used in a context where there is no profile directory, data
 * will not be persisted.
 *
 * Its lifecycle is as follows:
 * - Allocate with a filename (this is or will eventually be a file in the
 *   profile directory, if the profile exists).
 * - Call Init() from the main thread. This spins off an asynchronous read
 *   of the backing file.
 * - Eventually observers of the topic "data-storage-ready" will be notified
 *   with the backing filename as the data in the notification when this
 *   has completed.
 * - Should the profile directory not be available, (e.g. in xpcshell),
 *   DataStorage will not initially read any persistent data. The
 *   "data-storage-ready" event will still be emitted. This follows semantics
 *   similar to the permission manager and allows tests that test unrelated
 *   components to proceed without a profile.
 * - A timer periodically fires on a background thread that checks if any
 *   persistent data has changed, and if so writes all persistent data to the
 *   backing file. When this happens, observers will be notified with the
 *   topic "data-storage-written" and the backing filename as the data.
 *   It is possible to receive a "data-storage-written" event while there exist
 *   pending persistent data changes. However, those changes will eventually be
 *   written when the timer fires again, and eventually another
 *   "data-storage-written" event will be sent.
 * - When a DataStorage instance observes the topic "profile-before-change" in
 *   anticipation of shutdown, all persistent data for that DataStorage is
 *   written to the backing file (this blocks the main thread). In the process
 *   of doing this, the background serial event target responsible for these
 *   writes is then shut down to prevent further writes to that file (the
 *   background timer is also cancelled when this happens).
 *   If "profile-before-change" is not observed, this happens upon observing
 *   "xpcom-shutdown-threads".
 * - For testing purposes, the preference "test.datastorage.write_timer_ms" can
 *   be set to cause the asynchronous writing of data to happen more quickly.
 * - To prevent unbounded memory and disk use, the number of entries in each
 *   table is limited to 1024. Evictions are handled in by a modified LRU scheme
 *   (see implementation comments).
 * - NB: Instances of DataStorage have long lifetimes because they are strong
 *   observers of events and won't go away until the observer service does.
 *
 * For each key/value:
 * - The key must be a non-empty string containing no instances of '\t' or '\n'
 *   (this is a limitation of how the data is stored and will be addressed in
 *   the future).
 * - The key must have a length no more than 256.
 * - The value must not contain '\n' and must have a length no more than 1024.
 *   (the length limits are to prevent unbounded disk and memory usage)
 */

/**
 * Data that is DataStorage_Persistent is saved on disk. DataStorage_Temporary
 * and DataStorage_Private are not saved. DataStorage_Private is meant to
 * only be set and accessed from private contexts. It will be cleared upon
 * observing the event "last-pb-context-exited".
 */
enum DataStorageType {
  DataStorage_Persistent,
  DataStorage_Temporary,
  DataStorage_Private
};

struct DataStorageItem final {
  nsCString key;
  nsCString value;
  DataStorageType type;
};

enum class DataStorageClass {
#define DATA_STORAGE(_) _,
#include "mozilla/DataStorageList.h"
#undef DATA_STORAGE
};

class DataStorage : public nsIObserver {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSIOBSERVER

  // If there is a profile directory, there is or will eventually be a file
  // by the name specified by aFilename there.
  static already_AddRefed<DataStorage> Get(DataStorageClass aFilename);

  // Initializes the DataStorage. Must be called before using.
  nsresult Init();

  // Given a key and a type of data, returns a value. Returns an empty string if
  // the key is not present for that type of data. If Get is called before the
  // "data-storage-ready" event is observed, it will block. NB: It is not
  // currently possible to differentiate between missing data and data that is
  // the empty string.
  nsCString Get(const nsCString& aKey, DataStorageType aType);
  // Give a key, value, and type of data, adds an entry as appropriate.
  // Updates existing entries.
  nsresult Put(const nsCString& aKey, const nsCString& aValue,
               DataStorageType aType);
  // Given a key and type of data, removes an entry if present.
  void Remove(const nsCString& aKey, DataStorageType aType);
  // Removes all entries of all types of data.
  nsresult Clear();

  // Read all of the data items.
  void GetAll(nsTArray<DataStorageItem>* aItems);

  size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);

  // Return true if this data storage is ready to be used.
  bool IsReady();

  void ArmTimer(const MutexAutoLock& aProofOfLock);
  void ShutdownTimer();

 private:
  explicit DataStorage(const nsString& aFilename);
  virtual ~DataStorage() = default;

  static already_AddRefed<DataStorage> GetFromRawFileName(
      const nsString& aFilename);

  friend class ::psm_DataStorageTest;
  friend class mozilla::DataStorageMemoryReporter;

  class Writer;
  class Reader;

  class Entry {
   public:
    Entry();
    bool UpdateScore();

    uint32_t mScore;
    int32_t mLastAccessed;  // the last accessed time in days since the epoch
    nsCString mValue;
  };

  // Utility class for scanning tables for an entry to evict.
  class KeyAndEntry {
   public:
    nsCString mKey;
    Entry mEntry;
  };

  typedef nsTHashMap<nsCStringHashKey, Entry> DataStorageTable;
  typedef nsRefPtrHashtable<nsStringHashKey, DataStorage> DataStorages;

  void WaitForReady();
  nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock);
  nsresult AsyncReadData(const MutexAutoLock& aProofOfLock);

  static nsresult ValidateKeyAndValue(const nsCString& aKey,
                                      const nsCString& aValue);
  static void TimerCallback(nsITimer* aTimer, void* aClosure);
  void NotifyObservers(const char* aTopic);

  bool GetInternal(const nsCString& aKey, Entry* aEntry, DataStorageType aType,
                   const MutexAutoLock& aProofOfLock);
  nsresult PutInternal(const nsCString& aKey, Entry& aEntry,
                       DataStorageType aType,
                       const MutexAutoLock& aProofOfLock);
  void MaybeEvictOneEntry(DataStorageType aType,
                          const MutexAutoLock& aProofOfLock);
  DataStorageTable& GetTableForType(DataStorageType aType,
                                    const MutexAutoLock& aProofOfLock);

  void ReadAllFromTable(DataStorageType aType,
                        nsTArray<DataStorageItem>* aItems,
                        const MutexAutoLock& aProofOfLock);

  Mutex mMutex;  // This mutex protects access to the following members:
  DataStorageTable mPersistentDataTable MOZ_GUARDED_BY(mMutex);
  DataStorageTable mTemporaryDataTable MOZ_GUARDED_BY(mMutex);
  DataStorageTable mPrivateDataTable MOZ_GUARDED_BY(mMutex);
  nsCOMPtr<nsIFile> mBackingFile MOZ_GUARDED_BY(mMutex);
  bool mPendingWrite MOZ_GUARDED_BY(
      mMutex);  // true if a write is needed but hasn't been dispatched
  bool mTimerArmed MOZ_GUARDED_BY(mMutex);
  bool mShuttingDown MOZ_GUARDED_BY(mMutex);
  RefPtr<TaskQueue> mBackgroundTaskQueue MOZ_GUARDED_BY(mMutex);
  // (End list of members protected by mMutex)

  nsCOMPtr<nsITimer> mTimer;

  mozilla::Atomic<bool> mInitCalled;  // Indicates that Init() has been called.
  uint32_t mTimerDelayMS;

  Monitor mReadyMonitor;  // Do not acquire this at the same time as mMutex.
  bool mReady MOZ_GUARDED_BY(mReadyMonitor);  // Indicates that saved data has
                                              // been read and Get can proceed.

  const nsString mFilename;

  static StaticAutoPtr<DataStorages> sDataStorages;
};

}  // namespace mozilla

#endif  // mozilla_DataStorage_h