1/* 2 * Copyright (C) 2010 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 "DiskImageCacheIOS.h" 28 29#if ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS) 30 31#include "FileSystemIOS.h" 32#include "Logging.h" 33#include "WebCoreThread.h" 34#include "WebCoreThreadRun.h" 35#include <errno.h> 36#include <sys/mman.h> 37#include <wtf/NeverDestroyed.h> 38#include <wtf/text/CString.h> 39#include <wtf/text/WTFString.h> 40 41namespace WebCore { 42 43DiskImageCache& diskImageCache() 44{ 45 static NeverDestroyed<DiskImageCache> cache; 46 return cache; 47} 48 49DiskImageCache::Entry::Entry(SharedBuffer* buffer, DiskImageCacheId id) 50 : m_buffer(buffer) 51 , m_id(id) 52 , m_size(0) 53 , m_mapping(nullptr) 54{ 55 ASSERT(WebThreadIsCurrent()); 56 ASSERT(WebThreadIsLocked()); 57 ASSERT(m_buffer); 58 m_buffer->ref(); 59} 60 61DiskImageCache::Entry::~Entry() 62{ 63 ASSERT(WebThreadIsCurrent()); 64 ASSERT(WebThreadIsLocked()); 65 ASSERT(!m_buffer); 66 ASSERT(!m_mapping); 67} 68 69bool DiskImageCache::Entry::mapInternal(const String& path) 70{ 71 ASSERT(m_buffer); 72 ASSERT(!m_mapping); 73 74 m_path = path; 75 m_size = m_buffer->size(); 76 77 // Open the file for reading and writing. 78 PlatformFileHandle handle = open(m_path.utf8().data(), O_CREAT | O_RDWR | O_TRUNC, static_cast<mode_t>(0600)); 79 if (!isHandleValid(handle)) 80 return false; 81 82 // Write the data to the file. 83 if (writeToFileInternal(handle) == -1) { 84 closeFile(handle); 85 deleteFile(m_path); 86 return false; 87 } 88 89 // Seek back to the beginning. 90 if (seekFile(handle, 0, SeekFromBeginning) == -1) { 91 closeFile(handle); 92 deleteFile(m_path); 93 return false; 94 } 95 96 // Perform memory mapping for reading. 97 // NOTE: This must not conflict with the open() above, which must also open for reading. 98 m_mapping = mmap(nullptr, m_size, PROT_READ, MAP_SHARED, handle, 0); 99 closeFile(handle); 100 if (m_mapping == MAP_FAILED) { 101 LOG(DiskImageCache, "DiskImageCache: mapping failed (%d): (%s)", errno, strerror(errno)); 102 m_mapping = nullptr; 103 deleteFile(m_path); 104 return false; 105 } 106 107 return true; 108} 109 110void DiskImageCache::Entry::map(const String& path) 111{ 112 ASSERT(m_buffer); 113 ASSERT(!m_mapping); 114 DiskImageCache::Entry* thisEntry = this; 115 116 bool fileMapped = mapInternal(path); 117 if (!fileMapped) { 118 // Notify the buffer in the case of a failed mapping. 119 WebThreadRun(^{ 120 m_buffer->failedMemoryMap(); 121 m_buffer->deref(); 122 m_buffer = 0; 123 thisEntry->deref(); 124 }); 125 return; 126 } 127 128 // Notify the buffer in the case of a successful mapping. 129 // This should happen on the WebThread because this is being run 130 // asynchronously inside a dispatch queue. 131 WebThreadRun(^{ 132 m_buffer->markAsMemoryMapped(); 133 m_buffer->deref(); 134 m_buffer = 0; 135 thisEntry->deref(); 136 }); 137} 138 139void DiskImageCache::Entry::unmap() 140{ 141 if (!m_mapping) { 142 ASSERT(!m_size); 143 return; 144 } 145 146 if (munmap(m_mapping, m_size) == -1) 147 LOG_ERROR("DiskImageCache: Could not munmap a memory mapped file with id (%d)", m_id); 148 149 m_mapping = nullptr; 150 m_size = 0; 151} 152 153void DiskImageCache::Entry::removeFile() 154{ 155 ASSERT(!m_mapping); 156 ASSERT(!m_size); 157 158 if (!deleteFile(m_path)) 159 LOG_ERROR("DiskImageCache: Could not delete memory mapped file (%s)", m_path.utf8().data()); 160} 161 162void DiskImageCache::Entry::clearDataWithoutMapping() 163{ 164 ASSERT(!m_mapping); 165 ASSERT(m_buffer); 166 m_buffer->deref(); 167 m_buffer = 0; 168} 169 170int DiskImageCache::Entry::writeToFileInternal(PlatformFileHandle handle) 171{ 172 ASSERT(m_buffer); 173 int totalBytesWritten = 0; 174 175 const char* segment = nullptr; 176 unsigned position = 0; 177 while (unsigned length = m_buffer->getSomeData(segment, position)) { 178 int bytesWritten = writeToFile(handle, segment, length); 179 if (bytesWritten == -1) 180 return -1; 181 182 totalBytesWritten += bytesWritten; 183 position += length; 184 } 185 186 return totalBytesWritten; 187} 188 189 190DiskImageCache::DiskImageCache() 191 : m_enabled(false) 192 , m_size(0) 193 , m_maximumCacheSize(100 * 1024 * 1024) 194 , m_minimumImageSize(100 * 1024) 195 , m_nextAvailableId(DiskImageCache::invalidDiskCacheId + 1) 196{ 197} 198 199DiskImageCacheId DiskImageCache::writeItem(PassRefPtr<SharedBuffer> item) 200{ 201 if (!isEnabled() || !createDirectoryIfNeeded()) 202 return DiskImageCache::invalidDiskCacheId; 203 204 // We are already full, cannot add anything until something is removed. 205 if (isFull()) { 206 LOG(DiskImageCache, "DiskImageCache: could not process an item because the cache was full at (%d). The \"max\" being (%d)", m_size, m_maximumCacheSize); 207 return DiskImageCache::invalidDiskCacheId; 208 } 209 210 // Create an entry. 211 DiskImageCacheId id = nextAvailableId(); 212 RefPtr<SharedBuffer> buffer = item; 213 RefPtr<DiskImageCache::Entry> entry = DiskImageCache::Entry::create(buffer.get(), id); 214 m_table.add(id, entry); 215 216 // Create a temporary file path. 217 String path = temporaryFile(); 218 LOG(DiskImageCache, "DiskImageCache: creating entry (%d) at (%s)", id, path.utf8().data()); 219 if (path.isNull()) 220 return DiskImageCache::invalidDiskCacheId; 221 222 // The lifetime of the Entry is handled on the WebThread. 223 // Before we send to a dispatch queue we need to ref so 224 // that we are sure the object still exists. This call 225 // is balanced in the WebThreadRun inside of Entry::map. 226 // or the early return in this dispatch. 227 DiskImageCache::Entry* localEntryForBlock = entry.get(); 228 localEntryForBlock->ref(); 229 230 // Map to disk asynchronously. 231 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 232 // The cache became full since the time we were added to the queue. Don't map. 233 if (diskImageCache().isFull()) { 234 WebThreadRun(^{ 235 localEntryForBlock->clearDataWithoutMapping(); 236 localEntryForBlock->deref(); 237 }); 238 return; 239 } 240 241 localEntryForBlock->map(path); 242 243 // Update the size on a successful mapping. 244 if (localEntryForBlock->isMapped()) 245 diskImageCache().updateSize(localEntryForBlock->size()); 246 }); 247 248 return id; 249} 250 251void DiskImageCache::updateSize(unsigned delta) 252{ 253 MutexLocker lock(m_mutex); 254 m_size += delta; 255} 256 257void DiskImageCache::removeItem(DiskImageCacheId id) 258{ 259 LOG(DiskImageCache, "DiskImageCache: removeItem (%d)", id); 260 RefPtr<DiskImageCache::Entry> entry = m_table.get(id); 261 m_table.remove(id); 262 if (!entry->isMapped()) 263 return; 264 265 updateSize(-(entry->size())); 266 267 DiskImageCache::Entry *localEntryForBlock = entry.get(); 268 localEntryForBlock->ref(); 269 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 270 localEntryForBlock->unmap(); 271 localEntryForBlock->removeFile(); 272 WebThreadRun(^{ localEntryForBlock->deref(); }); 273 }); 274} 275 276void* DiskImageCache::dataForItem(DiskImageCacheId id) 277{ 278 ASSERT(id); 279 280 RefPtr<DiskImageCache::Entry> entry = m_table.get(id); 281 ASSERT(entry->isMapped()); 282 return entry->data(); 283} 284 285bool DiskImageCache::createDirectoryIfNeeded() 286{ 287 if (!m_cacheDirectory.isNull()) 288 return true; 289 290 m_cacheDirectory = temporaryDirectory(); 291 LOG(DiskImageCache, "DiskImageCache: Created temporary directory (%s)", m_cacheDirectory.utf8().data()); 292 if (m_cacheDirectory.isNull()) { 293 LOG_ERROR("DiskImageCache: could not create cache directory"); 294 return false; 295 } 296 297 if (m_client) 298 m_client->didCreateDiskImageCacheDirectory(m_cacheDirectory); 299 300 return true; 301} 302 303DiskImageCacheId DiskImageCache::nextAvailableId() 304{ 305 return m_nextAvailableId++; 306} 307 308String DiskImageCache::temporaryDirectory() 309{ 310 NSString *tempDiskCacheDirectory = createTemporaryDirectory(@"DiskImageCache"); 311 if (!tempDiskCacheDirectory) 312 LOG_ERROR("DiskImageCache: Could not create a temporary directory."); 313 314 return tempDiskCacheDirectory; 315} 316 317String DiskImageCache::temporaryFile() 318{ 319 NSString *tempFile = createTemporaryFile(m_cacheDirectory, @"tmp"); 320 if (!tempFile) 321 LOG_ERROR("DiskImageCache: Could not create a temporary file."); 322 323 return tempFile; 324} 325 326} // namespace WebCore 327 328#endif // ENABLE(DISK_IMAGE_CACHE) && PLATFORM(IOS) 329