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