Add release logging for CacheStorage::Engine disk related functions
[WebKit-https.git] / Source / WebKit / NetworkProcess / cache / CacheStorageEngine.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "CacheStorageEngine.h"
28
29 #include "Logging.h"
30 #include "NetworkCacheFileSystem.h"
31 #include "NetworkCacheIOChannel.h"
32 #include "NetworkProcess.h"
33 #include "WebsiteDataType.h"
34 #include <WebCore/CacheQueryOptions.h>
35 #include <WebCore/SecurityOrigin.h>
36 #include <pal/SessionID.h>
37 #include <wtf/CallbackAggregator.h>
38 #include <wtf/NeverDestroyed.h>
39 #include <wtf/text/StringBuilder.h>
40 #include <wtf/text/StringHash.h>
41
42 using namespace WebCore::DOMCacheEngine;
43 using namespace WebKit::NetworkCache;
44
45 namespace WebKit {
46
47 namespace CacheStorage {
48
49 static HashMap<PAL::SessionID, RefPtr<Engine>>& globalEngineMap()
50 {
51     static NeverDestroyed<HashMap<PAL::SessionID, RefPtr<Engine>>> map;
52
53     return map;
54 }
55
56 String Engine::cachesRootPath(const WebCore::ClientOrigin& origin)
57 {
58     if (!shouldPersist())
59         return { };
60
61     Key key(origin.topOrigin.toString(), origin.clientOrigin.toString(), { }, { }, salt());
62     return WebCore::FileSystem::pathByAppendingComponent(rootPath(), key.hashAsString());
63 }
64
65 Engine::~Engine()
66 {
67     for (auto& caches : m_caches.values())
68         caches->detach();
69 }
70
71 Engine& Engine::from(PAL::SessionID sessionID)
72 {
73     auto addResult = globalEngineMap().add(sessionID, nullptr);
74     if (addResult.isNewEntry)
75         addResult.iterator->value = Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID));
76     return *addResult.iterator->value;
77 }
78
79 void Engine::destroyEngine(PAL::SessionID sessionID)
80 {
81     ASSERT(sessionID != PAL::SessionID::defaultSessionID());
82     globalEngineMap().remove(sessionID);
83 }
84
85 void Engine::fetchEntries(PAL::SessionID sessionID, bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
86 {
87     from(sessionID).fetchEntries(shouldComputeSize, WTFMove(completionHandler));
88 }
89
90 Engine& Engine::defaultEngine()
91 {
92     auto sessionID = PAL::SessionID::defaultSessionID();
93     static NeverDestroyed<Ref<Engine>> defaultEngine = { Engine::create(NetworkProcess::singleton().cacheStorageDirectory(sessionID)) };
94     return defaultEngine.get();
95 }
96
97 Engine::Engine(String&& rootPath)
98     : m_rootPath(WTFMove(rootPath))
99 {
100     if (!m_rootPath.isNull())
101         m_ioQueue = WorkQueue::create("com.apple.WebKit.CacheStorageEngine.serialBackground", WorkQueue::Type::Serial, WorkQueue::QOS::Background);
102 }
103
104 void Engine::open(const WebCore::ClientOrigin& origin, const String& cacheName, CacheIdentifierCallback&& callback)
105 {
106     readCachesFromDisk(origin, [cacheName, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
107         if (!cachesOrError.has_value()) {
108             callback(makeUnexpected(cachesOrError.error()));
109             return;
110         }
111
112         cachesOrError.value().get().open(cacheName, WTFMove(callback));
113     });
114 }
115
116 void Engine::remove(uint64_t cacheIdentifier, CacheIdentifierCallback&& callback)
117 {
118     Caches* cachesToModify = nullptr;
119     for (auto& caches : m_caches.values()) {
120         auto* cacheToRemove = caches->find(cacheIdentifier);
121         if (cacheToRemove) {
122             cachesToModify = caches.get();
123             break;
124         }
125     }
126     if (!cachesToModify) {
127         callback(makeUnexpected(Error::Internal));
128         return;
129     }
130
131     cachesToModify->remove(cacheIdentifier, WTFMove(callback));
132 }
133
134 void Engine::retrieveCaches(const WebCore::ClientOrigin& origin, uint64_t updateCounter, CacheInfosCallback&& callback)
135 {
136     readCachesFromDisk(origin, [updateCounter, callback = WTFMove(callback)](CachesOrError&& cachesOrError) mutable {
137         if (!cachesOrError.has_value()) {
138             callback(makeUnexpected(cachesOrError.error()));
139             return;
140         }
141
142         cachesOrError.value().get().cacheInfos(updateCounter, WTFMove(callback));
143     });
144 }
145
146 void Engine::retrieveRecords(uint64_t cacheIdentifier, WebCore::URL&& url, RecordsCallback&& callback)
147 {
148     readCache(cacheIdentifier, [url = WTFMove(url), callback = WTFMove(callback)](CacheOrError&& result) mutable {
149         if (!result.has_value()) {
150             callback(makeUnexpected(result.error()));
151             return;
152         }
153         result.value().get().retrieveRecords(url, WTFMove(callback));
154     });
155 }
156
157 void Engine::putRecords(uint64_t cacheIdentifier, Vector<Record>&& records, RecordIdentifiersCallback&& callback)
158 {
159     readCache(cacheIdentifier, [records = WTFMove(records), callback = WTFMove(callback)](CacheOrError&& result) mutable {
160         if (!result.has_value()) {
161             callback(makeUnexpected(result.error()));
162             return;
163         }
164
165         result.value().get().put(WTFMove(records), WTFMove(callback));
166     });
167 }
168
169 void Engine::deleteMatchingRecords(uint64_t cacheIdentifier, WebCore::ResourceRequest&& request, WebCore::CacheQueryOptions&& options, RecordIdentifiersCallback&& callback)
170 {
171     readCache(cacheIdentifier, [request = WTFMove(request), options = WTFMove(options), callback = WTFMove(callback)](CacheOrError&& result) mutable {
172         if (!result.has_value()) {
173             callback(makeUnexpected(result.error()));
174             return;
175         }
176
177         result.value().get().remove(WTFMove(request), WTFMove(options), WTFMove(callback));
178     });
179 }
180
181 void Engine::initialize(Function<void(std::optional<Error>&&)>&& callback)
182 {
183     if (m_salt) {
184         callback(std::nullopt);
185         return;
186     }
187
188     if (!shouldPersist()) {
189         callback(std::nullopt);
190         return;
191     }
192
193     String saltPath = WebCore::FileSystem::pathByAppendingComponent(m_rootPath, ASCIILiteral("salt"));
194     m_ioQueue->dispatch([protectedThis = makeRef(*this), this, callback = WTFMove(callback), saltPath = WTFMove(saltPath)] () mutable {
195         WebCore::FileSystem::makeAllDirectories(m_rootPath);
196         RunLoop::main().dispatch([protectedThis = WTFMove(protectedThis), this, salt = readOrMakeSalt(saltPath), callback = WTFMove(callback)]() mutable {
197             if (!salt) {
198                 callback(Error::WriteDisk);
199                 return;
200             }
201             m_salt = WTFMove(salt);
202             callback(std::nullopt);
203         });
204     });
205 }
206
207 void Engine::readCachesFromDisk(const WebCore::ClientOrigin& origin, CachesCallback&& callback)
208 {
209     initialize([this, origin, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
210         auto& caches = m_caches.ensure(origin, [&origin, this] {
211             auto path = cachesRootPath(origin);
212             return Caches::create(*this, WebCore::ClientOrigin { origin }, WTFMove(path), NetworkProcess::singleton().cacheStoragePerOriginQuota());
213         }).iterator->value;
214
215         if (caches->isInitialized()) {
216             callback(std::reference_wrapper<Caches> { *caches });
217             return;
218         }
219
220         if (error) {
221             callback(makeUnexpected(error.value()));
222             return;
223         }
224
225         caches->initialize([callback = WTFMove(callback), caches = caches.copyRef()](std::optional<Error>&& error) mutable {
226             if (error) {
227                 callback(makeUnexpected(error.value()));
228                 return;
229             }
230
231             callback(std::reference_wrapper<Caches> { *caches });
232         });
233     });
234 }
235
236 void Engine::readCache(uint64_t cacheIdentifier, CacheCallback&& callback)
237 {
238     auto* cache = this->cache(cacheIdentifier);
239     if (!cache) {
240         callback(makeUnexpected(Error::Internal));
241         return;
242     }
243     if (!cache->isOpened()) {
244         cache->open([this, protectedThis = makeRef(*this), cacheIdentifier, callback = WTFMove(callback)](std::optional<Error>&& error) mutable {
245             if (error) {
246                 callback(makeUnexpected(error.value()));
247                 return;
248             }
249
250             auto* cache = this->cache(cacheIdentifier);
251             if (!cache) {
252                 callback(makeUnexpected(Error::Internal));
253                 return;
254             }
255             ASSERT(cache->isOpened());
256             callback(std::reference_wrapper<Cache> { *cache });
257         });
258         return;
259     }
260     callback(std::reference_wrapper<Cache> { *cache });
261 }
262
263 Cache* Engine::cache(uint64_t cacheIdentifier)
264 {
265     Cache* result = nullptr;
266     for (auto& caches : m_caches.values()) {
267         if ((result = caches->find(cacheIdentifier)))
268             break;
269     }
270     return result;
271 }
272
273 void Engine::writeFile(const String& filename, NetworkCache::Data&& data, WebCore::DOMCacheEngine::CompletionCallback&& callback)
274 {
275     if (!shouldPersist()) {
276         callback(std::nullopt);
277         return;
278     }
279
280     m_ioQueue->dispatch([callback = WTFMove(callback), data = WTFMove(data), filename = filename.isolatedCopy()] () mutable {
281         auto channel = IOChannel::open(filename, IOChannel::Type::Create);
282         channel->write(0, data, nullptr, [callback = WTFMove(callback)](int error) mutable {
283             ASSERT(RunLoop::isMain());
284             if (error) {
285                 RELEASE_LOG_ERROR(CacheStorage, "CacheStorage::Engine::writeFile failed with error %d", error);
286
287                 callback(Error::WriteDisk);
288                 return;
289             }
290             callback(std::nullopt);
291         });
292     });
293 }
294
295 void Engine::readFile(const String& filename, WTF::Function<void(const NetworkCache::Data&, int error)>&& callback)
296 {
297     if (!shouldPersist()) {
298         callback(Data { }, 0);
299         return;
300     }
301
302     m_ioQueue->dispatch([callback = WTFMove(callback), filename = filename.isolatedCopy()]() mutable {
303         auto channel = IOChannel::open(filename, IOChannel::Type::Read);
304         if (channel->fileDescriptor() < 0) {
305             RunLoop::main().dispatch([callback = WTFMove(callback)]() mutable {
306                 callback(Data { }, 0);
307             });
308             return;
309         }
310
311         channel->read(0, std::numeric_limits<size_t>::max(), nullptr, [callback = WTFMove(callback)](const Data& data, int error) mutable {
312             RELEASE_LOG_ERROR_IF(error, CacheStorage, "CacheStorage::Engine::readFile failed with error %d", error);
313
314             // FIXME: We should do the decoding in the background thread.
315             ASSERT(RunLoop::isMain());
316             callback(data, error);
317         });
318     });
319 }
320
321 void Engine::removeFile(const String& filename)
322 {
323     if (!shouldPersist())
324         return;
325
326     m_ioQueue->dispatch([filename = filename.isolatedCopy()]() mutable {
327         WebCore::FileSystem::deleteFile(filename);
328     });
329 }
330
331 class ReadOriginsTaskCounter : public RefCounted<ReadOriginsTaskCounter> {
332 public:
333     static Ref<ReadOriginsTaskCounter> create(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
334     {
335         return adoptRef(*new ReadOriginsTaskCounter(WTFMove(callback)));
336     }
337
338     ~ReadOriginsTaskCounter()
339     {
340         m_callback(WTFMove(m_entries));
341     }
342
343     void addOrigin(WebCore::SecurityOriginData&& origin, uint64_t size)
344     {
345         m_entries.append(WebsiteData::Entry { WTFMove(origin), WebsiteDataType::DOMCache, size });
346     }
347
348 private:
349     explicit ReadOriginsTaskCounter(WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& callback)
350         : m_callback(WTFMove(callback))
351     {
352     }
353
354     WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)> m_callback;
355     Vector<WebsiteData::Entry> m_entries;
356 };
357
358 void Engine::fetchEntries(bool shouldComputeSize, WTF::CompletionHandler<void(Vector<WebsiteData::Entry>)>&& completionHandler)
359 {
360     if (!shouldPersist()) {
361         auto entries = WTF::map(m_caches, [] (auto& pair) {
362             return WebsiteData::Entry { pair.value->origin().clientOrigin, WebsiteDataType::DOMCache, 0 };
363         });
364         completionHandler(WTFMove(entries));
365         return;
366     }
367
368     auto taskCounter = ReadOriginsTaskCounter::create(WTFMove(completionHandler));
369     for (auto& folderPath : WebCore::FileSystem::listDirectory(m_rootPath, "*")) {
370         if (!WebCore::FileSystem::fileIsDirectory(folderPath, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
371             continue;
372         Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [protectedThis = makeRef(*this), shouldComputeSize, taskCounter = taskCounter.copyRef()] (auto&& origin) mutable {
373             ASSERT(RunLoop::isMain());
374             if (!origin)
375                 return;
376
377             if (!shouldComputeSize) {
378                 taskCounter->addOrigin(WTFMove(origin->topOrigin), 0);
379                 taskCounter->addOrigin(WTFMove(origin->clientOrigin), 0);
380                 return;
381             }
382
383             protectedThis->readCachesFromDisk(origin.value(), [origin = origin.value(), taskCounter = WTFMove(taskCounter)] (CachesOrError&& result) mutable {
384                 if (!result.has_value())
385                     return;
386                 taskCounter->addOrigin(WTFMove(origin.topOrigin), 0);
387                 taskCounter->addOrigin(WTFMove(origin.clientOrigin), result.value().get().storageSize());
388             });
389         });
390     }
391 }
392
393 void Engine::clearAllCaches(CallbackAggregator& taskHandler)
394 {
395     for (auto& caches : m_caches.values())
396         caches->clear([taskHandler = makeRef(taskHandler)] { });
397
398     if (!shouldPersist())
399         return;
400
401     m_ioQueue->dispatch([path = m_rootPath.isolatedCopy(), taskHandler = makeRef(taskHandler)] {
402         for (auto& filename : WebCore::FileSystem::listDirectory(path, "*")) {
403             if (WebCore::FileSystem::fileIsDirectory(filename, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
404                 deleteDirectoryRecursively(filename);
405         }
406     });
407 }
408
409 void Engine::clearCachesForOrigin(const WebCore::SecurityOriginData& origin, CallbackAggregator& taskHandler)
410 {
411     for (auto& keyValue : m_caches) {
412         if (keyValue.key.topOrigin == origin || keyValue.key.clientOrigin == origin)
413             keyValue.value->clear([taskHandler = makeRef(taskHandler)] { });
414     }
415
416     if (!shouldPersist())
417         return;
418
419     for (auto& folderPath : WebCore::FileSystem::listDirectory(m_rootPath, "*")) {
420         if (!WebCore::FileSystem::fileIsDirectory(folderPath, WebCore::FileSystem::ShouldFollowSymbolicLinks::No))
421             continue;
422         Caches::retrieveOriginFromDirectory(folderPath, *m_ioQueue, [this, protectedThis = makeRef(*this), origin, taskHandler = makeRef(taskHandler), folderPath] (std::optional<WebCore::ClientOrigin>&& folderOrigin) mutable {
423             if (!folderOrigin)
424                 return;
425             if (folderOrigin->topOrigin != origin && folderOrigin->clientOrigin != origin)
426                 return;
427
428             ASSERT(folderPath == cachesRootPath(*folderOrigin));
429             m_ioQueue->dispatch([path = folderPath.isolatedCopy(), taskHandler = WTFMove(taskHandler)] {
430                 deleteDirectoryRecursively(path);
431             });
432         });
433     }
434 }
435
436 void Engine::clearMemoryRepresentation(const WebCore::ClientOrigin& origin, WebCore::DOMCacheEngine::CompletionCallback&& callback)
437 {
438     readCachesFromDisk(origin, [callback = WTFMove(callback)](CachesOrError&& result) {
439         if (!result.has_value()) {
440             callback(result.error());
441             return;
442         }
443         result.value().get().clearMemoryRepresentation();
444         callback(std::nullopt);
445     });
446 }
447
448 void Engine::lock(uint64_t cacheIdentifier)
449 {
450     auto& counter = m_cacheLocks.ensure(cacheIdentifier, []() {
451         return 0;
452     }).iterator->value;
453
454     ++counter;
455 }
456
457 void Engine::unlock(uint64_t cacheIdentifier)
458 {
459     auto lockCount = m_cacheLocks.find(cacheIdentifier);
460     if (lockCount == m_cacheLocks.end())
461         return;
462
463     ASSERT(lockCount->value);
464     if (--lockCount->value)
465         return;
466
467     auto* cache = this->cache(cacheIdentifier);
468     if (!cache)
469         return;
470
471     cache->dispose();
472 }
473
474 String Engine::representation()
475 {
476     bool isFirst = true;
477     StringBuilder builder;
478     builder.append("[");
479     for (auto& keyValue : m_caches) {
480         if (!isFirst)
481             builder.append(",");
482         isFirst = false;
483
484         builder.append("\n{ \"origin\" : { \"topOrigin\" : \"");
485         builder.append(keyValue.key.topOrigin.toString());
486         builder.append("\", \"clientOrigin\": \"");
487         builder.append(keyValue.key.clientOrigin.toString());
488         builder.append("\" }, \"caches\" : ");
489         keyValue.value->appendRepresentation(builder);
490         builder.append("}");
491     }
492     builder.append("\n]");
493     return builder.toString();
494 }
495
496 } // namespace CacheStorage
497
498 } // namespace WebKit