Don't use dispatch_semaphore in NetworkCacheStorage
[WebKit-https.git] / Source / WebKit2 / NetworkProcess / cache / NetworkCacheStorageCocoa.mm
1 /*
2  * Copyright (C) 2014-2015 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "NetworkCacheStorage.h"
28
29 #if ENABLE(NETWORK_CACHE)
30
31 #include "Logging.h"
32 #include "NetworkCacheCoders.h"
33 #include "NetworkCacheFileSystemPosix.h"
34 #include "NetworkCacheIOChannel.h"
35 #include <wtf/PageBlock.h>
36 #include <wtf/RandomNumber.h>
37 #include <wtf/RunLoop.h>
38 #include <wtf/text/CString.h>
39 #include <wtf/text/StringBuilder.h>
40
41 namespace WebKit {
42 namespace NetworkCache {
43
44 static const char networkCacheSubdirectory[] = "WebKitCache";
45 static const char versionDirectoryPrefix[] = "Version ";
46
47 std::unique_ptr<Storage> Storage::open(const String& cachePath)
48 {
49     ASSERT(RunLoop::isMain());
50
51     String networkCachePath = WebCore::pathByAppendingComponent(cachePath, networkCacheSubdirectory);
52     if (!WebCore::makeAllDirectories(networkCachePath))
53         return nullptr;
54     return std::unique_ptr<Storage>(new Storage(networkCachePath));
55 }
56
57 static String makeVersionedDirectoryPath(const String& baseDirectoryPath)
58 {
59     String versionSubdirectory = versionDirectoryPrefix + String::number(Storage::version);
60     return WebCore::pathByAppendingComponent(baseDirectoryPath, versionSubdirectory);
61 }
62
63 Storage::Storage(const String& baseDirectoryPath)
64     : m_baseDirectoryPath(baseDirectoryPath)
65     , m_directoryPath(makeVersionedDirectoryPath(baseDirectoryPath))
66     , m_ioQueue(WorkQueue::create("com.apple.WebKit.Cache.Storage", WorkQueue::Type::Concurrent))
67     , m_backgroundIOQueue(WorkQueue::create("com.apple.WebKit.Cache.Storage", WorkQueue::Type::Concurrent, WorkQueue::QOS::Background))
68 {
69     deleteOldVersions();
70     initialize();
71 }
72
73 void Storage::initialize()
74 {
75     ASSERT(RunLoop::isMain());
76
77     StringCapture cachePathCapture(m_directoryPath);
78
79     backgroundIOQueue().dispatch([this, cachePathCapture] {
80         String cachePath = cachePathCapture.string();
81         traverseCacheFiles(cachePath, [this](const String& fileName, const String& partitionPath) {
82             Key::HashType hash;
83             if (!Key::stringToHash(fileName, hash))
84                 return;
85             unsigned shortHash = Key::toShortHash(hash);
86             RunLoop::main().dispatch([this, shortHash] {
87                 m_contentsFilter.add(shortHash);
88             });
89             auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
90             long long fileSize = 0;
91             WebCore::getFileSize(filePath, fileSize);
92             m_approximateSize += fileSize;
93         });
94     });
95 }
96
97 static String directoryPathForKey(const Key& key, const String& cachePath)
98 {
99     ASSERT(!key.partition().isEmpty());
100     return WebCore::pathByAppendingComponent(cachePath, key.partition());
101 }
102
103 static String fileNameForKey(const Key& key)
104 {
105     return key.hashAsString();
106 }
107
108 static String filePathForKey(const Key& key, const String& cachePath)
109 {
110     return WebCore::pathByAppendingComponent(directoryPathForKey(key, cachePath), fileNameForKey(key));
111 }
112
113 static Ref<IOChannel> openFileForKey(const Key& key, IOChannel::Type type, const String& cachePath)
114 {
115     auto directoryPath = directoryPathForKey(key, cachePath);
116     auto filePath = WebCore::pathByAppendingComponent(directoryPath, fileNameForKey(key));
117     if (type == IOChannel::Type::Create)
118         WebCore::makeAllDirectories(directoryPath);
119     return IOChannel::open(filePath, type);
120 }
121
122 static unsigned hashData(const Data& data)
123 {
124     StringHasher hasher;
125     data.apply([&hasher](const uint8_t* data, size_t size) {
126         hasher.addCharacters(data, size);
127         return true;
128     });
129     return hasher.hash();
130 }
131
132 struct EntryMetaData {
133     EntryMetaData() { }
134     explicit EntryMetaData(const Key& key)
135         : cacheStorageVersion(Storage::version)
136         , key(key)
137     { }
138
139     unsigned cacheStorageVersion;
140     Key key;
141     std::chrono::milliseconds timeStamp;
142     unsigned headerChecksum;
143     uint64_t headerOffset;
144     uint64_t headerSize;
145     unsigned bodyChecksum;
146     uint64_t bodyOffset;
147     uint64_t bodySize;
148 };
149
150 static bool decodeEntryMetaData(EntryMetaData& metaData, const Data& fileData)
151 {
152     bool success = false;
153     fileData.apply([&metaData, &success](const uint8_t* data, size_t size) {
154         Decoder decoder(data, size);
155         if (!decoder.decode(metaData.cacheStorageVersion))
156             return false;
157         if (!decoder.decode(metaData.key))
158             return false;
159         if (!decoder.decode(metaData.timeStamp))
160             return false;
161         if (!decoder.decode(metaData.headerChecksum))
162             return false;
163         if (!decoder.decode(metaData.headerSize))
164             return false;
165         if (!decoder.decode(metaData.bodyChecksum))
166             return false;
167         if (!decoder.decode(metaData.bodySize))
168             return false;
169         if (!decoder.verifyChecksum())
170             return false;
171         metaData.headerOffset = decoder.currentOffset();
172         metaData.bodyOffset = WTF::roundUpToMultipleOf(pageSize(), metaData.headerOffset + metaData.headerSize);
173         success = true;
174         return false;
175     });
176     return success;
177 }
178
179 static bool decodeEntryHeader(const Data& fileData, EntryMetaData& metaData, Data& data)
180 {
181     if (!decodeEntryMetaData(metaData, fileData))
182         return false;
183     if (metaData.cacheStorageVersion != Storage::version)
184         return false;
185     if (metaData.headerOffset + metaData.headerSize > metaData.bodyOffset)
186         return false;
187
188     auto headerData = fileData.subrange(metaData.headerOffset, metaData.headerSize);
189     if (metaData.headerChecksum != hashData(headerData)) {
190         LOG(NetworkCacheStorage, "(NetworkProcess) header checksum mismatch");
191         return false;
192     }
193     data = { headerData };
194     return true;
195 }
196
197 static std::unique_ptr<Storage::Entry> decodeEntry(const Data& fileData, int fd, const Key& key)
198 {
199     EntryMetaData metaData;
200     Data headerData;
201     if (!decodeEntryHeader(fileData, metaData, headerData))
202         return nullptr;
203
204     if (metaData.key != key)
205         return nullptr;
206     if (metaData.bodyOffset + metaData.bodySize != fileData.size())
207         return nullptr;
208
209     auto bodyData = mapFile(fd, metaData.bodyOffset, metaData.bodySize);
210     if (bodyData.isNull()) {
211         LOG(NetworkCacheStorage, "(NetworkProcess) map failed");
212         return nullptr;
213     }
214
215     if (metaData.bodyChecksum != hashData(bodyData)) {
216         LOG(NetworkCacheStorage, "(NetworkProcess) data checksum mismatch");
217         return nullptr;
218     }
219
220     return std::make_unique<Storage::Entry>(Storage::Entry {
221         metaData.key,
222         metaData.timeStamp,
223         headerData,
224         bodyData
225     });
226 }
227
228 static Data encodeEntryMetaData(const EntryMetaData& entry)
229 {
230     Encoder encoder;
231
232     encoder << entry.cacheStorageVersion;
233     encoder << entry.key;
234     encoder << entry.timeStamp;
235     encoder << entry.headerChecksum;
236     encoder << entry.headerSize;
237     encoder << entry.bodyChecksum;
238     encoder << entry.bodySize;
239
240     encoder.encodeChecksum();
241
242     return Data(encoder.buffer(), encoder.bufferSize());
243 }
244
245 static Data encodeEntryHeader(const Storage::Entry& entry)
246 {
247     EntryMetaData metaData(entry.key);
248     metaData.timeStamp = entry.timeStamp;
249     metaData.headerChecksum = hashData(entry.header);
250     metaData.headerSize = entry.header.size();
251     metaData.bodyChecksum = hashData(entry.body);
252     metaData.bodySize = entry.body.size();
253
254     auto encodedMetaData = encodeEntryMetaData(metaData);
255     auto headerData = concatenate(encodedMetaData, entry.header);
256     if (!entry.body.size())
257         return { headerData };
258
259     size_t dataOffset = WTF::roundUpToMultipleOf(pageSize(), headerData.size());
260     Vector<uint8_t, 4096> filler(dataOffset - headerData.size(), 0);
261     Data alignmentData(filler.data(), filler.size());
262
263     return concatenate(headerData, alignmentData);
264 }
265
266 void Storage::removeEntry(const Key& key)
267 {
268     ASSERT(RunLoop::isMain());
269
270     // For simplicity we don't reduce m_approximateSize on removals caused by load or decode errors.
271     // The next cache shrink will update the size.
272
273     if (m_contentsFilter.mayContain(key.shortHash()))
274         m_contentsFilter.remove(key.shortHash());
275
276     StringCapture filePathCapture(filePathForKey(key, m_directoryPath));
277     backgroundIOQueue().dispatch([this, filePathCapture] {
278         WebCore::deleteFile(filePathCapture.string());
279     });
280 }
281
282 void Storage::dispatchReadOperation(const ReadOperation& read)
283 {
284     ASSERT(RunLoop::isMain());
285     ASSERT(m_activeReadOperations.contains(&read));
286
287     StringCapture cachePathCapture(m_directoryPath);
288     ioQueue().dispatch([this, &read, cachePathCapture] {
289         auto channel = openFileForKey(read.key, IOChannel::Type::Read, cachePathCapture.string());
290         int fd = channel->fileDescriptor();
291         channel->read(0, std::numeric_limits<size_t>::max(), [this, &read, fd](Data& fileData, int error) {
292             if (error) {
293                 removeEntry(read.key);
294                 read.completionHandler(nullptr);
295             } else {
296                 auto entry = decodeEntry(fileData, fd, read.key);
297                 bool success = read.completionHandler(WTF::move(entry));
298                 if (!success)
299                     removeEntry(read.key);
300             }
301
302             ASSERT(m_activeReadOperations.contains(&read));
303             m_activeReadOperations.remove(&read);
304             dispatchPendingReadOperations();
305
306             LOG(NetworkCacheStorage, "(NetworkProcess) read complete error=%d", error);
307         });
308     });
309 }
310
311 void Storage::dispatchPendingReadOperations()
312 {
313     ASSERT(RunLoop::isMain());
314
315     const int maximumActiveReadOperationCount = 5;
316
317     for (int priority = maximumRetrievePriority; priority >= 0; --priority) {
318         if (m_activeReadOperations.size() > maximumActiveReadOperationCount) {
319             LOG(NetworkCacheStorage, "(NetworkProcess) limiting parallel retrieves");
320             return;
321         }
322         auto& pendingRetrieveQueue = m_pendingReadOperationsByPriority[priority];
323         if (pendingRetrieveQueue.isEmpty())
324             continue;
325         auto readOperation = pendingRetrieveQueue.takeFirst();
326         auto& read = *readOperation;
327         m_activeReadOperations.add(WTF::move(readOperation));
328         dispatchReadOperation(read);
329     }
330 }
331
332 template <class T> bool retrieveFromMemory(const T& operations, const Key& key, Storage::RetrieveCompletionHandler& completionHandler)
333 {
334     for (auto& operation : operations) {
335         if (operation->entry.key == key) {
336             LOG(NetworkCacheStorage, "(NetworkProcess) found write operation in progress");
337             auto entry = operation->entry;
338             RunLoop::main().dispatch([entry, completionHandler] {
339                 completionHandler(std::make_unique<Storage::Entry>(entry));
340             });
341             return true;
342         }
343     }
344     return false;
345 }
346
347 void Storage::retrieve(const Key& key, unsigned priority, RetrieveCompletionHandler&& completionHandler)
348 {
349     ASSERT(RunLoop::isMain());
350     ASSERT(priority <= maximumRetrievePriority);
351     ASSERT(!key.isNull());
352
353     if (!m_maximumSize) {
354         completionHandler(nullptr);
355         return;
356     }
357
358     if (!m_contentsFilter.mayContain(key.shortHash())) {
359         completionHandler(nullptr);
360         return;
361     }
362
363     if (retrieveFromMemory(m_pendingWriteOperations, key, completionHandler))
364         return;
365     if (retrieveFromMemory(m_activeWriteOperations, key, completionHandler))
366         return;
367
368     m_pendingReadOperationsByPriority[priority].append(new ReadOperation { key, WTF::move(completionHandler) });
369     dispatchPendingReadOperations();
370 }
371
372 void Storage::store(const Entry& entry, StoreCompletionHandler&& completionHandler)
373 {
374     ASSERT(RunLoop::isMain());
375     ASSERT(!entry.key.isNull());
376
377     if (!m_maximumSize) {
378         completionHandler(false, { });
379         return;
380     }
381
382     m_pendingWriteOperations.append(new WriteOperation { entry, { }, WTF::move(completionHandler) });
383
384     // Add key to the filter already here as we do lookups from the pending operations too.
385     m_contentsFilter.add(entry.key.shortHash());
386
387     dispatchPendingWriteOperations();
388 }
389
390 void Storage::update(const Entry& updateEntry, const Entry& existingEntry, StoreCompletionHandler&& completionHandler)
391 {
392     ASSERT(RunLoop::isMain());
393     ASSERT(!existingEntry.key.isNull());
394     ASSERT(existingEntry.key == updateEntry.key);
395
396     if (!m_maximumSize) {
397         completionHandler(false, { });
398         return;
399     }
400
401     m_pendingWriteOperations.append(new WriteOperation { updateEntry, existingEntry, WTF::move(completionHandler) });
402
403     dispatchPendingWriteOperations();
404 }
405
406 void Storage::traverse(std::function<void (const Entry*)>&& traverseHandler)
407 {
408     StringCapture cachePathCapture(m_directoryPath);
409     ioQueue().dispatch([this, cachePathCapture, traverseHandler] {
410         String cachePath = cachePathCapture.string();
411         traverseCacheFiles(cachePath, [this, &traverseHandler](const String& fileName, const String& partitionPath) {
412             auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
413             auto channel = IOChannel::open(filePath, IOChannel::Type::Read);
414             const size_t headerReadSize = 16 << 10;
415             // FIXME: Traversal is slower than it should be due to lack of parallelism.
416             channel->readSync(0, headerReadSize, [this, &traverseHandler](Data& fileData, int) {
417                 EntryMetaData metaData;
418                 Data headerData;
419                 if (decodeEntryHeader(fileData, metaData, headerData)) {
420                     Entry entry { metaData.key, metaData.timeStamp, headerData, { } };
421                     traverseHandler(&entry);
422                 }
423             });
424         });
425         RunLoop::main().dispatch([this, traverseHandler] {
426             traverseHandler(nullptr);
427         });
428     });
429 }
430
431 void Storage::dispatchPendingWriteOperations()
432 {
433     ASSERT(RunLoop::isMain());
434
435     const int maximumActiveWriteOperationCount { 3 };
436
437     while (!m_pendingWriteOperations.isEmpty()) {
438         if (m_activeWriteOperations.size() >= maximumActiveWriteOperationCount) {
439             LOG(NetworkCacheStorage, "(NetworkProcess) limiting parallel writes");
440             return;
441         }
442         auto writeOperation = m_pendingWriteOperations.takeFirst();
443         auto& write = *writeOperation;
444         m_activeWriteOperations.add(WTF::move(writeOperation));
445
446         if (write.existingEntry && m_contentsFilter.mayContain(write.entry.key.shortHash())) {
447             dispatchHeaderWriteOperation(write);
448             continue;
449         }
450         dispatchFullWriteOperation(write);
451     }
452 }
453
454 void Storage::dispatchFullWriteOperation(const WriteOperation& write)
455 {
456     ASSERT(RunLoop::isMain());
457     ASSERT(m_activeWriteOperations.contains(&write));
458
459     if (!m_contentsFilter.mayContain(write.entry.key.shortHash()))
460         m_contentsFilter.add(write.entry.key.shortHash());
461
462     StringCapture cachePathCapture(m_directoryPath);
463     backgroundIOQueue().dispatch([this, &write, cachePathCapture] {
464         auto encodedHeader = encodeEntryHeader(write.entry);
465         auto headerAndBodyData = concatenate(encodedHeader, write.entry.body);
466
467         auto channel = openFileForKey(write.entry.key, IOChannel::Type::Create, cachePathCapture.string());
468         int fd = channel->fileDescriptor();
469         size_t bodyOffset = encodedHeader.size();
470
471         channel->write(0, headerAndBodyData, [this, &write, bodyOffset, fd](int error) {
472             LOG(NetworkCacheStorage, "(NetworkProcess) write complete error=%d", error);
473             if (error) {
474                 if (m_contentsFilter.mayContain(write.entry.key.shortHash()))
475                     m_contentsFilter.remove(write.entry.key.shortHash());
476             }
477             size_t bodySize = write.entry.body.size();
478             size_t totalSize = bodyOffset + bodySize;
479
480             m_approximateSize += totalSize;
481
482             bool shouldMapBody = !error && bodySize >= pageSize();
483             auto bodyMap = shouldMapBody ? mapFile(fd, bodyOffset, bodySize) : Data();
484
485             write.completionHandler(!error, bodyMap);
486
487             ASSERT(m_activeWriteOperations.contains(&write));
488             m_activeWriteOperations.remove(&write);
489             dispatchPendingWriteOperations();
490         });
491     });
492
493     shrinkIfNeeded();
494 }
495
496 void Storage::dispatchHeaderWriteOperation(const WriteOperation& write)
497 {
498     ASSERT(RunLoop::isMain());
499     ASSERT(write.existingEntry);
500     ASSERT(m_activeWriteOperations.contains(&write));
501     ASSERT(m_contentsFilter.mayContain(write.entry.key.shortHash()));
502
503     // Try to update the header of an existing entry.
504     StringCapture cachePathCapture(m_directoryPath);
505     backgroundIOQueue().dispatch([this, &write, cachePathCapture] {
506         auto headerData = encodeEntryHeader(write.entry);
507         auto existingHeaderData = encodeEntryHeader(write.existingEntry.value());
508
509         bool pageRoundedHeaderSizeChanged = headerData.size() != existingHeaderData.size();
510         if (pageRoundedHeaderSizeChanged) {
511             LOG(NetworkCacheStorage, "(NetworkProcess) page-rounded header size changed, storing full entry");
512             RunLoop::main().dispatch([this, &write] {
513                 dispatchFullWriteOperation(write);
514             });
515             return;
516         }
517
518         auto channel = openFileForKey(write.entry.key, IOChannel::Type::Write, cachePathCapture.string());
519         channel->write(0, headerData, [this, &write](int error) {
520             LOG(NetworkCacheStorage, "(NetworkProcess) update complete error=%d", error);
521
522             if (error)
523                 removeEntry(write.entry.key);
524
525             write.completionHandler(!error, { });
526
527             ASSERT(m_activeWriteOperations.contains(&write));
528             m_activeWriteOperations.remove(&write);
529             dispatchPendingWriteOperations();
530         });
531     });
532 }
533
534 void Storage::setMaximumSize(size_t size)
535 {
536     ASSERT(RunLoop::isMain());
537     m_maximumSize = size;
538
539     shrinkIfNeeded();
540 }
541
542 void Storage::clear()
543 {
544     ASSERT(RunLoop::isMain());
545     LOG(NetworkCacheStorage, "(NetworkProcess) clearing cache");
546
547     m_contentsFilter.clear();
548     m_approximateSize = 0;
549
550     StringCapture directoryPathCapture(m_directoryPath);
551
552     ioQueue().dispatch([directoryPathCapture] {
553         String directoryPath = directoryPathCapture.string();
554         traverseDirectory(directoryPath, DT_DIR, [&directoryPath](const String& subdirName) {
555             String subdirPath = WebCore::pathByAppendingComponent(directoryPath, subdirName);
556             traverseDirectory(subdirPath, DT_REG, [&subdirPath](const String& fileName) {
557                 WebCore::deleteFile(WebCore::pathByAppendingComponent(subdirPath, fileName));
558             });
559             WebCore::deleteEmptyDirectory(subdirPath);
560         });
561     });
562 }
563
564 void Storage::shrinkIfNeeded()
565 {
566     ASSERT(RunLoop::isMain());
567
568     static const double deletionProbability { 0.25 };
569
570     if (m_approximateSize <= m_maximumSize)
571         return;
572     if (m_shrinkInProgress)
573         return;
574     m_shrinkInProgress = true;
575
576     LOG(NetworkCacheStorage, "(NetworkProcess) shrinking cache approximateSize=%d, m_maximumSize=%d", static_cast<size_t>(m_approximateSize), m_maximumSize);
577
578     m_approximateSize = 0;
579
580     StringCapture cachePathCapture(m_directoryPath);
581     backgroundIOQueue().dispatch([this, cachePathCapture] {
582         String cachePath = cachePathCapture.string();
583         traverseCacheFiles(cachePath, [this](const String& fileName, const String& partitionPath) {
584             auto filePath = WebCore::pathByAppendingComponent(partitionPath, fileName);
585
586             bool shouldDelete = randomNumber() < deletionProbability;
587             if (!shouldDelete) {
588                 long long fileSize = 0;
589                 WebCore::getFileSize(filePath, fileSize);
590                 m_approximateSize += fileSize;
591                 return;
592             }
593
594             WebCore::deleteFile(filePath);
595             Key::HashType hash;
596             if (!Key::stringToHash(fileName, hash))
597                 return;
598             unsigned shortHash = Key::toShortHash(hash);
599             RunLoop::main().dispatch([this, shortHash] {
600                 if (m_contentsFilter.mayContain(shortHash))
601                     m_contentsFilter.remove(shortHash);
602             });
603         });
604
605         // Let system figure out if they are really empty.
606         traverseDirectory(cachePath, DT_DIR, [&cachePath](const String& subdirName) {
607             auto partitionPath = WebCore::pathByAppendingComponent(cachePath, subdirName);
608             WebCore::deleteEmptyDirectory(partitionPath);
609         });
610
611         m_shrinkInProgress = false;
612
613         LOG(NetworkCacheStorage, "(NetworkProcess) cache shrink completed approximateSize=%d", static_cast<size_t>(m_approximateSize));
614     });
615 }
616
617 void Storage::deleteOldVersions()
618 {
619     // Delete V1 cache.
620     StringCapture cachePathCapture(m_baseDirectoryPath);
621     backgroundIOQueue().dispatch([cachePathCapture] {
622         String cachePath = cachePathCapture.string();
623         traverseDirectory(cachePath, DT_DIR, [&cachePath](const String& subdirName) {
624             if (subdirName.startsWith(versionDirectoryPrefix))
625                 return;
626             String partitionPath = WebCore::pathByAppendingComponent(cachePath, subdirName);
627             traverseDirectory(partitionPath, DT_REG, [&partitionPath](const String& fileName) {
628                 WebCore::deleteFile(WebCore::pathByAppendingComponent(partitionPath, fileName));
629             });
630             WebCore::deleteEmptyDirectory(partitionPath);
631         });
632     });
633 }
634
635 }
636 }
637
638 #endif