09687222ae13758e2af484661313315277180cf7
[WebKit-https.git] / Source / WebKit / UIProcess / ResourceLoadStatisticsPersistentStorage.cpp
1 /*
2  * Copyright (C) 2017-2018 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 "ResourceLoadStatisticsMemoryStore.h"
31 #include "WebResourceLoadStatisticsStore.h"
32 #include <WebCore/FileMonitor.h>
33 #include <WebCore/FileSystem.h>
34 #include <WebCore/KeyedCoding.h>
35 #include <WebCore/SharedBuffer.h>
36 #include <wtf/RunLoop.h>
37 #include <wtf/WorkQueue.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(ResourceLoadStatisticsMemoryStore& memoryStore, WorkQueue& workQueue, const String& storageDirectoryPath, IsReadOnly isReadOnly)
86     : m_memoryStore(memoryStore)
87     , m_workQueue(workQueue)
88     , m_storageDirectoryPath(storageDirectoryPath)
89     , m_isReadOnly(isReadOnly)
90 {
91     ASSERT(!RunLoop::isMain());
92
93     m_memoryStore.setPersistentStorage(*this);
94
95     populateMemoryStoreFromDisk();
96     startMonitoringDisk();
97 }
98
99 ResourceLoadStatisticsPersistentStorage::~ResourceLoadStatisticsPersistentStorage()
100 {
101     ASSERT(!RunLoop::isMain());
102
103     if (m_hasPendingWrite)
104         writeMemoryStoreToDisk();
105 }
106
107 String ResourceLoadStatisticsPersistentStorage::storageDirectoryPath() const
108 {
109     return m_storageDirectoryPath.isolatedCopy();
110 }
111
112 String ResourceLoadStatisticsPersistentStorage::resourceLogFilePath() const
113 {
114     String storagePath = storageDirectoryPath();
115     if (storagePath.isEmpty())
116         return emptyString();
117
118     return FileSystem::pathByAppendingComponent(storagePath, "full_browsing_session_resourceLog.plist");
119 }
120
121 void ResourceLoadStatisticsPersistentStorage::startMonitoringDisk()
122 {
123     ASSERT(!RunLoop::isMain());
124     if (m_fileMonitor)
125         return;
126
127     String resourceLogPath = resourceLogFilePath();
128     if (resourceLogPath.isEmpty())
129         return;
130
131     m_fileMonitor = std::make_unique<FileMonitor>(resourceLogPath, m_workQueue.copyRef(), [this, weakThis = makeWeakPtr(*this)] (FileMonitor::FileChangeType type) {
132         ASSERT(!RunLoop::isMain());
133         if (!weakThis)
134             return;
135
136         switch (type) {
137         case FileMonitor::FileChangeType::Modification:
138             refreshMemoryStoreFromDisk();
139             break;
140         case FileMonitor::FileChangeType::Removal:
141             m_memoryStore.clear();
142             m_fileMonitor = nullptr;
143             monitorDirectoryForNewStatistics();
144             break;
145         }
146     });
147 }
148
149 void ResourceLoadStatisticsPersistentStorage::monitorDirectoryForNewStatistics()
150 {
151     ASSERT(!RunLoop::isMain());
152
153     String storagePath = storageDirectoryPath();
154     ASSERT(!storagePath.isEmpty());
155
156     if (!FileSystem::fileExists(storagePath)) {
157         if (!FileSystem::makeAllDirectories(storagePath)) {
158             RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: Failed to create directory path %s", storagePath.utf8().data());
159             return;
160         }
161     }
162
163     m_fileMonitor = std::make_unique<FileMonitor>(storagePath, m_workQueue.copyRef(), [this] (FileMonitor::FileChangeType type) {
164         ASSERT(!RunLoop::isMain());
165         if (type == FileMonitor::FileChangeType::Removal) {
166             // Directory was removed!
167             m_fileMonitor = nullptr;
168             return;
169         }
170
171         String resourceLogPath = resourceLogFilePath();
172         ASSERT(!resourceLogPath.isEmpty());
173
174         if (!FileSystem::fileExists(resourceLogPath))
175             return;
176
177         m_fileMonitor = nullptr;
178
179         refreshMemoryStoreFromDisk();
180         startMonitoringDisk();
181     });
182 }
183
184 void ResourceLoadStatisticsPersistentStorage::stopMonitoringDisk()
185 {
186     ASSERT(!RunLoop::isMain());
187     m_fileMonitor = nullptr;
188 }
189
190 // This is called when the file changes on disk.
191 void ResourceLoadStatisticsPersistentStorage::refreshMemoryStoreFromDisk()
192 {
193     ASSERT(!RunLoop::isMain());
194
195     String filePath = resourceLogFilePath();
196     if (filePath.isEmpty())
197         return;
198
199     // We sometimes see file changed events from before our load completed (we start
200     // reading at the first change event, but we might receive a series of events related
201     // to the same file operation). Catch this case to avoid reading overly often.
202     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime))
203         return;
204
205     WallTime readTime = WallTime::now();
206
207     auto decoder = createDecoderForFile(filePath);
208     if (!decoder)
209         return;
210
211     m_memoryStore.mergeWithDataFromDecoder(*decoder);
212     m_lastStatisticsFileSyncTime = readTime;
213 }
214
215 void ResourceLoadStatisticsPersistentStorage::populateMemoryStoreFromDisk()
216 {
217     ASSERT(!RunLoop::isMain());
218
219     String filePath = resourceLogFilePath();
220     if (filePath.isEmpty() || !FileSystem::fileExists(filePath)) {
221         m_memoryStore.grandfatherExistingWebsiteData([]() { });
222         monitorDirectoryForNewStatistics();
223         return;
224     }
225
226     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime)) {
227         // No need to grandfather in this case.
228         return;
229     }
230
231     WallTime readTime = WallTime::now();
232
233     auto decoder = createDecoderForFile(filePath);
234     if (!decoder) {
235         m_memoryStore.grandfatherExistingWebsiteData([]() { });
236         return;
237     }
238
239     ASSERT_WITH_MESSAGE(m_memoryStore.isEmpty(), "This is the initial import so the store should be empty");
240     m_memoryStore.mergeWithDataFromDecoder(*decoder);
241
242     m_lastStatisticsFileSyncTime = readTime;
243
244     m_memoryStore.logTestingEvent("PopulatedWithoutGrandfathering"_s);
245 }
246
247 void ResourceLoadStatisticsPersistentStorage::writeMemoryStoreToDisk()
248 {
249     ASSERT(!RunLoop::isMain());
250     RELEASE_ASSERT(m_isReadOnly != IsReadOnly::Yes);
251
252     m_hasPendingWrite = false;
253     stopMonitoringDisk();
254
255     auto encoder = m_memoryStore.createEncoderFromData();
256     auto rawData = encoder->finishEncoding();
257     if (!rawData)
258         return;
259
260     auto storagePath = storageDirectoryPath();
261     if (!storagePath.isEmpty()) {
262         FileSystem::makeAllDirectories(storagePath);
263         excludeFromBackup();
264     }
265
266     auto handle = FileSystem::openAndLockFile(resourceLogFilePath(), FileSystem::FileOpenMode::Write);
267     if (handle == FileSystem::invalidPlatformFileHandle)
268         return;
269
270     int64_t writtenBytes = FileSystem::writeToFile(handle, rawData->data(), rawData->size());
271     FileSystem::unlockAndCloseFile(handle);
272
273     if (writtenBytes != static_cast<int64_t>(rawData->size()))
274         RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: We only wrote %d out of %zu bytes to disk", static_cast<unsigned>(writtenBytes), rawData->size());
275
276     m_lastStatisticsFileSyncTime = WallTime::now();
277     m_lastStatisticsWriteTime = MonotonicTime::now();
278
279     startMonitoringDisk();
280 }
281
282 void ResourceLoadStatisticsPersistentStorage::scheduleOrWriteMemoryStore(ForceImmediateWrite forceImmediateWrite)
283 {
284     ASSERT(!RunLoop::isMain());
285     if (m_isReadOnly == IsReadOnly::Yes)
286         return;
287
288     auto timeSinceLastWrite = MonotonicTime::now() - m_lastStatisticsWriteTime;
289     if (forceImmediateWrite != ForceImmediateWrite::Yes && timeSinceLastWrite < minimumWriteInterval) {
290         if (!m_hasPendingWrite) {
291             m_hasPendingWrite = true;
292             Seconds delay = minimumWriteInterval - timeSinceLastWrite + 1_s;
293             m_workQueue->dispatchAfter(delay, [weakThis = makeWeakPtr(*this)] {
294                 if (weakThis)
295                     weakThis->writeMemoryStoreToDisk();
296             });
297         }
298         return;
299     }
300
301     writeMemoryStoreToDisk();
302 }
303
304 void ResourceLoadStatisticsPersistentStorage::clear()
305 {
306     ASSERT(!RunLoop::isMain());
307     String filePath = resourceLogFilePath();
308     if (filePath.isEmpty())
309         return;
310
311     stopMonitoringDisk();
312
313     if (!FileSystem::deleteFile(filePath))
314         RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: Unable to delete statistics file: %s", filePath.utf8().data());
315 }
316
317 #if !PLATFORM(IOS)
318 void ResourceLoadStatisticsPersistentStorage::excludeFromBackup() const
319 {
320 }
321 #endif
322
323 } // namespace WebKit