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     ASSERT(RunLoop::isMain());
90     m_memoryStore.statisticsQueue().dispatch([this] {
91         populateMemoryStoreFromDisk();
92         startMonitoringDisk();
93         m_memoryStore.includeTodayAsOperatingDateIfNecessary();
94     });
95 }
96
97 ResourceLoadStatisticsPersistentStorage::~ResourceLoadStatisticsPersistentStorage()
98 {
99     finishAllPendingWorkSynchronously();
100     ASSERT(!m_hasPendingWrite);
101 }
102
103 String ResourceLoadStatisticsPersistentStorage::storageDirectoryPath() const
104 {
105     return m_storageDirectoryPath.isolatedCopy();
106 }
107
108 String ResourceLoadStatisticsPersistentStorage::resourceLogFilePath() const
109 {
110     String storagePath = this->storageDirectoryPath();
111     if (storagePath.isEmpty())
112         return emptyString();
113
114     return pathByAppendingComponent(storagePath, "full_browsing_session_resourceLog.plist");
115 }
116
117 void ResourceLoadStatisticsPersistentStorage::startMonitoringDisk()
118 {
119     ASSERT(!RunLoop::isMain());
120     if (m_fileMonitor)
121         return;
122
123     String resourceLogPath = resourceLogFilePath();
124     if (resourceLogPath.isEmpty())
125         return;
126
127     m_fileMonitor = std::make_unique<FileMonitor>(resourceLogPath, m_memoryStore.statisticsQueue(), [this] (FileMonitor::FileChangeType type) {
128         ASSERT(!RunLoop::isMain());
129         switch (type) {
130         case FileMonitor::FileChangeType::Modification:
131             refreshMemoryStoreFromDisk();
132             break;
133         case FileMonitor::FileChangeType::Removal:
134             m_memoryStore.clearInMemory();
135             m_fileMonitor = nullptr;
136             break;
137         }
138     });
139 }
140
141 void ResourceLoadStatisticsPersistentStorage::stopMonitoringDisk()
142 {
143     ASSERT(!RunLoop::isMain());
144     m_fileMonitor = nullptr;
145 }
146
147 // This is called when the file changes on disk.
148 void ResourceLoadStatisticsPersistentStorage::refreshMemoryStoreFromDisk()
149 {
150     ASSERT(!RunLoop::isMain());
151
152     String filePath = resourceLogFilePath();
153     if (filePath.isEmpty())
154         return;
155
156     // We sometimes see file changed events from before our load completed (we start
157     // reading at the first change event, but we might receive a series of events related
158     // to the same file operation). Catch this case to avoid reading overly often.
159     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime))
160         return;
161
162     WallTime readTime = WallTime::now();
163
164     auto decoder = createDecoderForFile(filePath);
165     if (!decoder)
166         return;
167
168     m_memoryStore.resetDataFromDecoder(*decoder);
169     m_lastStatisticsFileSyncTime = readTime;
170 }
171
172 void ResourceLoadStatisticsPersistentStorage::populateMemoryStoreFromDisk()
173 {
174     ASSERT(!RunLoop::isMain());
175
176     String filePath = resourceLogFilePath();
177     if (filePath.isEmpty() || !fileExists(filePath)) {
178         m_memoryStore.grandfatherExistingWebsiteData();
179         return;
180     }
181
182     if (!hasFileChangedSince(filePath, m_lastStatisticsFileSyncTime)) {
183         // No need to grandfather in this case.
184         return;
185     }
186
187     WallTime readTime = WallTime::now();
188
189     auto decoder = createDecoderForFile(filePath);
190     if (!decoder) {
191         m_memoryStore.grandfatherExistingWebsiteData();
192         return;
193     }
194
195     m_memoryStore.resetDataFromDecoder(*decoder);
196
197     m_lastStatisticsFileSyncTime = readTime;
198
199     if (m_memoryStore.isEmpty())
200         m_memoryStore.grandfatherExistingWebsiteData();
201 }
202
203 void ResourceLoadStatisticsPersistentStorage::writeMemoryStoreToDisk()
204 {
205     ASSERT(!RunLoop::isMain());
206
207     m_hasPendingWrite = false;
208     stopMonitoringDisk();
209
210     auto encoder = m_memoryStore.createEncoderFromData();
211     auto rawData = encoder->finishEncoding();
212     if (!rawData)
213         return;
214
215     auto storagePath = this->storageDirectoryPath();
216     if (!storagePath.isEmpty()) {
217         makeAllDirectories(storagePath);
218         excludeFromBackup();
219     }
220
221     auto handle = openAndLockFile(resourceLogFilePath(), OpenForWrite);
222     if (handle == invalidPlatformFileHandle)
223         return;
224
225     int64_t writtenBytes = writeToFile(handle, rawData->data(), rawData->size());
226     unlockAndCloseFile(handle);
227
228     if (writtenBytes != static_cast<int64_t>(rawData->size()))
229         RELEASE_LOG_ERROR(ResourceLoadStatistics, "WebResourceLoadStatisticsStore: We only wrote %d out of %zu bytes to disk", static_cast<unsigned>(writtenBytes), rawData->size());
230
231     m_lastStatisticsFileSyncTime = WallTime::now();
232     m_lastStatisticsWriteTime = MonotonicTime::now();
233
234     startMonitoringDisk();
235 }
236
237 void ResourceLoadStatisticsPersistentStorage::scheduleOrWriteMemoryStore()
238 {
239     ASSERT(!RunLoop::isMain());
240
241     auto timeSinceLastWrite = MonotonicTime::now() - m_lastStatisticsWriteTime;
242     if (timeSinceLastWrite < minimumWriteInterval) {
243         if (!m_hasPendingWrite) {
244             m_hasPendingWrite = true;
245             Seconds delay = minimumWriteInterval - timeSinceLastWrite + 1_s;
246             m_memoryStore.statisticsQueue().dispatchAfter(delay, [this] () mutable {
247                 writeMemoryStoreToDisk();
248             });
249         }
250         return;
251     }
252
253     writeMemoryStoreToDisk();
254 }
255
256 void ResourceLoadStatisticsPersistentStorage::clear()
257 {
258     ASSERT(!RunLoop::isMain());
259     String filePath = resourceLogFilePath();
260     if (filePath.isEmpty())
261         return;
262
263     stopMonitoringDisk();
264
265     if (!deleteFile(filePath))
266         RELEASE_LOG_ERROR(ResourceLoadStatistics, "Unable to delete statistics file: %s", filePath.utf8().data());
267 }
268
269 void ResourceLoadStatisticsPersistentStorage::finishAllPendingWorkSynchronously()
270 {
271     BinarySemaphore semaphore;
272     // Make sure any pending work in our queue is finished before we terminate.
273     m_memoryStore.statisticsQueue().dispatch([&semaphore, this] {
274         // Write final file state to disk.
275         if (m_hasPendingWrite)
276             writeMemoryStoreToDisk();
277         semaphore.signal();
278     });
279     semaphore.wait(WallTime::infinity());
280 }
281
282 #if !PLATFORM(IOS)
283 void ResourceLoadStatisticsPersistentStorage::excludeFromBackup() const
284 {
285 }
286 #endif
287
288 } // namespace WebKit