Use enum classes within FileSystem
[WebKit-https.git] / Source / WebKit / UIProcess / ResourceLoadStatisticsPersistentStorage.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. 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 "ResourceLoadStatisticsPersistentStorage.h"
28
29 #include "Logging.h"
30 #include "WebResourceLoadStatisticsStore.h"
31 #include <WebCore/FileMonitor.h>
32 #include <WebCore/FileSystem.h>
33 #include <WebCore/KeyedCoding.h>
34 #include <WebCore/SharedBuffer.h>
35 #include <wtf/RunLoop.h>
36 #include <wtf/WorkQueue.h>
37 #include <wtf/threads/BinarySemaphore.h>
38
39 namespace WebKit {
40
41 constexpr Seconds minimumWriteInterval { 5_min };
42
43 using namespace WebCore;
44
45 static bool hasFileChangedSince(const String& path, WallTime since)
46 {
47     ASSERT(!RunLoop::isMain());
48     time_t modificationTime;
49     if (!FileSystem::getFileModificationTime(path, modificationTime))
50         return true;
51
52     return WallTime::fromRawSeconds(modificationTime) > since;
53 }
54
55 static std::unique_ptr<KeyedDecoder> createDecoderForFile(const String& path)
56 {
57     ASSERT(!RunLoop::isMain());
58     auto handle = FileSystem::openAndLockFile(path, FileSystem::FileOpenMode::Read);
59     if (handle == FileSystem::invalidPlatformFileHandle)
60         return nullptr;
61
62     long long fileSize = 0;
63     if (!FileSystem::getFileSize(handle, fileSize)) {
64         FileSystem::unlockAndCloseFile(handle);
65         return nullptr;
66     }
67
68     size_t bytesToRead;
69     if (!WTF::convertSafely(fileSize, bytesToRead)) {
70         FileSystem::unlockAndCloseFile(handle);
71         return nullptr;
72     }
73
74     Vector<char> buffer(bytesToRead);
75     size_t totalBytesRead = FileSystem::readFromFile(handle, buffer.data(), buffer.size());
76
77     FileSystem::unlockAndCloseFile(handle);
78
79     if (totalBytesRead != bytesToRead)
80         return nullptr;
81
82     return KeyedDecoder::decoder(reinterpret_cast<const uint8_t*>(buffer.data()), buffer.size());
83 }
84
85 ResourceLoadStatisticsPersistentStorage::ResourceLoadStatisticsPersistentStorage(WebResourceLoadStatisticsStore& store, const String& storageDirectoryPath)
86     : m_memoryStore(store)
87     , m_storageDirectoryPath(storageDirectoryPath)
88     , m_asyncWriteTimer(RunLoop::main(), this, &ResourceLoadStatisticsPersistentStorage::asyncWriteTimerFired)
89 {
90 }
91
92 void ResourceLoadStatisticsPersistentStorage::initialize()
93 {
94     ASSERT(!RunLoop::isMain());
95     populateMemoryStoreFromDisk();
96     startMonitoringDisk();
97 }
98
99 ResourceLoadStatisticsPersistentStorage::~ResourceLoadStatisticsPersistentStorage()
100 {
101     ASSERT(!m_hasPendingWrite);
102 }
103
104 String ResourceLoadStatisticsPersistentStorage::storageDirectoryPath() const
105 {
106     return m_storageDirectoryPath.isolatedCopy();
107 }
108
109 String ResourceLoadStatisticsPersistentStorage::resourceLogFilePath() const
110 {
111     String storagePath = storageDirectoryPath();
112     if (storagePath.isEmpty())
113         return emptyString();
114
115     return FileSystem::pathByAppendingComponent(storagePath, "full_browsing_session_resourceLog.plist");
116 }
117
118 void ResourceLoadStatisticsPersistentStorage::startMonitoringDisk()
119 {
120     ASSERT(!RunLoop::isMain());
121     if (m_fileMonitor)
122         return;
123
124     String resourceLogPath = resourceLogFilePath();
125     if (resourceLogPath.isEmpty())
126         return;
127
128     m_fileMonitor = std::make_unique<FileMonitor>(resourceLogPath, m_memoryStore.statisticsQueue(), [this] (FileMonitor::FileChangeType type) {
129         ASSERT(!RunLoop::isMain());
130         switch (type) {
131         case FileMonitor::FileChangeType::Modification:
132             refreshMemoryStoreFromDisk();
133             break;
134         case FileMonitor::FileChangeType::Removal:
135             m_memoryStore.clearInMemory();
136             m_fileMonitor = nullptr;
137             monitorDirectoryForNewStatistics();
138             break;
139         }
140     });
141 }
142
143 void ResourceLoadStatisticsPersistentStorage::monitorDirectoryForNewStatistics()
144 {
145     String storagePath = storageDirectoryPath();
146     ASSERT(!storagePath.isEmpty());
147
148     if (!FileSystem::fileExists(storagePath)) {
149         if (!FileSystem::makeAllDirectories(storagePath)) {
150             RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: Failed to create directory path %s", storagePath.utf8().data());
151             return;
152         }
153     }
154
155     m_fileMonitor = std::make_unique<FileMonitor>(storagePath, m_memoryStore.statisticsQueue(), [this] (FileMonitor::FileChangeType type) {
156         ASSERT(!RunLoop::isMain());
157         if (type == FileMonitor::FileChangeType::Removal) {
158             // Directory was removed!
159             m_fileMonitor = nullptr;
160             return;
161         }
162
163         String resourceLogPath = resourceLogFilePath();
164         ASSERT(!resourceLogPath.isEmpty());
165
166         if (!FileSystem::fileExists(resourceLogPath))
167             return;
168
169         m_fileMonitor = nullptr;
170
171         refreshMemoryStoreFromDisk();
172         startMonitoringDisk();
173     });
174 }
175
176 void ResourceLoadStatisticsPersistentStorage::stopMonitoringDisk()
177 {
178     ASSERT(!RunLoop::isMain());
179     m_fileMonitor = nullptr;
180 }
181
182 // This is called when the file changes on disk.
183 void ResourceLoadStatisticsPersistentStorage::refreshMemoryStoreFromDisk()
184 {
185     ASSERT(!RunLoop::isMain());
186
187     String filePath = resourceLogFilePath();
188     if (filePath.isEmpty())
189         return;
190
191     // We sometimes see file changed events from before our load completed (we start
192     // reading at the first change event, but we might receive a series of events related
193     // to the same file operation). Catch this case to avoid reading overly often.
194     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime))
195         return;
196
197     WallTime readTime = WallTime::now();
198
199     auto decoder = createDecoderForFile(filePath);
200     if (!decoder)
201         return;
202
203     m_memoryStore.mergeWithDataFromDecoder(*decoder);
204     m_lastStatisticsFileSyncTime = readTime;
205 }
206
207 void ResourceLoadStatisticsPersistentStorage::populateMemoryStoreFromDisk()
208 {
209     ASSERT(!RunLoop::isMain());
210
211     String filePath = resourceLogFilePath();
212     if (filePath.isEmpty() || !FileSystem::fileExists(filePath)) {
213         m_memoryStore.grandfatherExistingWebsiteData();
214         monitorDirectoryForNewStatistics();
215         return;
216     }
217
218     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime)) {
219         // No need to grandfather in this case.
220         return;
221     }
222
223     WallTime readTime = WallTime::now();
224
225     auto decoder = createDecoderForFile(filePath);
226     if (!decoder) {
227         m_memoryStore.grandfatherExistingWebsiteData();
228         return;
229     }
230
231     ASSERT_WITH_MESSAGE(m_memoryStore.isEmpty(), "This is the initial import so the store should be empty");
232     m_memoryStore.mergeWithDataFromDecoder(*decoder);
233
234     m_lastStatisticsFileSyncTime = readTime;
235
236     m_memoryStore.logTestingEvent(ASCIILiteral("PopulatedWithoutGrandfathering"));
237 }
238
239 void ResourceLoadStatisticsPersistentStorage::asyncWriteTimerFired()
240 {
241     ASSERT(RunLoop::isMain());
242     m_memoryStore.statisticsQueue().dispatch([this] () mutable {
243         writeMemoryStoreToDisk();
244     });
245 }
246
247 void ResourceLoadStatisticsPersistentStorage::writeMemoryStoreToDisk()
248 {
249     ASSERT(!RunLoop::isMain());
250
251     m_hasPendingWrite = false;
252     stopMonitoringDisk();
253
254     auto encoder = m_memoryStore.createEncoderFromData();
255     auto rawData = encoder->finishEncoding();
256     if (!rawData)
257         return;
258
259     auto storagePath = storageDirectoryPath();
260     if (!storagePath.isEmpty()) {
261         FileSystem::makeAllDirectories(storagePath);
262         excludeFromBackup();
263     }
264
265     auto handle = FileSystem::openAndLockFile(resourceLogFilePath(), FileSystem::FileOpenMode::Write);
266     if (handle == FileSystem::invalidPlatformFileHandle)
267         return;
268
269     int64_t writtenBytes = FileSystem::writeToFile(handle, rawData->data(), rawData->size());
270     FileSystem::unlockAndCloseFile(handle);
271
272     if (writtenBytes != static_cast<int64_t>(rawData->size()))
273         RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: We only wrote %d out of %zu bytes to disk", static_cast<unsigned>(writtenBytes), rawData->size());
274
275     m_lastStatisticsFileSyncTime = WallTime::now();
276     m_lastStatisticsWriteTime = MonotonicTime::now();
277
278     startMonitoringDisk();
279 }
280
281 void ResourceLoadStatisticsPersistentStorage::scheduleOrWriteMemoryStore(ForceImmediateWrite forceImmediateWrite)
282 {
283     ASSERT(!RunLoop::isMain());
284
285     auto timeSinceLastWrite = MonotonicTime::now() - m_lastStatisticsWriteTime;
286     if (forceImmediateWrite != ForceImmediateWrite::Yes && timeSinceLastWrite < minimumWriteInterval) {
287         if (!m_hasPendingWrite) {
288             m_hasPendingWrite = true;
289             Seconds delay = minimumWriteInterval - timeSinceLastWrite + 1_s;
290             RunLoop::main().dispatch([this, protectedThis = makeRef(*this), delay] {
291                 m_asyncWriteTimer.startOneShot(delay);
292             });
293         }
294         return;
295     }
296
297     writeMemoryStoreToDisk();
298 }
299
300 void ResourceLoadStatisticsPersistentStorage::clear()
301 {
302     ASSERT(!RunLoop::isMain());
303     String filePath = resourceLogFilePath();
304     if (filePath.isEmpty())
305         return;
306
307     stopMonitoringDisk();
308
309     if (!FileSystem::deleteFile(filePath))
310         RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: Unable to delete statistics file: %s", filePath.utf8().data());
311 }
312
313 void ResourceLoadStatisticsPersistentStorage::finishAllPendingWorkSynchronously()
314 {
315     m_asyncWriteTimer.stop();
316
317     BinarySemaphore semaphore;
318     // Make sure any pending work in our queue is finished before we terminate.
319     m_memoryStore.statisticsQueue().dispatch([&semaphore, this] {
320         // Write final file state to disk.
321         if (m_hasPendingWrite)
322             writeMemoryStoreToDisk();
323         semaphore.signal();
324     });
325     semaphore.wait(WallTime::infinity());
326 }
327
328 void ResourceLoadStatisticsPersistentStorage::ref()
329 {
330     m_memoryStore.ref();
331 }
332
333 void ResourceLoadStatisticsPersistentStorage::deref()
334 {
335     m_memoryStore.deref();
336 }
337
338 #if !PLATFORM(IOS)
339 void ResourceLoadStatisticsPersistentStorage::excludeFromBackup() const
340 {
341 }
342 #endif
343
344 } // namespace WebKit