/src/mozilla-central/storage/TelemetryVFS.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
2 | | * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : |
3 | | * This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include <string.h> |
8 | | #include "mozilla/Telemetry.h" |
9 | | #include "mozilla/Preferences.h" |
10 | | #include "sqlite3.h" |
11 | | #include "nsThreadUtils.h" |
12 | | #include "mozilla/dom/quota/PersistenceType.h" |
13 | | #include "mozilla/dom/quota/QuotaManager.h" |
14 | | #include "mozilla/dom/quota/QuotaObject.h" |
15 | | #include "mozilla/net/IOActivityMonitor.h" |
16 | | #include "mozilla/IOInterposer.h" |
17 | | |
18 | | // The last VFS version for which this file has been updated. |
19 | | #define LAST_KNOWN_VFS_VERSION 3 |
20 | | |
21 | | // The last io_methods version for which this file has been updated. |
22 | | #define LAST_KNOWN_IOMETHODS_VERSION 3 |
23 | | |
24 | | /** |
25 | | * By default use the unix-excl VFS, for the following reasons: |
26 | | * 1. It improves compatibility with NFS shares, whose implementation |
27 | | * is incompatible with SQLite's locking requirements. |
28 | | * Bug 433129 attempted to automatically identify such file-systems, |
29 | | * but a reliable way was not found and the fallback locking is slower than |
30 | | * POSIX locking, so we do not want to do it by default. |
31 | | * 2. It allows wal mode to avoid the memory mapped -shm file, reducing the |
32 | | * likelihood of SIGBUS failures when disk space is exhausted. |
33 | | * 3. It provides some protection from third party database tampering while a |
34 | | * connection is open. |
35 | | * This preference allows to revert to the "unix" VFS, that is not exclusive, |
36 | | * thus it can be used by developers to query a database through the Sqlite |
37 | | * command line while it's already in use. |
38 | | */ |
39 | 0 | #define PREF_MULTI_PROCESS_ACCESS "storage.multiProcessAccess.enabled" |
40 | | |
41 | | namespace { |
42 | | |
43 | | using namespace mozilla; |
44 | | using namespace mozilla::dom::quota; |
45 | | using namespace mozilla::net; |
46 | | |
47 | | struct Histograms { |
48 | | const char *name; |
49 | | const Telemetry::HistogramID readB; |
50 | | const Telemetry::HistogramID writeB; |
51 | | const Telemetry::HistogramID readMS; |
52 | | const Telemetry::HistogramID writeMS; |
53 | | const Telemetry::HistogramID syncMS; |
54 | | }; |
55 | | |
56 | | #define SQLITE_TELEMETRY(FILENAME, HGRAM) \ |
57 | | { FILENAME, \ |
58 | | Telemetry::MOZ_SQLITE_ ## HGRAM ## _READ_B, \ |
59 | | Telemetry::MOZ_SQLITE_ ## HGRAM ## _WRITE_B, \ |
60 | | Telemetry::MOZ_SQLITE_ ## HGRAM ## _READ_MS, \ |
61 | | Telemetry::MOZ_SQLITE_ ## HGRAM ## _WRITE_MS, \ |
62 | | Telemetry::MOZ_SQLITE_ ## HGRAM ## _SYNC_MS \ |
63 | | } |
64 | | |
65 | | Histograms gHistograms[] = { |
66 | | SQLITE_TELEMETRY("places.sqlite", PLACES), |
67 | | SQLITE_TELEMETRY("cookies.sqlite", COOKIES), |
68 | | SQLITE_TELEMETRY("webappsstore.sqlite", WEBAPPS), |
69 | | SQLITE_TELEMETRY(nullptr, OTHER) |
70 | | }; |
71 | | #undef SQLITE_TELEMETRY |
72 | | |
73 | | /** RAII class for measuring how long io takes on/off main thread |
74 | | */ |
75 | | class IOThreadAutoTimer { |
76 | | public: |
77 | | /** |
78 | | * IOThreadAutoTimer measures time spent in IO. Additionally it |
79 | | * automatically determines whether IO is happening on the main |
80 | | * thread and picks an appropriate histogram. |
81 | | * |
82 | | * @param id takes a telemetry histogram id. The id+1 must be an |
83 | | * equivalent histogram for the main thread. Eg, MOZ_SQLITE_OPEN_MS |
84 | | * is followed by MOZ_SQLITE_OPEN_MAIN_THREAD_MS. |
85 | | * |
86 | | * @param aOp optionally takes an IO operation to report through the |
87 | | * IOInterposer. Filename will be reported as NULL, and reference will be |
88 | | * either "sqlite-mainthread" or "sqlite-otherthread". |
89 | | */ |
90 | | explicit IOThreadAutoTimer(Telemetry::HistogramID aId, |
91 | | IOInterposeObserver::Operation aOp = IOInterposeObserver::OpNone) |
92 | | : start(TimeStamp::Now()), |
93 | | id(aId) |
94 | | #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN) |
95 | | , op(aOp) |
96 | | #endif |
97 | 0 | { |
98 | 0 | } |
99 | | |
100 | | /** |
101 | | * This constructor is for when we want to report an operation to |
102 | | * IOInterposer but do not require a telemetry probe. |
103 | | * |
104 | | * @param aOp IO Operation to report through the IOInterposer. |
105 | | */ |
106 | | explicit IOThreadAutoTimer(IOInterposeObserver::Operation aOp) |
107 | | : start(TimeStamp::Now()), |
108 | | id(Telemetry::HistogramCount) |
109 | | #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN) |
110 | | , op(aOp) |
111 | | #endif |
112 | 0 | { |
113 | 0 | } |
114 | | |
115 | | ~IOThreadAutoTimer() |
116 | 0 | { |
117 | 0 | TimeStamp end(TimeStamp::Now()); |
118 | 0 | uint32_t mainThread = NS_IsMainThread() ? 1 : 0; |
119 | 0 | if (id != Telemetry::HistogramCount) { |
120 | 0 | Telemetry::AccumulateTimeDelta(static_cast<Telemetry::HistogramID>(id + mainThread), |
121 | 0 | start, end); |
122 | 0 | } |
123 | 0 | // We don't report SQLite I/O on Windows because we have a comprehensive |
124 | 0 | // mechanism for intercepting I/O on that platform that captures a superset |
125 | 0 | // of the data captured here. |
126 | 0 | #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN) |
127 | 0 | if (IOInterposer::IsObservedOperation(op)) { |
128 | 0 | const char* main_ref = "sqlite-mainthread"; |
129 | 0 | const char* other_ref = "sqlite-otherthread"; |
130 | 0 |
|
131 | 0 | // Create observation |
132 | 0 | IOInterposeObserver::Observation ob(op, start, end, |
133 | 0 | (mainThread ? main_ref : other_ref)); |
134 | 0 | // Report observation |
135 | 0 | IOInterposer::Report(ob); |
136 | 0 | } |
137 | 0 | #endif /* defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN) */ |
138 | 0 | } |
139 | | |
140 | | private: |
141 | | const TimeStamp start; |
142 | | const Telemetry::HistogramID id; |
143 | | #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN) |
144 | | IOInterposeObserver::Operation op; |
145 | | #endif |
146 | | }; |
147 | | |
148 | | struct telemetry_file { |
149 | | // Base class. Must be first |
150 | | sqlite3_file base; |
151 | | |
152 | | // histograms pertaining to this file |
153 | | Histograms *histograms; |
154 | | |
155 | | // quota object for this file |
156 | | RefPtr<QuotaObject> quotaObject; |
157 | | |
158 | | // The chunk size for this file. See the documentation for |
159 | | // sqlite3_file_control() and FCNTL_CHUNK_SIZE. |
160 | | int fileChunkSize; |
161 | | |
162 | | // The filename |
163 | | char* location; |
164 | | |
165 | | // This contains the vfs that actually does work |
166 | | sqlite3_file pReal[1]; |
167 | | }; |
168 | | |
169 | | const char* |
170 | | DatabasePathFromWALPath(const char *zWALName) |
171 | 0 | { |
172 | 0 | /** |
173 | 0 | * Do some sketchy pointer arithmetic to find the parameter key. The WAL |
174 | 0 | * filename is in the middle of a big allocated block that contains: |
175 | 0 | * |
176 | 0 | * - Random Values |
177 | 0 | * - Main Database Path |
178 | 0 | * - \0 |
179 | 0 | * - Multiple URI components consisting of: |
180 | 0 | * - Key |
181 | 0 | * - \0 |
182 | 0 | * - Value |
183 | 0 | * - \0 |
184 | 0 | * - \0 |
185 | 0 | * - Journal Path |
186 | 0 | * - \0 |
187 | 0 | * - WAL Path (zWALName) |
188 | 0 | * - \0 |
189 | 0 | * |
190 | 0 | * Because the main database path is preceded by a random value we have to be |
191 | 0 | * careful when trying to figure out when we should terminate this loop. |
192 | 0 | */ |
193 | 0 | MOZ_ASSERT(zWALName); |
194 | 0 |
|
195 | 0 | nsDependentCSubstring dbPath(zWALName, strlen(zWALName)); |
196 | 0 |
|
197 | 0 | // Chop off the "-wal" suffix. |
198 | 0 | NS_NAMED_LITERAL_CSTRING(kWALSuffix, "-wal"); |
199 | 0 | MOZ_ASSERT(StringEndsWith(dbPath, kWALSuffix)); |
200 | 0 |
|
201 | 0 | dbPath.Rebind(zWALName, dbPath.Length() - kWALSuffix.Length()); |
202 | 0 | MOZ_ASSERT(!dbPath.IsEmpty()); |
203 | 0 |
|
204 | 0 | // We want to scan to the end of the key/value URI pairs. Skip the preceding |
205 | 0 | // null and go to the last char of the journal path. |
206 | 0 | const char* cursor = zWALName - 2; |
207 | 0 |
|
208 | 0 | // Make sure we just skipped a null. |
209 | 0 | MOZ_ASSERT(!*(cursor + 1)); |
210 | 0 |
|
211 | 0 | // Walk backwards over the journal path. |
212 | 0 | while (*cursor) { |
213 | 0 | cursor--; |
214 | 0 | } |
215 | 0 |
|
216 | 0 | // There should be another null here. |
217 | 0 | cursor--; |
218 | 0 | MOZ_ASSERT(!*cursor); |
219 | 0 |
|
220 | 0 | // Back up one more char to the last char of the previous string. It may be |
221 | 0 | // the database path or it may be a key/value URI pair. |
222 | 0 | cursor--; |
223 | 0 |
|
224 | | #ifdef DEBUG |
225 | | { |
226 | | // Verify that we just walked over the journal path. Account for the two |
227 | | // nulls we just skipped. |
228 | | const char *journalStart = cursor + 3; |
229 | | |
230 | | nsDependentCSubstring journalPath(journalStart, |
231 | | strlen(journalStart)); |
232 | | |
233 | | // Chop off the "-journal" suffix. |
234 | | NS_NAMED_LITERAL_CSTRING(kJournalSuffix, "-journal"); |
235 | | MOZ_ASSERT(StringEndsWith(journalPath, kJournalSuffix)); |
236 | | |
237 | | journalPath.Rebind(journalStart, |
238 | | journalPath.Length() - kJournalSuffix.Length()); |
239 | | MOZ_ASSERT(!journalPath.IsEmpty()); |
240 | | |
241 | | // Make sure that the database name is a substring of the journal name. |
242 | | MOZ_ASSERT(journalPath == dbPath); |
243 | | } |
244 | | #endif |
245 | |
|
246 | 0 | // Now we're either at the end of the key/value URI pairs or we're at the |
247 | 0 | // end of the database path. Carefully walk backwards one character at a |
248 | 0 | // time to do this safely without running past the beginning of the database |
249 | 0 | // path. |
250 | 0 | const char *const dbPathStart = dbPath.BeginReading(); |
251 | 0 | const char *dbPathCursor = dbPath.EndReading() - 1; |
252 | 0 | bool isDBPath = true; |
253 | 0 |
|
254 | 0 | while (true) { |
255 | 0 | MOZ_ASSERT(*dbPathCursor, "dbPathCursor should never see a null char!"); |
256 | 0 |
|
257 | 0 | if (isDBPath) { |
258 | 0 | isDBPath = dbPathStart <= dbPathCursor && |
259 | 0 | *dbPathCursor == *cursor && |
260 | 0 | *cursor; |
261 | 0 | } |
262 | 0 |
|
263 | 0 | if (!isDBPath) { |
264 | 0 | // This isn't the database path so it must be a value. Scan past it and |
265 | 0 | // the key also. |
266 | 0 | for (size_t stringCount = 0; stringCount < 2; stringCount++) { |
267 | 0 | // Scan past the string to the preceding null character. |
268 | 0 | while (*cursor) { |
269 | 0 | cursor--; |
270 | 0 | } |
271 | 0 |
|
272 | 0 | // Back up one more char to the last char of preceding string. |
273 | 0 | cursor--; |
274 | 0 | } |
275 | 0 |
|
276 | 0 | // Reset and start again. |
277 | 0 | dbPathCursor = dbPath.EndReading() - 1; |
278 | 0 | isDBPath = true; |
279 | 0 |
|
280 | 0 | continue; |
281 | 0 | } |
282 | 0 |
|
283 | 0 | MOZ_ASSERT(isDBPath); |
284 | 0 | MOZ_ASSERT(*cursor); |
285 | 0 |
|
286 | 0 | if (dbPathStart == dbPathCursor) { |
287 | 0 | // Found the full database path, we're all done. |
288 | 0 | MOZ_ASSERT(nsDependentCString(cursor) == dbPath); |
289 | 0 | return cursor; |
290 | 0 | } |
291 | 0 |
|
292 | 0 | // Change the cursors and go through the loop again. |
293 | 0 | cursor--; |
294 | 0 | dbPathCursor--; |
295 | 0 | } |
296 | 0 |
|
297 | 0 | MOZ_CRASH("Should never get here!"); |
298 | 0 | } |
299 | | |
300 | | already_AddRefed<QuotaObject> |
301 | | GetQuotaObjectFromNameAndParameters(const char *zName, |
302 | | const char *zURIParameterKey) |
303 | 0 | { |
304 | 0 | MOZ_ASSERT(zName); |
305 | 0 | MOZ_ASSERT(zURIParameterKey); |
306 | 0 |
|
307 | 0 | const char *persistenceType = |
308 | 0 | sqlite3_uri_parameter(zURIParameterKey, "persistenceType"); |
309 | 0 | if (!persistenceType) { |
310 | 0 | return nullptr; |
311 | 0 | } |
312 | 0 | |
313 | 0 | const char *group = sqlite3_uri_parameter(zURIParameterKey, "group"); |
314 | 0 | if (!group) { |
315 | 0 | NS_WARNING("SQLite URI had 'persistenceType' but not 'group'?!"); |
316 | 0 | return nullptr; |
317 | 0 | } |
318 | 0 |
|
319 | 0 | const char *origin = sqlite3_uri_parameter(zURIParameterKey, "origin"); |
320 | 0 | if (!origin) { |
321 | 0 | NS_WARNING("SQLite URI had 'persistenceType' and 'group' but not " |
322 | 0 | "'origin'?!"); |
323 | 0 | return nullptr; |
324 | 0 | } |
325 | 0 |
|
326 | 0 | QuotaManager *quotaManager = QuotaManager::Get(); |
327 | 0 | MOZ_ASSERT(quotaManager); |
328 | 0 |
|
329 | 0 | return quotaManager->GetQuotaObject( |
330 | 0 | PersistenceTypeFromText(nsDependentCString(persistenceType)), |
331 | 0 | nsDependentCString(group), |
332 | 0 | nsDependentCString(origin), |
333 | 0 | NS_ConvertUTF8toUTF16(zName)); |
334 | 0 | } |
335 | | |
336 | | void |
337 | | MaybeEstablishQuotaControl(const char *zName, |
338 | | telemetry_file *pFile, |
339 | | int flags) |
340 | 0 | { |
341 | 0 | MOZ_ASSERT(pFile); |
342 | 0 | MOZ_ASSERT(!pFile->quotaObject); |
343 | 0 |
|
344 | 0 | if (!(flags & (SQLITE_OPEN_URI | SQLITE_OPEN_WAL))) { |
345 | 0 | return; |
346 | 0 | } |
347 | 0 | |
348 | 0 | MOZ_ASSERT(zName); |
349 | 0 |
|
350 | 0 | const char *zURIParameterKey = (flags & SQLITE_OPEN_WAL) ? |
351 | 0 | DatabasePathFromWALPath(zName) : |
352 | 0 | zName; |
353 | 0 |
|
354 | 0 | MOZ_ASSERT(zURIParameterKey); |
355 | 0 |
|
356 | 0 | pFile->quotaObject = |
357 | 0 | GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey); |
358 | 0 | } |
359 | | |
360 | | /* |
361 | | ** Close a telemetry_file. |
362 | | */ |
363 | | int |
364 | | xClose(sqlite3_file *pFile) |
365 | 0 | { |
366 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
367 | 0 | int rc; |
368 | 0 | { // Scope for IOThreadAutoTimer |
369 | 0 | IOThreadAutoTimer ioTimer(IOInterposeObserver::OpClose); |
370 | 0 | rc = p->pReal->pMethods->xClose(p->pReal); |
371 | 0 | } |
372 | 0 | if( rc==SQLITE_OK ){ |
373 | 0 | delete p->base.pMethods; |
374 | 0 | p->base.pMethods = nullptr; |
375 | 0 | p->quotaObject = nullptr; |
376 | 0 | delete[] p->location; |
377 | | #ifdef DEBUG |
378 | | p->fileChunkSize = 0; |
379 | | #endif |
380 | | } |
381 | 0 | return rc; |
382 | 0 | } |
383 | | |
384 | | /* |
385 | | ** Read data from a telemetry_file. |
386 | | */ |
387 | | int |
388 | | xRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) |
389 | 0 | { |
390 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
391 | 0 | IOThreadAutoTimer ioTimer(p->histograms->readMS, IOInterposeObserver::OpRead); |
392 | 0 | int rc; |
393 | 0 | rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); |
394 | 0 | if (rc == SQLITE_OK && IOActivityMonitor::IsActive()) { |
395 | 0 | IOActivityMonitor::Read(nsDependentCString(p->location), iAmt); |
396 | 0 | } |
397 | 0 | // sqlite likes to read from empty files, this is normal, ignore it. |
398 | 0 | if (rc != SQLITE_IOERR_SHORT_READ) |
399 | 0 | Telemetry::Accumulate(p->histograms->readB, rc == SQLITE_OK ? iAmt : 0); |
400 | 0 | return rc; |
401 | 0 | } |
402 | | |
403 | | /* |
404 | | ** Return the current file-size of a telemetry_file. |
405 | | */ |
406 | | int |
407 | | xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize) |
408 | 0 | { |
409 | 0 | IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat); |
410 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
411 | 0 | int rc; |
412 | 0 | rc = p->pReal->pMethods->xFileSize(p->pReal, pSize); |
413 | 0 | return rc; |
414 | 0 | } |
415 | | |
416 | | /* |
417 | | ** Write data to a telemetry_file. |
418 | | */ |
419 | | int |
420 | | xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) |
421 | 0 | { |
422 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
423 | 0 | IOThreadAutoTimer ioTimer(p->histograms->writeMS, IOInterposeObserver::OpWrite); |
424 | 0 | int rc; |
425 | 0 | if (p->quotaObject) { |
426 | 0 | MOZ_ASSERT(INT64_MAX - iOfst >= iAmt); |
427 | 0 | if (!p->quotaObject->MaybeUpdateSize(iOfst + iAmt, /* aTruncate */ false)) { |
428 | 0 | return SQLITE_FULL; |
429 | 0 | } |
430 | 0 | } |
431 | 0 | rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst); |
432 | 0 | if (rc == SQLITE_OK && IOActivityMonitor::IsActive()) { |
433 | 0 | IOActivityMonitor::Write(nsDependentCString(p->location), iAmt); |
434 | 0 | } |
435 | 0 |
|
436 | 0 | Telemetry::Accumulate(p->histograms->writeB, rc == SQLITE_OK ? iAmt : 0); |
437 | 0 | if (p->quotaObject && rc != SQLITE_OK) { |
438 | 0 | NS_WARNING("xWrite failed on a quota-controlled file, attempting to " |
439 | 0 | "update its current size..."); |
440 | 0 | sqlite_int64 currentSize; |
441 | 0 | if (xFileSize(pFile, ¤tSize) == SQLITE_OK) { |
442 | 0 | p->quotaObject->MaybeUpdateSize(currentSize, /* aTruncate */ true); |
443 | 0 | } |
444 | 0 | } |
445 | 0 | return rc; |
446 | 0 | } |
447 | | |
448 | | /* |
449 | | ** Truncate a telemetry_file. |
450 | | */ |
451 | | int |
452 | | xTruncate(sqlite3_file *pFile, sqlite_int64 size) |
453 | 0 | { |
454 | 0 | IOThreadAutoTimer ioTimer(Telemetry::MOZ_SQLITE_TRUNCATE_MS); |
455 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
456 | 0 | int rc; |
457 | 0 | Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_TRUNCATE_MS> timer; |
458 | 0 | if (p->quotaObject) { |
459 | 0 | if (p->fileChunkSize > 0) { |
460 | 0 | // Round up to the smallest multiple of the chunk size that will hold all |
461 | 0 | // the data. |
462 | 0 | size = |
463 | 0 | ((size + p->fileChunkSize - 1) / p->fileChunkSize) * p->fileChunkSize; |
464 | 0 | } |
465 | 0 | if (!p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true)) { |
466 | 0 | return SQLITE_FULL; |
467 | 0 | } |
468 | 0 | } |
469 | 0 | rc = p->pReal->pMethods->xTruncate(p->pReal, size); |
470 | 0 | if (p->quotaObject) { |
471 | 0 | if (rc == SQLITE_OK) { |
472 | | #ifdef DEBUG |
473 | | // Make sure xTruncate set the size exactly as we calculated above. |
474 | | sqlite_int64 newSize; |
475 | | MOZ_ASSERT(xFileSize(pFile, &newSize) == SQLITE_OK); |
476 | | MOZ_ASSERT(newSize == size); |
477 | | #endif |
478 | 0 | } else { |
479 | 0 | NS_WARNING("xTruncate failed on a quota-controlled file, attempting to " |
480 | 0 | "update its current size..."); |
481 | 0 | if (xFileSize(pFile, &size) == SQLITE_OK) { |
482 | 0 | p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true); |
483 | 0 | } |
484 | 0 | } |
485 | 0 | } |
486 | 0 | return rc; |
487 | 0 | } |
488 | | |
489 | | /* |
490 | | ** Sync a telemetry_file. |
491 | | */ |
492 | | int |
493 | | xSync(sqlite3_file *pFile, int flags) |
494 | 0 | { |
495 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
496 | 0 | IOThreadAutoTimer ioTimer(p->histograms->syncMS, IOInterposeObserver::OpFSync); |
497 | 0 | return p->pReal->pMethods->xSync(p->pReal, flags); |
498 | 0 | } |
499 | | |
500 | | /* |
501 | | ** Lock a telemetry_file. |
502 | | */ |
503 | | int |
504 | | xLock(sqlite3_file *pFile, int eLock) |
505 | 0 | { |
506 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
507 | 0 | int rc; |
508 | 0 | rc = p->pReal->pMethods->xLock(p->pReal, eLock); |
509 | 0 | return rc; |
510 | 0 | } |
511 | | |
512 | | /* |
513 | | ** Unlock a telemetry_file. |
514 | | */ |
515 | | int |
516 | | xUnlock(sqlite3_file *pFile, int eLock) |
517 | 0 | { |
518 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
519 | 0 | int rc; |
520 | 0 | rc = p->pReal->pMethods->xUnlock(p->pReal, eLock); |
521 | 0 | return rc; |
522 | 0 | } |
523 | | |
524 | | /* |
525 | | ** Check if another file-handle holds a RESERVED lock on a telemetry_file. |
526 | | */ |
527 | | int |
528 | | xCheckReservedLock(sqlite3_file *pFile, int *pResOut) |
529 | 0 | { |
530 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
531 | 0 | int rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut); |
532 | 0 | return rc; |
533 | 0 | } |
534 | | |
535 | | /* |
536 | | ** File control method. For custom operations on a telemetry_file. |
537 | | */ |
538 | | int |
539 | | xFileControl(sqlite3_file *pFile, int op, void *pArg) |
540 | 0 | { |
541 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
542 | 0 | int rc; |
543 | 0 | // Hook SQLITE_FCNTL_SIZE_HINT for quota-controlled files and do the necessary |
544 | 0 | // work before passing to the SQLite VFS. |
545 | 0 | if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject) { |
546 | 0 | sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg); |
547 | 0 | sqlite3_int64 currentSize; |
548 | 0 | rc = xFileSize(pFile, ¤tSize); |
549 | 0 | if (rc != SQLITE_OK) { |
550 | 0 | return rc; |
551 | 0 | } |
552 | 0 | if (hintSize > currentSize) { |
553 | 0 | rc = xTruncate(pFile, hintSize); |
554 | 0 | if (rc != SQLITE_OK) { |
555 | 0 | return rc; |
556 | 0 | } |
557 | 0 | } |
558 | 0 | } |
559 | 0 | rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg); |
560 | 0 | // Grab the file chunk size after the SQLite VFS has approved. |
561 | 0 | if (op == SQLITE_FCNTL_CHUNK_SIZE && rc == SQLITE_OK) { |
562 | 0 | p->fileChunkSize = *static_cast<int*>(pArg); |
563 | 0 | } |
564 | | #ifdef DEBUG |
565 | | if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject && rc == SQLITE_OK) { |
566 | | sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg); |
567 | | if (p->fileChunkSize > 0) { |
568 | | hintSize = |
569 | | ((hintSize + p->fileChunkSize - 1) / p->fileChunkSize) * |
570 | | p->fileChunkSize; |
571 | | } |
572 | | sqlite3_int64 currentSize; |
573 | | MOZ_ASSERT(xFileSize(pFile, ¤tSize) == SQLITE_OK); |
574 | | MOZ_ASSERT(currentSize >= hintSize); |
575 | | } |
576 | | #endif |
577 | | return rc; |
578 | 0 | } |
579 | | |
580 | | /* |
581 | | ** Return the sector-size in bytes for a telemetry_file. |
582 | | */ |
583 | | int |
584 | | xSectorSize(sqlite3_file *pFile) |
585 | 0 | { |
586 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
587 | 0 | int rc; |
588 | 0 | rc = p->pReal->pMethods->xSectorSize(p->pReal); |
589 | 0 | return rc; |
590 | 0 | } |
591 | | |
592 | | /* |
593 | | ** Return the device characteristic flags supported by a telemetry_file. |
594 | | */ |
595 | | int |
596 | | xDeviceCharacteristics(sqlite3_file *pFile) |
597 | 0 | { |
598 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
599 | 0 | int rc; |
600 | 0 | rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal); |
601 | 0 | return rc; |
602 | 0 | } |
603 | | |
604 | | /* |
605 | | ** Shared-memory operations. |
606 | | */ |
607 | | int |
608 | | xShmLock(sqlite3_file *pFile, int ofst, int n, int flags) |
609 | 0 | { |
610 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
611 | 0 | return p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); |
612 | 0 | } |
613 | | |
614 | | int |
615 | | xShmMap(sqlite3_file *pFile, int iRegion, int szRegion, int isWrite, void volatile **pp) |
616 | 0 | { |
617 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
618 | 0 | int rc; |
619 | 0 | rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp); |
620 | 0 | return rc; |
621 | 0 | } |
622 | | |
623 | | void |
624 | 0 | xShmBarrier(sqlite3_file *pFile){ |
625 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
626 | 0 | p->pReal->pMethods->xShmBarrier(p->pReal); |
627 | 0 | } |
628 | | |
629 | | int |
630 | 0 | xShmUnmap(sqlite3_file *pFile, int delFlag){ |
631 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
632 | 0 | int rc; |
633 | 0 | rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag); |
634 | 0 | return rc; |
635 | 0 | } |
636 | | |
637 | | int |
638 | | xFetch(sqlite3_file *pFile, sqlite3_int64 iOff, int iAmt, void **pp) |
639 | 0 | { |
640 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
641 | 0 | MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3); |
642 | 0 | return p->pReal->pMethods->xFetch(p->pReal, iOff, iAmt, pp); |
643 | 0 | } |
644 | | |
645 | | int |
646 | | xUnfetch(sqlite3_file *pFile, sqlite3_int64 iOff, void *pResOut) |
647 | 0 | { |
648 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
649 | 0 | MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3); |
650 | 0 | return p->pReal->pMethods->xUnfetch(p->pReal, iOff, pResOut); |
651 | 0 | } |
652 | | |
653 | | int |
654 | | xOpen(sqlite3_vfs* vfs, const char *zName, sqlite3_file* pFile, |
655 | | int flags, int *pOutFlags) |
656 | 0 | { |
657 | 0 | IOThreadAutoTimer ioTimer(Telemetry::MOZ_SQLITE_OPEN_MS, |
658 | 0 | IOInterposeObserver::OpCreateOrOpen); |
659 | 0 | Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_OPEN_MS> timer; |
660 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
661 | 0 | int rc; |
662 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
663 | 0 | Histograms *h = nullptr; |
664 | 0 | // check if the filename is one we are probing for |
665 | 0 | for(size_t i = 0;i < sizeof(gHistograms)/sizeof(gHistograms[0]);i++) { |
666 | 0 | h = &gHistograms[i]; |
667 | 0 | // last probe is the fallback probe |
668 | 0 | if (!h->name) |
669 | 0 | break; |
670 | 0 | if (!zName) |
671 | 0 | continue; |
672 | 0 | const char *match = strstr(zName, h->name); |
673 | 0 | if (!match) |
674 | 0 | continue; |
675 | 0 | char c = match[strlen(h->name)]; |
676 | 0 | // include -wal/-journal too |
677 | 0 | if (!c || c == '-') |
678 | 0 | break; |
679 | 0 | } |
680 | 0 | p->histograms = h; |
681 | 0 |
|
682 | 0 | MaybeEstablishQuotaControl(zName, p, flags); |
683 | 0 |
|
684 | 0 | rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags); |
685 | 0 | if( rc != SQLITE_OK ) |
686 | 0 | return rc; |
687 | 0 | |
688 | 0 | if (zName) { |
689 | 0 | p->location = new char[7 + strlen(zName) + 1]; |
690 | 0 | strcpy(p->location, "file://"); |
691 | 0 | strcpy(p->location + 7, zName); |
692 | 0 | } else { |
693 | 0 | p->location = new char[8]; |
694 | 0 | strcpy(p->location, "file://"); |
695 | 0 | } |
696 | 0 |
|
697 | 0 | if( p->pReal->pMethods ){ |
698 | 0 | sqlite3_io_methods *pNew = new sqlite3_io_methods; |
699 | 0 | const sqlite3_io_methods *pSub = p->pReal->pMethods; |
700 | 0 | memset(pNew, 0, sizeof(*pNew)); |
701 | 0 | // If the io_methods version is higher than the last known one, you should |
702 | 0 | // update this VFS adding appropriate IO methods for any methods added in |
703 | 0 | // the version change. |
704 | 0 | pNew->iVersion = pSub->iVersion; |
705 | 0 | MOZ_ASSERT(pNew->iVersion <= LAST_KNOWN_IOMETHODS_VERSION); |
706 | 0 | pNew->xClose = xClose; |
707 | 0 | pNew->xRead = xRead; |
708 | 0 | pNew->xWrite = xWrite; |
709 | 0 | pNew->xTruncate = xTruncate; |
710 | 0 | pNew->xSync = xSync; |
711 | 0 | pNew->xFileSize = xFileSize; |
712 | 0 | pNew->xLock = xLock; |
713 | 0 | pNew->xUnlock = xUnlock; |
714 | 0 | pNew->xCheckReservedLock = xCheckReservedLock; |
715 | 0 | pNew->xFileControl = xFileControl; |
716 | 0 | pNew->xSectorSize = xSectorSize; |
717 | 0 | pNew->xDeviceCharacteristics = xDeviceCharacteristics; |
718 | 0 | if (pNew->iVersion >= 2) { |
719 | 0 | // Methods added in version 2. |
720 | 0 | pNew->xShmMap = pSub->xShmMap ? xShmMap : 0; |
721 | 0 | pNew->xShmLock = pSub->xShmLock ? xShmLock : 0; |
722 | 0 | pNew->xShmBarrier = pSub->xShmBarrier ? xShmBarrier : 0; |
723 | 0 | pNew->xShmUnmap = pSub->xShmUnmap ? xShmUnmap : 0; |
724 | 0 | } |
725 | 0 | if (pNew->iVersion >= 3) { |
726 | 0 | // Methods added in version 3. |
727 | 0 | // SQLite 3.7.17 calls these methods without checking for nullptr first, |
728 | 0 | // so we always define them. Verify that we're not going to call |
729 | 0 | // nullptrs, though. |
730 | 0 | MOZ_ASSERT(pSub->xFetch); |
731 | 0 | pNew->xFetch = xFetch; |
732 | 0 | MOZ_ASSERT(pSub->xUnfetch); |
733 | 0 | pNew->xUnfetch = xUnfetch; |
734 | 0 | } |
735 | 0 | pFile->pMethods = pNew; |
736 | 0 | } |
737 | 0 | return rc; |
738 | 0 | } |
739 | | |
740 | | int |
741 | | xDelete(sqlite3_vfs* vfs, const char *zName, int syncDir) |
742 | 0 | { |
743 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
744 | 0 | int rc; |
745 | 0 | RefPtr<QuotaObject> quotaObject; |
746 | 0 |
|
747 | 0 | if (StringEndsWith(nsDependentCString(zName), NS_LITERAL_CSTRING("-wal"))) { |
748 | 0 | const char *zURIParameterKey = DatabasePathFromWALPath(zName); |
749 | 0 | MOZ_ASSERT(zURIParameterKey); |
750 | 0 |
|
751 | 0 | quotaObject = GetQuotaObjectFromNameAndParameters(zName, zURIParameterKey); |
752 | 0 | } |
753 | 0 |
|
754 | 0 | rc = orig_vfs->xDelete(orig_vfs, zName, syncDir); |
755 | 0 | if (rc == SQLITE_OK && quotaObject) { |
756 | 0 | MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true)); |
757 | 0 | } |
758 | 0 |
|
759 | 0 | return rc; |
760 | 0 | } |
761 | | |
762 | | int |
763 | | xAccess(sqlite3_vfs *vfs, const char *zName, int flags, int *pResOut) |
764 | 0 | { |
765 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
766 | 0 | return orig_vfs->xAccess(orig_vfs, zName, flags, pResOut); |
767 | 0 | } |
768 | | |
769 | | int |
770 | | xFullPathname(sqlite3_vfs *vfs, const char *zName, int nOut, char *zOut) |
771 | 0 | { |
772 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
773 | 0 | return orig_vfs->xFullPathname(orig_vfs, zName, nOut, zOut); |
774 | 0 | } |
775 | | |
776 | | void* |
777 | | xDlOpen(sqlite3_vfs *vfs, const char *zFilename) |
778 | 0 | { |
779 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
780 | 0 | return orig_vfs->xDlOpen(orig_vfs, zFilename); |
781 | 0 | } |
782 | | |
783 | | void |
784 | | xDlError(sqlite3_vfs *vfs, int nByte, char *zErrMsg) |
785 | 0 | { |
786 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
787 | 0 | orig_vfs->xDlError(orig_vfs, nByte, zErrMsg); |
788 | 0 | } |
789 | | |
790 | | void |
791 | 0 | (*xDlSym(sqlite3_vfs *vfs, void *pHdle, const char *zSym))(void){ |
792 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
793 | 0 | return orig_vfs->xDlSym(orig_vfs, pHdle, zSym); |
794 | 0 | } |
795 | | |
796 | | void |
797 | | xDlClose(sqlite3_vfs *vfs, void *pHandle) |
798 | 0 | { |
799 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
800 | 0 | orig_vfs->xDlClose(orig_vfs, pHandle); |
801 | 0 | } |
802 | | |
803 | | int |
804 | | xRandomness(sqlite3_vfs *vfs, int nByte, char *zOut) |
805 | 0 | { |
806 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
807 | 0 | return orig_vfs->xRandomness(orig_vfs, nByte, zOut); |
808 | 0 | } |
809 | | |
810 | | int |
811 | | xSleep(sqlite3_vfs *vfs, int microseconds) |
812 | 0 | { |
813 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
814 | 0 | return orig_vfs->xSleep(orig_vfs, microseconds); |
815 | 0 | } |
816 | | |
817 | | int |
818 | | xCurrentTime(sqlite3_vfs *vfs, double *prNow) |
819 | 0 | { |
820 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
821 | 0 | return orig_vfs->xCurrentTime(orig_vfs, prNow); |
822 | 0 | } |
823 | | |
824 | | int |
825 | | xGetLastError(sqlite3_vfs *vfs, int nBuf, char *zBuf) |
826 | 0 | { |
827 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
828 | 0 | return orig_vfs->xGetLastError(orig_vfs, nBuf, zBuf); |
829 | 0 | } |
830 | | |
831 | | int |
832 | | xCurrentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *piNow) |
833 | 0 | { |
834 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
835 | 0 | return orig_vfs->xCurrentTimeInt64(orig_vfs, piNow); |
836 | 0 | } |
837 | | |
838 | | static |
839 | | int |
840 | | xSetSystemCall(sqlite3_vfs *vfs, const char *zName, sqlite3_syscall_ptr pFunc) |
841 | 0 | { |
842 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
843 | 0 | return orig_vfs->xSetSystemCall(orig_vfs, zName, pFunc); |
844 | 0 | } |
845 | | |
846 | | static |
847 | | sqlite3_syscall_ptr |
848 | | xGetSystemCall(sqlite3_vfs *vfs, const char *zName) |
849 | 0 | { |
850 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
851 | 0 | return orig_vfs->xGetSystemCall(orig_vfs, zName); |
852 | 0 | } |
853 | | |
854 | | static |
855 | | const char * |
856 | | xNextSystemCall(sqlite3_vfs *vfs, const char *zName) |
857 | 0 | { |
858 | 0 | sqlite3_vfs *orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData); |
859 | 0 | return orig_vfs->xNextSystemCall(orig_vfs, zName); |
860 | 0 | } |
861 | | |
862 | | } // namespace |
863 | | |
864 | | namespace mozilla { |
865 | | namespace storage { |
866 | | |
867 | | const char *GetVFSName() |
868 | 0 | { |
869 | 0 | return "telemetry-vfs"; |
870 | 0 | } |
871 | | |
872 | | sqlite3_vfs* ConstructTelemetryVFS() |
873 | 0 | { |
874 | | #if defined(XP_WIN) |
875 | | #define EXPECTED_VFS "win32" |
876 | | #define EXPECTED_VFS_EXCL "win32" |
877 | | #else |
878 | 0 | #define EXPECTED_VFS "unix" |
879 | 0 | #define EXPECTED_VFS_EXCL "unix-excl" |
880 | 0 | #endif |
881 | 0 |
|
882 | 0 | bool expected_vfs; |
883 | 0 | sqlite3_vfs *vfs; |
884 | 0 | if (Preferences::GetBool(PREF_MULTI_PROCESS_ACCESS, false)) { |
885 | 0 | // Use the non-exclusive VFS. |
886 | 0 | vfs = sqlite3_vfs_find(nullptr); |
887 | 0 | expected_vfs = vfs->zName && !strcmp(vfs->zName, EXPECTED_VFS); |
888 | 0 | } else { |
889 | 0 | vfs = sqlite3_vfs_find(EXPECTED_VFS_EXCL); |
890 | 0 | expected_vfs = (vfs != nullptr); |
891 | 0 | } |
892 | 0 | if (!expected_vfs) { |
893 | 0 | return nullptr; |
894 | 0 | } |
895 | 0 | |
896 | 0 | sqlite3_vfs *tvfs = new ::sqlite3_vfs; |
897 | 0 | memset(tvfs, 0, sizeof(::sqlite3_vfs)); |
898 | 0 | // If the VFS version is higher than the last known one, you should update |
899 | 0 | // this VFS adding appropriate methods for any methods added in the version |
900 | 0 | // change. |
901 | 0 | tvfs->iVersion = vfs->iVersion; |
902 | 0 | MOZ_ASSERT(vfs->iVersion <= LAST_KNOWN_VFS_VERSION); |
903 | 0 | tvfs->szOsFile = sizeof(telemetry_file) - sizeof(sqlite3_file) + vfs->szOsFile; |
904 | 0 | tvfs->mxPathname = vfs->mxPathname; |
905 | 0 | tvfs->zName = GetVFSName(); |
906 | 0 | tvfs->pAppData = vfs; |
907 | 0 | tvfs->xOpen = xOpen; |
908 | 0 | tvfs->xDelete = xDelete; |
909 | 0 | tvfs->xAccess = xAccess; |
910 | 0 | tvfs->xFullPathname = xFullPathname; |
911 | 0 | tvfs->xDlOpen = xDlOpen; |
912 | 0 | tvfs->xDlError = xDlError; |
913 | 0 | tvfs->xDlSym = xDlSym; |
914 | 0 | tvfs->xDlClose = xDlClose; |
915 | 0 | tvfs->xRandomness = xRandomness; |
916 | 0 | tvfs->xSleep = xSleep; |
917 | 0 | tvfs->xCurrentTime = xCurrentTime; |
918 | 0 | tvfs->xGetLastError = xGetLastError; |
919 | 0 | if (tvfs->iVersion >= 2) { |
920 | 0 | // Methods added in version 2. |
921 | 0 | tvfs->xCurrentTimeInt64 = xCurrentTimeInt64; |
922 | 0 | } |
923 | 0 | if (tvfs->iVersion >= 3) { |
924 | 0 | // Methods added in version 3. |
925 | 0 | tvfs->xSetSystemCall = xSetSystemCall; |
926 | 0 | tvfs->xGetSystemCall = xGetSystemCall; |
927 | 0 | tvfs->xNextSystemCall = xNextSystemCall; |
928 | 0 | } |
929 | 0 | return tvfs; |
930 | 0 | } |
931 | | |
932 | | already_AddRefed<QuotaObject> |
933 | | GetQuotaObjectForFile(sqlite3_file *pFile) |
934 | 0 | { |
935 | 0 | MOZ_ASSERT(pFile); |
936 | 0 |
|
937 | 0 | telemetry_file *p = (telemetry_file *)pFile; |
938 | 0 | RefPtr<QuotaObject> result = p->quotaObject; |
939 | 0 | return result.forget(); |
940 | 0 | } |
941 | | |
942 | | } // namespace storage |
943 | | } // namespace mozilla |