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#if ENABLE(RESOURCE_LOAD_STATISTICS)
30
31#include "Logging.h"
32#include "PersistencyUtils.h"
33#include "ResourceLoadStatisticsMemoryStore.h"
34#include "WebResourceLoadStatisticsStore.h"
35#include <WebCore/FileMonitor.h>
36#include <WebCore/KeyedCoding.h>
37#include <WebCore/SharedBuffer.h>
38#include <wtf/FileSystem.h>
39#include <wtf/RunLoop.h>
40#include <wtf/WorkQueue.h>
41
42namespace WebKit {
43
44constexpr Seconds minimumWriteInterval { 5_min };
45
46using namespace WebCore;
47
48static bool hasFileChangedSince(const String& path, WallTime since)
49{
50 ASSERT(!RunLoop::isMain());
51 auto modificationTime = FileSystem::getFileModificationTime(path);
52 if (!modificationTime)
53 return true;
54
55 return modificationTime.value() > since;
56}
57
58ResourceLoadStatisticsPersistentStorage::ResourceLoadStatisticsPersistentStorage(ResourceLoadStatisticsMemoryStore& memoryStore, WorkQueue& workQueue, const String& storageDirectoryPath)
59 : m_memoryStore(memoryStore)
60 , m_workQueue(workQueue)
61 , m_storageDirectoryPath(storageDirectoryPath)
62{
63 ASSERT(!RunLoop::isMain());
64
65 m_memoryStore.setPersistentStorage(*this);
66
67 populateMemoryStoreFromDisk();
68 startMonitoringDisk();
69}
70
71ResourceLoadStatisticsPersistentStorage::~ResourceLoadStatisticsPersistentStorage()
72{
73 ASSERT(!RunLoop::isMain());
74
75 if (m_hasPendingWrite)
76 writeMemoryStoreToDisk();
77}
78
79String ResourceLoadStatisticsPersistentStorage::storageDirectoryPath() const
80{
81 return m_storageDirectoryPath.isolatedCopy();
82}
83
84String ResourceLoadStatisticsPersistentStorage::resourceLogFilePath() const
85{
86 String storagePath = storageDirectoryPath();
87 if (storagePath.isEmpty())
88 return emptyString();
89
90 return FileSystem::pathByAppendingComponent(storagePath, "full_browsing_session_resourceLog.plist");
91}
92
93void ResourceLoadStatisticsPersistentStorage::startMonitoringDisk()
94{
95 ASSERT(!RunLoop::isMain());
96 if (m_fileMonitor)
97 return;
98
99 String resourceLogPath = resourceLogFilePath();
100 if (resourceLogPath.isEmpty())
101 return;
102
103 m_fileMonitor = std::make_unique<FileMonitor>(resourceLogPath, m_workQueue.copyRef(), [this, weakThis = makeWeakPtr(*this)] (FileMonitor::FileChangeType type) {
104 ASSERT(!RunLoop::isMain());
105 if (!weakThis)
106 return;
107
108 switch (type) {
109 case FileMonitor::FileChangeType::Modification:
110 refreshMemoryStoreFromDisk();
111 break;
112 case FileMonitor::FileChangeType::Removal:
113 m_memoryStore.clear([] { });
114 m_fileMonitor = nullptr;
115 monitorDirectoryForNewStatistics();
116 break;
117 }
118 });
119}
120
121void ResourceLoadStatisticsPersistentStorage::monitorDirectoryForNewStatistics()
122{
123 ASSERT(!RunLoop::isMain());
124
125 String storagePath = storageDirectoryPath();
126 ASSERT(!storagePath.isEmpty());
127
128 if (!FileSystem::fileExists(storagePath)) {
129 if (!FileSystem::makeAllDirectories(storagePath)) {
130 RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: Failed to create directory path %s", storagePath.utf8().data());
131 return;
132 }
133 }
134
135 m_fileMonitor = std::make_unique<FileMonitor>(storagePath, m_workQueue.copyRef(), [this] (FileMonitor::FileChangeType type) {
136 ASSERT(!RunLoop::isMain());
137 if (type == FileMonitor::FileChangeType::Removal) {
138 // Directory was removed!
139 m_fileMonitor = nullptr;
140 return;
141 }
142
143 String resourceLogPath = resourceLogFilePath();
144 ASSERT(!resourceLogPath.isEmpty());
145
146 if (!FileSystem::fileExists(resourceLogPath))
147 return;
148
149 m_fileMonitor = nullptr;
150
151 refreshMemoryStoreFromDisk();
152 startMonitoringDisk();
153 });
154}
155
156void ResourceLoadStatisticsPersistentStorage::stopMonitoringDisk()
157{
158 ASSERT(!RunLoop::isMain());
159 m_fileMonitor = nullptr;
160}
161
162// This is called when the file changes on disk.
163void ResourceLoadStatisticsPersistentStorage::refreshMemoryStoreFromDisk()
164{
165 ASSERT(!RunLoop::isMain());
166
167 String filePath = resourceLogFilePath();
168 if (filePath.isEmpty())
169 return;
170
171 // We sometimes see file changed events from before our load completed (we start
172 // reading at the first change event, but we might receive a series of events related
173 // to the same file operation). Catch this case to avoid reading overly often.
174 if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime))
175 return;
176
177 WallTime readTime = WallTime::now();
178
179 auto decoder = createForFile(filePath);
180 if (!decoder)
181 return;
182
183 m_memoryStore.mergeWithDataFromDecoder(*decoder);
184 m_lastStatisticsFileSyncTime = readTime;
185}
186
187void ResourceLoadStatisticsPersistentStorage::populateMemoryStoreFromDisk()
188{
189 ASSERT(!RunLoop::isMain());
190
191 String filePath = resourceLogFilePath();
192 if (filePath.isEmpty() || !FileSystem::fileExists(filePath)) {
193 m_memoryStore.grandfatherExistingWebsiteData([]() { });
194 monitorDirectoryForNewStatistics();
195 return;
196 }
197
198 if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime)) {
199 // No need to grandfather in this case.
200 return;
201 }
202
203 WallTime readTime = WallTime::now();
204
205 auto decoder = createForFile(filePath);
206 if (!decoder) {
207 m_memoryStore.grandfatherExistingWebsiteData([]() { });
208 return;
209 }
210
211 // Debug mode has a prepoulated memory store.
212 ASSERT_WITH_MESSAGE(m_memoryStore.isEmpty() || m_memoryStore.isDebugModeEnabled(), "This is the initial import so the store should be empty");
213 m_memoryStore.mergeWithDataFromDecoder(*decoder);
214
215 m_lastStatisticsFileSyncTime = readTime;
216
217 m_memoryStore.logTestingEvent("PopulatedWithoutGrandfathering"_s);
218}
219
220void ResourceLoadStatisticsPersistentStorage::writeMemoryStoreToDisk()
221{
222 ASSERT(!RunLoop::isMain());
223
224 m_hasPendingWrite = false;
225 stopMonitoringDisk();
226
227 writeToDisk(m_memoryStore.createEncoderFromData(), resourceLogFilePath());
228
229 m_lastStatisticsFileSyncTime = WallTime::now();
230 m_lastStatisticsWriteTime = MonotonicTime::now();
231
232 startMonitoringDisk();
233}
234
235void ResourceLoadStatisticsPersistentStorage::scheduleOrWriteMemoryStore(ForceImmediateWrite forceImmediateWrite)
236{
237 ASSERT(!RunLoop::isMain());
238
239 auto timeSinceLastWrite = MonotonicTime::now() - m_lastStatisticsWriteTime;
240 if (forceImmediateWrite != ForceImmediateWrite::Yes && timeSinceLastWrite < minimumWriteInterval) {
241 if (!m_hasPendingWrite) {
242 m_hasPendingWrite = true;
243 Seconds delay = minimumWriteInterval - timeSinceLastWrite + 1_s;
244 m_workQueue->dispatchAfter(delay, [weakThis = makeWeakPtr(*this)] {
245 if (weakThis)
246 weakThis->writeMemoryStoreToDisk();
247 });
248 }
249 return;
250 }
251
252 writeMemoryStoreToDisk();
253}
254
255void ResourceLoadStatisticsPersistentStorage::clear()
256{
257 ASSERT(!RunLoop::isMain());
258 String filePath = resourceLogFilePath();
259 if (filePath.isEmpty())
260 return;
261
262 stopMonitoringDisk();
263
264 if (!FileSystem::deleteFile(filePath) && FileSystem::fileExists(filePath))
265 RELEASE_LOG_ERROR(ResourceLoadStatistics, "ResourceLoadStatisticsPersistentStorage: Unable to delete statistics file: %s", filePath.utf8().data());
266}
267
268#if !PLATFORM(IOS_FAMILY)
269void ResourceLoadStatisticsPersistentStorage::excludeFromBackup() const
270{
271}
272#endif
273
274} // namespace WebKit
275
276#endif
277