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