1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
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 #include "mozilla/MemoryReporting.h"
8 #include "nsCache.h"
9 #include <limits.h>
10 
11 #include "nscore.h"
12 #include "nsDiskCacheBinding.h"
13 #include "nsCacheService.h"
14 
15 using namespace mozilla;
16 
17 /******************************************************************************
18  *  static hash table callback functions
19  *
20  *****************************************************************************/
21 struct HashTableEntry : PLDHashEntryHdr {
22   nsDiskCacheBinding *mBinding;
23 };
24 
HashKey(const void * key)25 static PLDHashNumber HashKey(const void *key) {
26   return (PLDHashNumber)NS_PTR_TO_INT32(key);
27 }
28 
MatchEntry(const PLDHashEntryHdr * header,const void * key)29 static bool MatchEntry(const PLDHashEntryHdr *header, const void *key) {
30   HashTableEntry *hashEntry = (HashTableEntry *)header;
31   return (hashEntry->mBinding->mRecord.HashNumber() ==
32           (PLDHashNumber)NS_PTR_TO_INT32(key));
33 }
34 
MoveEntry(PLDHashTable *,const PLDHashEntryHdr * src,PLDHashEntryHdr * dst)35 static void MoveEntry(PLDHashTable * /* table */, const PLDHashEntryHdr *src,
36                       PLDHashEntryHdr *dst) {
37   ((HashTableEntry *)dst)->mBinding = ((HashTableEntry *)src)->mBinding;
38 }
39 
ClearEntry(PLDHashTable *,PLDHashEntryHdr * header)40 static void ClearEntry(PLDHashTable * /* table */, PLDHashEntryHdr *header) {
41   ((HashTableEntry *)header)->mBinding = nullptr;
42 }
43 
44 /******************************************************************************
45  *  Utility Functions
46  *****************************************************************************/
GetCacheEntryBinding(nsCacheEntry * entry)47 nsDiskCacheBinding *GetCacheEntryBinding(nsCacheEntry *entry) {
48   return (nsDiskCacheBinding *)entry->Data();
49 }
50 
51 /******************************************************************************
52  *  nsDiskCacheBinding
53  *****************************************************************************/
54 
NS_IMPL_ISUPPORTS0(nsDiskCacheBinding)55 NS_IMPL_ISUPPORTS0(nsDiskCacheBinding)
56 
57 nsDiskCacheBinding::nsDiskCacheBinding(nsCacheEntry *entry,
58                                        nsDiskCacheRecord *record)
59     : mCacheEntry(entry), mStreamIO(nullptr), mDeactivateEvent(nullptr) {
60   NS_ASSERTION(record->ValidRecord(), "bad record");
61   PR_INIT_CLIST(this);
62   mRecord = *record;
63   mDoomed = entry->IsDoomed();
64   mGeneration = record->Generation();  // 0 == uninitialized, or data & meta
65                                        // using block files
66 }
67 
~nsDiskCacheBinding()68 nsDiskCacheBinding::~nsDiskCacheBinding() {
69   // Grab the cache lock since the binding is stored in nsCacheEntry::mData
70   // and it is released using nsCacheService::ReleaseObject_Locked() which
71   // releases the object outside the cache lock.
72   nsCacheServiceAutoLock lock;
73 
74   NS_ASSERTION(PR_CLIST_IS_EMPTY(this), "binding deleted while still on list");
75   if (!PR_CLIST_IS_EMPTY(this))
76     PR_REMOVE_LINK(this);  // XXX why are we still on a list?
77 
78   // sever streamIO/binding link
79   if (mStreamIO) {
80     if (NS_FAILED(mStreamIO->ClearBinding()))
81       nsCacheService::DoomEntry(mCacheEntry);
82     NS_RELEASE(mStreamIO);
83   }
84 }
85 
EnsureStreamIO()86 nsresult nsDiskCacheBinding::EnsureStreamIO() {
87   if (!mStreamIO) {
88     mStreamIO = new nsDiskCacheStreamIO(this);
89     if (!mStreamIO) return NS_ERROR_OUT_OF_MEMORY;
90     NS_ADDREF(mStreamIO);
91   }
92   return NS_OK;
93 }
94 
95 /******************************************************************************
96  *  nsDiskCacheBindery
97  *
98  *  Keeps track of bound disk cache entries to detect for collisions.
99  *
100  *****************************************************************************/
101 
102 const PLDHashTableOps nsDiskCacheBindery::ops = {HashKey, MatchEntry, MoveEntry,
103                                                  ClearEntry};
104 
nsDiskCacheBindery()105 nsDiskCacheBindery::nsDiskCacheBindery()
106     : table(&ops, sizeof(HashTableEntry), kInitialTableLength),
107       initialized(false) {}
108 
~nsDiskCacheBindery()109 nsDiskCacheBindery::~nsDiskCacheBindery() { Reset(); }
110 
Init()111 void nsDiskCacheBindery::Init() {
112   table.ClearAndPrepareForLength(kInitialTableLength);
113   initialized = true;
114 }
115 
Reset()116 void nsDiskCacheBindery::Reset() {
117   if (initialized) {
118     table.ClearAndPrepareForLength(kInitialTableLength);
119     initialized = false;
120   }
121 }
122 
CreateBinding(nsCacheEntry * entry,nsDiskCacheRecord * record)123 nsDiskCacheBinding *nsDiskCacheBindery::CreateBinding(
124     nsCacheEntry *entry, nsDiskCacheRecord *record) {
125   NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
126   nsCOMPtr<nsISupports> data = entry->Data();
127   if (data) {
128     NS_ERROR("cache entry already has bind data");
129     return nullptr;
130   }
131 
132   nsDiskCacheBinding *binding = new nsDiskCacheBinding(entry, record);
133   if (!binding) return nullptr;
134 
135   // give ownership of the binding to the entry
136   entry->SetData(binding);
137 
138   // add binding to collision detection system
139   nsresult rv = AddBinding(binding);
140   if (NS_FAILED(rv)) {
141     entry->SetData(nullptr);
142     return nullptr;
143   }
144 
145   return binding;
146 }
147 
148 /**
149  *  FindActiveEntry :  to find active colliding entry so we can doom it
150  */
FindActiveBinding(uint32_t hashNumber)151 nsDiskCacheBinding *nsDiskCacheBindery::FindActiveBinding(uint32_t hashNumber) {
152   NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
153   // find hash entry for key
154   auto hashEntry = static_cast<HashTableEntry *>(
155       table.Search((void *)(uintptr_t)hashNumber));
156   if (!hashEntry) return nullptr;
157 
158   // walk list looking for active entry
159   NS_ASSERTION(hashEntry->mBinding, "hash entry left with no binding");
160   nsDiskCacheBinding *binding = hashEntry->mBinding;
161   while (binding->mCacheEntry->IsDoomed()) {
162     binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
163     if (binding == hashEntry->mBinding) return nullptr;
164   }
165   return binding;
166 }
167 
168 /**
169  *  AddBinding
170  *
171  *  Called from FindEntry() if we read an entry off of disk
172  *      - it may already have a generation number
173  *      - a generation number conflict is an error
174  *
175  *  Called from BindEntry()
176  *      - a generation number needs to be assigned
177  */
AddBinding(nsDiskCacheBinding * binding)178 nsresult nsDiskCacheBindery::AddBinding(nsDiskCacheBinding *binding) {
179   NS_ENSURE_ARG_POINTER(binding);
180   NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
181 
182   // find hash entry for key
183   auto hashEntry = static_cast<HashTableEntry *>(
184       table.Add((void *)(uintptr_t)binding->mRecord.HashNumber(), fallible));
185   if (!hashEntry) return NS_ERROR_OUT_OF_MEMORY;
186 
187   if (hashEntry->mBinding == nullptr) {
188     hashEntry->mBinding = binding;
189     if (binding->mGeneration == 0)
190       binding->mGeneration = 1;  // if generation uninitialized, set it to 1
191 
192     return NS_OK;
193   }
194 
195   // insert binding in generation order
196   nsDiskCacheBinding *p = hashEntry->mBinding;
197   bool calcGeneration =
198       (binding->mGeneration == 0);  // do we need to calculate generation?
199   if (calcGeneration)
200     binding->mGeneration = 1;  // initialize to 1 if uninitialized
201   while (1) {
202     if (binding->mGeneration < p->mGeneration) {
203       // here we are
204       PR_INSERT_BEFORE(binding, p);
205       if (hashEntry->mBinding == p) hashEntry->mBinding = binding;
206       break;
207     }
208 
209     if (binding->mGeneration == p->mGeneration) {
210       if (calcGeneration)
211         ++binding->mGeneration;  // try the next generation
212       else {
213         NS_ERROR("### disk cache: generations collide!");
214         return NS_ERROR_UNEXPECTED;
215       }
216     }
217 
218     p = (nsDiskCacheBinding *)PR_NEXT_LINK(p);
219     if (p == hashEntry->mBinding) {
220       // end of line: insert here or die
221       p = (nsDiskCacheBinding *)PR_PREV_LINK(
222           p);  // back up and check generation
223       if (p->mGeneration == 255) {
224         NS_WARNING("### disk cache: generation capacity at full");
225         return NS_ERROR_UNEXPECTED;
226       }
227       PR_INSERT_BEFORE(binding, hashEntry->mBinding);
228       break;
229     }
230   }
231   return NS_OK;
232 }
233 
234 /**
235  *  RemoveBinding :  remove binding from collision detection on deactivation
236  */
RemoveBinding(nsDiskCacheBinding * binding)237 void nsDiskCacheBindery::RemoveBinding(nsDiskCacheBinding *binding) {
238   NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
239   if (!initialized) return;
240 
241   void *key = (void *)(uintptr_t)binding->mRecord.HashNumber();
242   auto hashEntry =
243       static_cast<HashTableEntry *>(table.Search((void *)(uintptr_t)key));
244   if (!hashEntry) {
245     NS_WARNING("### disk cache: binding not in hashtable!");
246     return;
247   }
248 
249   if (binding == hashEntry->mBinding) {
250     if (PR_CLIST_IS_EMPTY(binding)) {
251       // remove this hash entry
252       table.Remove((void *)(uintptr_t)binding->mRecord.HashNumber());
253       return;
254 
255     } else {
256       // promote next binding to head, and unlink this binding
257       hashEntry->mBinding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
258     }
259   }
260   PR_REMOVE_AND_INIT_LINK(binding);
261 }
262 
263 /**
264  * ActiveBindings: return true if any bindings have open descriptors.
265  */
ActiveBindings()266 bool nsDiskCacheBindery::ActiveBindings() {
267   NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
268   if (!initialized) return false;
269 
270   for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
271     auto entry = static_cast<HashTableEntry *>(iter.Get());
272     nsDiskCacheBinding *binding = entry->mBinding;
273     nsDiskCacheBinding *head = binding;
274     do {
275       if (binding->IsActive()) {
276         return true;
277       }
278       binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
279     } while (binding != head);
280   }
281 
282   return false;
283 }
284 
285 /**
286  * SizeOfExcludingThis: return the amount of heap memory (bytes) being used by
287  * the bindery.
288  */
SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)289 size_t nsDiskCacheBindery::SizeOfExcludingThis(
290     mozilla::MallocSizeOf aMallocSizeOf) {
291   NS_ASSERTION(initialized, "nsDiskCacheBindery not initialized");
292   if (!initialized) return 0;
293 
294   size_t size = 0;
295 
296   for (auto iter = table.Iter(); !iter.Done(); iter.Next()) {
297     auto entry = static_cast<HashTableEntry *>(iter.Get());
298     nsDiskCacheBinding *binding = entry->mBinding;
299 
300     nsDiskCacheBinding *head = binding;
301     do {
302       size += aMallocSizeOf(binding);
303       if (binding->mStreamIO) {
304         size += binding->mStreamIO->SizeOfIncludingThis(aMallocSizeOf);
305       }
306 
307       // No good way to get at mDeactivateEvent internals for proper
308       // size, so we use this as an estimate.
309       if (binding->mDeactivateEvent) {
310         size += aMallocSizeOf(binding->mDeactivateEvent);
311       }
312       binding = (nsDiskCacheBinding *)PR_NEXT_LINK(binding);
313     } while (binding != head);
314   }
315 
316   return size;
317 }
318