Moved filesystem code out of WebResourceLoadStatisticsStore class
[WebKit.git] / Source / WebKit2 / UIProcess / Storage / 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 (!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 = openAndLockFile(path, OpenForRead);
59     if (handle == invalidPlatformFileHandle)
60         return nullptr;
61
62     long long fileSize = 0;
63     if (!getFileSize(handle, fileSize)) {
64         unlockAndCloseFile(handle);
65         return nullptr;
66     }
67
68     size_t bytesToRead;
69     if (!WTF::convertSafely(fileSize, bytesToRead)) {
70         unlockAndCloseFile(handle);
71         return nullptr;
72     }
73
74     Vector<char> buffer(bytesToRead);
75     size_t totalBytesRead = readFromFile(handle, buffer.data(), buffer.size());
76
77     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 {
89 }
90
91 void ResourceLoadStatisticsPersistentStorage::initialize()
92 {
93     ASSERT(!RunLoop::isMain());
94     populateMemoryStoreFromDisk();
95     startMonitoringDisk();
96 }
97
98 ResourceLoadStatisticsPersistentStorage::~ResourceLoadStatisticsPersistentStorage()
99 {
100     finishAllPendingWorkSynchronously();
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 = this->storageDirectoryPath();
112     if (storagePath.isEmpty())
113         return emptyString();
114
115     return 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             break;
138         }
139     });
140 }
141
142 void ResourceLoadStatisticsPersistentStorage::stopMonitoringDisk()
143 {
144     ASSERT(!RunLoop::isMain());
145     m_fileMonitor = nullptr;
146 }
147
148 // This is called when the file changes on disk.
149 void ResourceLoadStatisticsPersistentStorage::refreshMemoryStoreFromDisk()
150 {
151     ASSERT(!RunLoop::isMain());
152
153     String filePath = resourceLogFilePath();
154     if (filePath.isEmpty())
155         return;
156
157     // We sometimes see file changed events from before our load completed (we start
158     // reading at the first change event, but we might receive a series of events related
159     // to the same file operation). Catch this case to avoid reading overly often.
160     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime))
161         return;
162
163     WallTime readTime = WallTime::now();
164
165     auto decoder = createDecoderForFile(filePath);
166     if (!decoder)
167         return;
168
169     m_memoryStore.resetDataFromDecoder(*decoder);
170     m_lastStatisticsFileSyncTime = readTime;
171 }
172
173 void ResourceLoadStatisticsPersistentStorage::populateMemoryStoreFromDisk()
174 {
175     ASSERT(!RunLoop::isMain());
176
177     String filePath = resourceLogFilePath();
178     if (filePath.isEmpty() || !fileExists(filePath)) {
179         m_memoryStore.grandfatherExistingWebsiteData();
180         return;
181     }
182
183     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime)) {
184         // No need to grandfather in this case.
185         return;
186     }
187
188     WallTime readTime = WallTime::now();
189
190     auto decoder = createDecoderForFile(filePath);
191     if (!decoder) {
192         m_memoryStore.grandfatherExistingWebsiteData();
193         return;
194     }
195
196     m_memoryStore.resetDataFromDecoder(*decoder);
197
198     m_lastStatisticsFileSyncTime = readTime;
199
200     if (m_memoryStore.isEmpty())
201         m_memoryStore.grandfatherExistingWebsiteData();
202 }
203
204 void ResourceLoadStatisticsPersistentStorage::writeMemoryStoreToDisk()
205 {
206     ASSERT(!RunLoop::isMain());
207
208     m_hasPendingWrite = false;
209     stopMonitoringDisk();
210
211     auto encoder = m_memoryStore.createEncoderFromData();
212     auto rawData = encoder->finishEncoding();
213     if (!rawData)
214         return;
215
216     auto storagePath = this->storageDirectoryPath();
217     if (!storagePath.isEmpty()) {
218         makeAllDirectories(storagePath);
219         excludeFromBackup();
220     }
221
222     auto handle = openAndLockFile(resourceLogFilePath(), OpenForWrite);
223     if (handle == invalidPlatformFileHandle)
224         return;
225
226     int64_t writtenBytes = writeToFile(handle, rawData->data(), rawData->size());
227     unlockAndCloseFile(handle);
228
229     if (writtenBytes != static_cast<int64_t>(rawData->size()))
230         RELEASE_LOG_ERROR(ResourceLoadStatistics, "WebResourceLoadStatisticsStore: We only wrote %d out of %zu bytes to disk", static_cast<unsigned>(writtenBytes), rawData->size());
231
232     m_lastStatisticsFileSyncTime = WallTime::now();
233     m_lastStatisticsWriteTime = MonotonicTime::now();
234
235     startMonitoringDisk();
236 }
237
238 void ResourceLoadStatisticsPersistentStorage::scheduleOrWriteMemoryStore()
239 {
240     ASSERT(!RunLoop::isMain());
241
242     auto timeSinceLastWrite = MonotonicTime::now() - m_lastStatisticsWriteTime;
243     if (timeSinceLastWrite < minimumWriteInterval) {
244         if (!m_hasPendingWrite) {
245             m_hasPendingWrite = true;
246             Seconds delay = minimumWriteInterval - timeSinceLastWrite + 1_s;
247             m_memoryStore.statisticsQueue().dispatchAfter(delay, [this] () mutable {
248                 writeMemoryStoreToDisk();
249             });
250         }
251         return;
252     }
253
254     writeMemoryStoreToDisk();
255 }
256
257 void ResourceLoadStatisticsPersistentStorage::clear()
258 {
259     ASSERT(!RunLoop::isMain());
260     String filePath = resourceLogFilePath();
261     if (filePath.isEmpty())
262         return;
263
264     stopMonitoringDisk();
265
266     if (!deleteFile(filePath))
267         RELEASE_LOG_ERROR(ResourceLoadStatistics, "Unable to delete statistics file: %s", filePath.utf8().data());
268 }
269
270 void ResourceLoadStatisticsPersistentStorage::finishAllPendingWorkSynchronously()
271 {
272     BinarySemaphore semaphore;
273     // Make sure any pending work in our queue is finished before we terminate.
274     m_memoryStore.statisticsQueue().dispatch([&semaphore, this] {
275         // Write final file state to disk.
276         if (m_hasPendingWrite)
277             writeMemoryStoreToDisk();
278         semaphore.signal();
279     });
280     semaphore.wait(WallTime::infinity());
281 }
282
283 #if !PLATFORM(IOS)
284 void ResourceLoadStatisticsPersistentStorage::excludeFromBackup() const
285 {
286 }
287 #endif
288
289 } // namespace WebKit