1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_DataStorage_h
8 #define mozilla_DataStorage_h
9 
10 #include "mozilla/Atomics.h"
11 #include "mozilla/ipc/FileDescriptor.h"
12 #include "mozilla/MemoryReporting.h"
13 #include "mozilla/Monitor.h"
14 #include "mozilla/Mutex.h"
15 #include "mozilla/StaticPtr.h"
16 #include "nsCOMPtr.h"
17 #include "nsDataHashtable.h"
18 #include "nsIObserver.h"
19 #include "nsITimer.h"
20 #include "nsRefPtrHashtable.h"
21 #include "nsString.h"
22 
23 class psm_DataStorageTest;
24 
25 namespace mozilla {
26 class DataStorageMemoryReporter;
27 
28 namespace dom {
29 class ContentChild;
30 }  // namespace dom
31 
32 namespace psm {
33 class DataStorageEntry;
34 class DataStorageItem;
35 }  // namespace psm
36 
37 /**
38  * DataStorage is a threadsafe, generic, narrow string-based hash map that
39  * persists data on disk and additionally handles temporary and private data.
40  * However, if used in a context where there is no profile directory, data
41  * will not be persisted.
42  *
43  * Its lifecycle is as follows:
44  * - Allocate with a filename (this is or will eventually be a file in the
45  *   profile directory, if the profile exists).
46  * - Call Init() from the main thread. This spins off an asynchronous read
47  *   of the backing file.
48  * - Eventually observers of the topic "data-storage-ready" will be notified
49  *   with the backing filename as the data in the notification when this
50  *   has completed.
51  * - Should the profile directory not be available, (e.g. in xpcshell),
52  *   DataStorage will not initially read any persistent data. The
53  *   "data-storage-ready" event will still be emitted. This follows semantics
54  *   similar to the permission manager and allows tests that test
55  *   unrelated components to proceed without a profile.
56  * - When any persistent data changes, a timer is initialized that will
57  *   eventually asynchronously write all persistent data to the backing file.
58  *   When this happens, observers will be notified with the topic
59  *   "data-storage-written" and the backing filename as the data.
60  *   It is possible to receive a "data-storage-written" event while there exist
61  *   pending persistent data changes. However, those changes will cause the
62  *   timer to be reinitialized and another "data-storage-written" event will
63  *   be sent.
64  * - When any DataStorage observes the topic "profile-before-change" in
65  *   anticipation of shutdown, all persistent data for all DataStorage instances
66  *   is synchronously written to the appropriate backing file. The worker thread
67  *   responsible for these writes is then disabled to prevent further writes to
68  *   that file (the delayed-write timer is cancelled when this happens). Note
69  *   that the "worker thread" is actually a single thread shared between all
70  *   DataStorage instances. If "profile-before-change" is not observed, this
71  *   happens upon observing "xpcom-shutdown-threads".
72  * - For testing purposes, the preference "test.datastorage.write_timer_ms" can
73  *   be set to cause the asynchronous writing of data to happen more quickly.
74  * - To prevent unbounded memory and disk use, the number of entries in each
75  *   table is limited to 1024. Evictions are handled in by a modified LRU scheme
76  *   (see implementation comments).
77  * - NB: Instances of DataStorage have long lifetimes because they are strong
78  *   observers of events and won't go away until the observer service does.
79  *
80  * For each key/value:
81  * - The key must be a non-empty string containing no instances of '\t' or '\n'
82  *   (this is a limitation of how the data is stored and will be addressed in
83  *   the future).
84  * - The key must have a length no more than 256.
85  * - The value must not contain '\n' and must have a length no more than 1024.
86  *   (the length limits are to prevent unbounded disk and memory usage)
87  */
88 
89 /**
90  * Data that is DataStorage_Persistent is saved on disk. DataStorage_Temporary
91  * and DataStorage_Private are not saved. DataStorage_Private is meant to
92  * only be set and accessed from private contexts. It will be cleared upon
93  * observing the event "last-pb-context-exited".
94  */
95 enum DataStorageType {
96   DataStorage_Persistent,
97   DataStorage_Temporary,
98   DataStorage_Private
99 };
100 
101 enum class DataStorageClass {
102 #define DATA_STORAGE(_) _,
103 #include "mozilla/DataStorageList.h"
104 #undef DATA_STORAGE
105 };
106 
107 class DataStorage : public nsIObserver {
108   typedef psm::DataStorageItem DataStorageItem;
109 
110  public:
111   NS_DECL_THREADSAFE_ISUPPORTS
112   NS_DECL_NSIOBSERVER
113 
114   // If there is a profile directory, there is or will eventually be a file
115   // by the name specified by aFilename there.
116   static already_AddRefed<DataStorage> Get(DataStorageClass aFilename);
117 
118   // Initializes the DataStorage. Must be called before using.
119   // aItems is used in the content process and the socket process to initialize
120   // a cache of the items received from the parent process over IPC. nullptr
121   // must be passed for the parent process.
122   // aWriteFd is only used in the socket process for now. The FileDesc is opened
123   // in parent process and send to socket process. The data storage instance in
124   // socket process will use this FD to write data to the backing file.
125   nsresult Init(
126       const nsTArray<mozilla::psm::DataStorageItem>* aItems,
127       mozilla::ipc::FileDescriptor aWriteFd = mozilla::ipc::FileDescriptor());
128 
129   // This function is used to create the file descriptor asynchronously. The FD
130   // will be sent via the callback. Note that after this call, mBackingFile will
131   // be nulled to prevent parent process to access the file.
132   nsresult AsyncTakeFileDesc(
133       std::function<void(mozilla::ipc::FileDescriptor&&)>&& aResolver);
134 
135   // Given a key and a type of data, returns a value. Returns an empty string if
136   // the key is not present for that type of data. If Get is called before the
137   // "data-storage-ready" event is observed, it will block. NB: It is not
138   // currently possible to differentiate between missing data and data that is
139   // the empty string.
140   nsCString Get(const nsCString& aKey, DataStorageType aType);
141   // Give a key, value, and type of data, adds an entry as appropriate.
142   // Updates existing entries.
143   nsresult Put(const nsCString& aKey, const nsCString& aValue,
144                DataStorageType aType);
145   // Given a key and type of data, removes an entry if present.
146   void Remove(const nsCString& aKey, DataStorageType aType);
147   // Removes all entries of all types of data.
148   nsresult Clear();
149 
150   // Read all file names that we know about.
151   static void GetAllFileNames(nsTArray<nsString>& aItems);
152 
153   // Read all child process data that we know about.
154   static void GetAllChildProcessData(
155       nsTArray<mozilla::psm::DataStorageEntry>& aEntries);
156 
157   // Read all of the data items.
158   void GetAll(nsTArray<DataStorageItem>* aItems);
159 
160   // Set the cached copy of our DataStorage entries in the content process.
161   static void SetCachedStorageEntries(
162       const nsTArray<mozilla::psm::DataStorageEntry>& aEntries);
163 
164   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
165 
166   // Return true if this data storage is ready to be used.
167   bool IsReady();
168 
169  private:
170   explicit DataStorage(const nsString& aFilename);
171   virtual ~DataStorage();
172 
173   static already_AddRefed<DataStorage> GetFromRawFileName(
174       const nsString& aFilename);
175 
176   friend class ::psm_DataStorageTest;
177   friend class mozilla::dom::ContentChild;
178   friend class mozilla::DataStorageMemoryReporter;
179 
180   class Writer;
181   class Reader;
182   class Opener;
183 
184   class Entry {
185    public:
186     Entry();
187     bool UpdateScore();
188 
189     uint32_t mScore;
190     int32_t mLastAccessed;  // the last accessed time in days since the epoch
191     nsCString mValue;
192   };
193 
194   // Utility class for scanning tables for an entry to evict.
195   class KeyAndEntry {
196    public:
197     nsCString mKey;
198     Entry mEntry;
199   };
200 
201   typedef nsDataHashtable<nsCStringHashKey, Entry> DataStorageTable;
202   typedef nsRefPtrHashtable<nsStringHashKey, DataStorage> DataStorages;
203 
204   void WaitForReady();
205   nsresult AsyncWriteData(const MutexAutoLock& aProofOfLock);
206   nsresult AsyncReadData(const MutexAutoLock& aProofOfLock);
207   nsresult AsyncSetTimer(const MutexAutoLock& aProofOfLock);
208   nsresult DispatchShutdownTimer(const MutexAutoLock& aProofOfLock);
209 
210   static nsresult ValidateKeyAndValue(const nsCString& aKey,
211                                       const nsCString& aValue);
212   static void TimerCallback(nsITimer* aTimer, void* aClosure);
213   void SetTimer();
214   void ShutdownTimer();
215   void NotifyObservers(const char* aTopic);
216 
217   static void PrefChanged(const char* aPref, void* aSelf);
218   void PrefChanged(const char* aPref);
219 
220   bool GetInternal(const nsCString& aKey, Entry* aEntry, DataStorageType aType,
221                    const MutexAutoLock& aProofOfLock);
222   nsresult PutInternal(const nsCString& aKey, Entry& aEntry,
223                        DataStorageType aType,
224                        const MutexAutoLock& aProofOfLock);
225   void MaybeEvictOneEntry(DataStorageType aType,
226                           const MutexAutoLock& aProofOfLock);
227   DataStorageTable& GetTableForType(DataStorageType aType,
228                                     const MutexAutoLock& aProofOfLock);
229 
230   void ReadAllFromTable(DataStorageType aType,
231                         nsTArray<DataStorageItem>* aItems,
232                         const MutexAutoLock& aProofOfLock);
233 
234   Mutex mMutex;  // This mutex protects access to the following members:
235   DataStorageTable mPersistentDataTable;
236   DataStorageTable mTemporaryDataTable;
237   DataStorageTable mPrivateDataTable;
238   nsCOMPtr<nsIFile> mBackingFile;
239   nsCOMPtr<nsITimer>
240       mTimer;            // All uses after init must be on the worker thread
241   uint32_t mTimerDelay;  // in milliseconds
242   bool mPendingWrite;    // true if a write is needed but hasn't been dispatched
243   bool mShuttingDown;
244   // (End list of members protected by mMutex)
245 
246   mozilla::Atomic<bool> mInitCalled;  // Indicates that Init() has been called.
247 
248   Monitor mReadyMonitor;  // Do not acquire this at the same time as mMutex.
249   bool mReady;  // Indicates that saved data has been read and Get can proceed.
250 
251   const nsString mFilename;
252 
253   mozilla::ipc::FileDescriptor mWriteFd;
254 
255   static StaticAutoPtr<DataStorages> sDataStorages;
256 };
257 
258 }  // namespace mozilla
259 
260 #endif  // mozilla_DataStorage_h
261