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 file,
5  * You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "DirectoryLockImpl.h"
8 
9 #include "mozilla/ReverseIterator.h"
10 #include "mozilla/dom/quota/Client.h"
11 #include "mozilla/dom/quota/QuotaManager.h"
12 
13 namespace mozilla::dom::quota {
14 
DirectoryLockImpl(MovingNotNull<RefPtr<QuotaManager>> aQuotaManager,const Nullable<PersistenceType> & aPersistenceType,const nsACString & aSuffix,const nsACString & aGroup,const OriginScope & aOriginScope,const Nullable<Client::Type> & aClientType,const bool aExclusive,const bool aInternal,const ShouldUpdateLockIdTableFlag aShouldUpdateLockIdTableFlag)15 DirectoryLockImpl::DirectoryLockImpl(
16     MovingNotNull<RefPtr<QuotaManager>> aQuotaManager,
17     const Nullable<PersistenceType>& aPersistenceType,
18     const nsACString& aSuffix, const nsACString& aGroup,
19     const OriginScope& aOriginScope, const Nullable<Client::Type>& aClientType,
20     const bool aExclusive, const bool aInternal,
21     const ShouldUpdateLockIdTableFlag aShouldUpdateLockIdTableFlag)
22     : mQuotaManager(std::move(aQuotaManager)),
23       mPersistenceType(aPersistenceType),
24       mSuffix(aSuffix),
25       mGroup(aGroup),
26       mOriginScope(aOriginScope),
27       mClientType(aClientType),
28       mId(mQuotaManager->GenerateDirectoryLockId()),
29       mExclusive(aExclusive),
30       mInternal(aInternal),
31       mShouldUpdateLockIdTable(aShouldUpdateLockIdTableFlag ==
32                                ShouldUpdateLockIdTableFlag::Yes),
33       mRegistered(false) {
34   AssertIsOnOwningThread();
35   MOZ_ASSERT_IF(aOriginScope.IsOrigin(), !aOriginScope.GetOrigin().IsEmpty());
36   MOZ_ASSERT_IF(!aInternal, !aPersistenceType.IsNull());
37   MOZ_ASSERT_IF(!aInternal,
38                 aPersistenceType.Value() != PERSISTENCE_TYPE_INVALID);
39   MOZ_ASSERT_IF(!aInternal, !aGroup.IsEmpty());
40   MOZ_ASSERT_IF(!aInternal, aOriginScope.IsOrigin());
41   MOZ_ASSERT_IF(!aInternal, !aClientType.IsNull());
42   MOZ_ASSERT_IF(!aInternal, aClientType.Value() < Client::TypeMax());
43 }
44 
~DirectoryLockImpl()45 DirectoryLockImpl::~DirectoryLockImpl() {
46   AssertIsOnOwningThread();
47 
48   // We must call UnregisterDirectoryLock before unblocking other locks because
49   // UnregisterDirectoryLock also updates the origin last access time and the
50   // access flag (if the last lock for given origin is unregistered). One of the
51   // blocked locks could be requested by the clear/reset operation which stores
52   // cached information about origins in storage.sqlite. So if the access flag
53   // is not updated before unblocking the lock for reset/clear, we might store
54   // invalid information which can lead to omitting origin initialization during
55   // next temporary storage initialization.
56   if (mRegistered) {
57     mQuotaManager->UnregisterDirectoryLock(*this);
58   }
59 
60   MOZ_ASSERT(!mRegistered);
61 
62   for (NotNull<RefPtr<DirectoryLockImpl>> blockingLock : mBlocking) {
63     blockingLock->MaybeUnblock(*this);
64   }
65 
66   mBlocking.Clear();
67 }
68 
69 #ifdef DEBUG
70 
AssertIsOnOwningThread() const71 void DirectoryLockImpl::AssertIsOnOwningThread() const {
72   mQuotaManager->AssertIsOnOwningThread();
73 }
74 
75 #endif  // DEBUG
76 
Overlaps(const DirectoryLockImpl & aLock) const77 bool DirectoryLockImpl::Overlaps(const DirectoryLockImpl& aLock) const {
78   AssertIsOnOwningThread();
79 
80   // If the persistence types don't overlap, the op can proceed.
81   if (!aLock.mPersistenceType.IsNull() && !mPersistenceType.IsNull() &&
82       aLock.mPersistenceType.Value() != mPersistenceType.Value()) {
83     return false;
84   }
85 
86   // If the origin scopes don't overlap, the op can proceed.
87   bool match = aLock.mOriginScope.Matches(mOriginScope);
88   if (!match) {
89     return false;
90   }
91 
92   // If the client types don't overlap, the op can proceed.
93   if (!aLock.mClientType.IsNull() && !mClientType.IsNull() &&
94       aLock.mClientType.Value() != mClientType.Value()) {
95     return false;
96   }
97 
98   // Otherwise, when all attributes overlap (persistence type, origin scope and
99   // client type) the op must wait.
100   return true;
101 }
102 
MustWaitFor(const DirectoryLockImpl & aLock) const103 bool DirectoryLockImpl::MustWaitFor(const DirectoryLockImpl& aLock) const {
104   AssertIsOnOwningThread();
105 
106   // Waiting is never required if the ops in comparison represent shared locks.
107   if (!aLock.mExclusive && !mExclusive) {
108     return false;
109   }
110 
111   // Wait if the ops overlap.
112   return Overlaps(aLock);
113 }
114 
NotifyOpenListener()115 void DirectoryLockImpl::NotifyOpenListener() {
116   AssertIsOnOwningThread();
117 
118   if (mInvalidated) {
119     (*mOpenListener)->DirectoryLockFailed();
120   } else {
121     (*mOpenListener)
122         ->DirectoryLockAcquired(static_cast<UniversalDirectoryLock*>(this));
123   }
124 
125   mOpenListener.destroy();
126 
127   mQuotaManager->RemovePendingDirectoryLock(*this);
128 
129   mPending.Flip();
130 }
131 
Acquire(RefPtr<OpenDirectoryListener> aOpenListener)132 void DirectoryLockImpl::Acquire(RefPtr<OpenDirectoryListener> aOpenListener) {
133   AssertIsOnOwningThread();
134   MOZ_ASSERT(aOpenListener);
135 
136   mOpenListener.init(WrapNotNullUnchecked(std::move(aOpenListener)));
137 
138   mQuotaManager->AddPendingDirectoryLock(*this);
139 
140   // See if this lock needs to wait.
141   bool blocked = false;
142 
143   // XXX It is probably unnecessary to iterate this in reverse order.
144   for (DirectoryLockImpl* const existingLock :
145        Reversed(mQuotaManager->mDirectoryLocks)) {
146     if (MustWaitFor(*existingLock)) {
147       existingLock->AddBlockingLock(*this);
148       AddBlockedOnLock(*existingLock);
149       blocked = true;
150     }
151   }
152 
153   mQuotaManager->RegisterDirectoryLock(*this);
154 
155   // Otherwise, notify the open listener immediately.
156   if (!blocked) {
157     NotifyOpenListener();
158     return;
159   }
160 
161   if (!mExclusive || !mInternal) {
162     return;
163   }
164 
165   // All the locks that block this new exclusive internal lock need to be
166   // invalidated. We also need to notify clients to abort operations for them.
167   QuotaManager::DirectoryLockIdTableArray lockIds;
168   lockIds.SetLength(Client::TypeMax());
169 
170   const auto& blockedOnLocks = GetBlockedOnLocks();
171   MOZ_ASSERT(!blockedOnLocks.IsEmpty());
172 
173   for (DirectoryLockImpl* blockedOnLock : blockedOnLocks) {
174     if (!blockedOnLock->IsInternal()) {
175       blockedOnLock->Invalidate();
176 
177       // Clients don't have to handle pending locks. Invalidation is sufficient
178       // in that case (once a lock is ready and the listener needs to be
179       // notified, we will call DirectoryLockFailed instead of
180       // DirectoryLockAcquired which should release any remaining references to
181       // the lock).
182       if (!blockedOnLock->IsPending()) {
183         lockIds[blockedOnLock->ClientType()].Put(blockedOnLock->Id());
184       }
185     }
186   }
187 
188   mQuotaManager->AbortOperationsForLocks(lockIds);
189 }
190 
AcquireImmediately()191 void DirectoryLockImpl::AcquireImmediately() {
192   AssertIsOnOwningThread();
193 
194 #ifdef DEBUG
195   for (const DirectoryLockImpl* const existingLock :
196        mQuotaManager->mDirectoryLocks) {
197     MOZ_ASSERT(!MustWaitFor(*existingLock));
198   }
199 #endif
200 
201   mQuotaManager->RegisterDirectoryLock(*this);
202 }
203 
SpecializeForClient(PersistenceType aPersistenceType,const quota::OriginMetadata & aOriginMetadata,Client::Type aClientType) const204 RefPtr<ClientDirectoryLock> DirectoryLockImpl::SpecializeForClient(
205     PersistenceType aPersistenceType,
206     const quota::OriginMetadata& aOriginMetadata,
207     Client::Type aClientType) const {
208   AssertIsOnOwningThread();
209   MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
210   MOZ_ASSERT(!aOriginMetadata.mGroup.IsEmpty());
211   MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
212   MOZ_ASSERT(aClientType < Client::TypeMax());
213   MOZ_ASSERT(!mOpenListener);
214   MOZ_ASSERT(mBlockedOn.IsEmpty());
215 
216   if (NS_WARN_IF(mExclusive)) {
217     return nullptr;
218   }
219 
220   RefPtr<DirectoryLockImpl> lock = Create(
221       mQuotaManager, Nullable<PersistenceType>(aPersistenceType),
222       aOriginMetadata.mSuffix, aOriginMetadata.mGroup,
223       OriginScope::FromOrigin(aOriginMetadata.mOrigin),
224       Nullable<Client::Type>(aClientType),
225       /* aExclusive */ false, mInternal, ShouldUpdateLockIdTableFlag::Yes);
226   if (NS_WARN_IF(!Overlaps(*lock))) {
227     return nullptr;
228   }
229 
230 #ifdef DEBUG
231   for (DirectoryLockImpl* const existingLock :
232        Reversed(mQuotaManager->mDirectoryLocks)) {
233     if (existingLock != this && !existingLock->MustWaitFor(*this)) {
234       MOZ_ASSERT(!existingLock->MustWaitFor(*lock));
235     }
236   }
237 #endif
238 
239   for (const auto& blockedLock : mBlocking) {
240     if (blockedLock->MustWaitFor(*lock)) {
241       lock->AddBlockingLock(*blockedLock);
242       blockedLock->AddBlockedOnLock(*lock);
243     }
244   }
245 
246   mQuotaManager->RegisterDirectoryLock(*lock);
247 
248   if (mInvalidated) {
249     lock->Invalidate();
250   }
251 
252   return lock;
253 }
254 
Log() const255 void DirectoryLockImpl::Log() const {
256   AssertIsOnOwningThread();
257 
258   if (!QM_LOG_TEST()) {
259     return;
260   }
261 
262   QM_LOG(("DirectoryLockImpl [%p]", this));
263 
264   nsCString persistenceType;
265   if (mPersistenceType.IsNull()) {
266     persistenceType.AssignLiteral("null");
267   } else {
268     persistenceType.Assign(PersistenceTypeToString(mPersistenceType.Value()));
269   }
270   QM_LOG(("  mPersistenceType: %s", persistenceType.get()));
271 
272   QM_LOG(("  mGroup: %s", mGroup.get()));
273 
274   nsCString originScope;
275   if (mOriginScope.IsOrigin()) {
276     originScope.AssignLiteral("origin:");
277     originScope.Append(mOriginScope.GetOrigin());
278   } else if (mOriginScope.IsPrefix()) {
279     originScope.AssignLiteral("prefix:");
280     originScope.Append(mOriginScope.GetOriginNoSuffix());
281   } else if (mOriginScope.IsPattern()) {
282     originScope.AssignLiteral("pattern:");
283     // Can't call GetJSONPattern since it only works on the main thread.
284   } else {
285     MOZ_ASSERT(mOriginScope.IsNull());
286     originScope.AssignLiteral("null");
287   }
288   QM_LOG(("  mOriginScope: %s", originScope.get()));
289 
290   const auto clientType = mClientType.IsNull()
291                               ? nsAutoCString{"null"_ns}
292                               : Client::TypeToText(mClientType.Value());
293   QM_LOG(("  mClientType: %s", clientType.get()));
294 
295   nsCString blockedOnString;
296   for (auto blockedOn : mBlockedOn) {
297     blockedOnString.Append(
298         nsPrintfCString(" [%p]", static_cast<void*>(blockedOn)));
299   }
300   QM_LOG(("  mBlockedOn:%s", blockedOnString.get()));
301 
302   QM_LOG(("  mExclusive: %d", mExclusive));
303 
304   QM_LOG(("  mInternal: %d", mInternal));
305 
306   QM_LOG(("  mInvalidated: %d", static_cast<bool>(mInvalidated)));
307 
308   for (auto blockedOn : mBlockedOn) {
309     blockedOn->Log();
310   }
311 }
312 
313 }  // namespace mozilla::dom::quota
314