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 "sqlite3.h"
10 #include "nsThreadUtils.h"
11 #include "mozilla/dom/quota/PersistenceType.h"
12 #include "mozilla/dom/quota/QuotaManager.h"
13 #include "mozilla/dom/quota/QuotaObject.h"
14 #include "mozilla/net/IOActivityMonitor.h"
15 #include "mozilla/IOInterposer.h"
16 #include "nsEscape.h"
17 #include "mozilla/StaticPrefs_storage.h"
18 
19 #ifdef XP_WIN
20 #  include "mozilla/StaticPrefs_dom.h"
21 #endif
22 
23 // The last VFS version for which this file has been updated.
24 #define LAST_KNOWN_VFS_VERSION 3
25 
26 // The last io_methods version for which this file has been updated.
27 #define LAST_KNOWN_IOMETHODS_VERSION 3
28 
29 namespace {
30 
31 using namespace mozilla;
32 using namespace mozilla::dom::quota;
33 using namespace mozilla::net;
34 
35 struct Histograms {
36   const char* name;
37   const Telemetry::HistogramID readB;
38   const Telemetry::HistogramID writeB;
39   const Telemetry::HistogramID readMS;
40   const Telemetry::HistogramID writeMS;
41   const Telemetry::HistogramID syncMS;
42 };
43 
44 #define SQLITE_TELEMETRY(FILENAME, HGRAM)             \
45   {                                                   \
46     FILENAME, Telemetry::MOZ_SQLITE_##HGRAM##_READ_B, \
47         Telemetry::MOZ_SQLITE_##HGRAM##_WRITE_B,      \
48         Telemetry::MOZ_SQLITE_##HGRAM##_READ_MS,      \
49         Telemetry::MOZ_SQLITE_##HGRAM##_WRITE_MS,     \
50         Telemetry::MOZ_SQLITE_##HGRAM##_SYNC_MS       \
51   }
52 
53 Histograms gHistograms[] = {SQLITE_TELEMETRY("places.sqlite", PLACES),
54                             SQLITE_TELEMETRY("cookies.sqlite", COOKIES),
55                             SQLITE_TELEMETRY("webappsstore.sqlite", WEBAPPS),
56                             SQLITE_TELEMETRY(nullptr, OTHER)};
57 #undef SQLITE_TELEMETRY
58 
59 /** RAII class for measuring how long io takes on/off main thread
60  */
61 class IOThreadAutoTimer {
62  public:
63   /**
64    * IOThreadAutoTimer measures time spent in IO. Additionally it
65    * automatically determines whether IO is happening on the main
66    * thread and picks an appropriate histogram.
67    *
68    * @param id takes a telemetry histogram id. The id+1 must be an
69    * equivalent histogram for the main thread. Eg, MOZ_SQLITE_OPEN_MS
70    * is followed by MOZ_SQLITE_OPEN_MAIN_THREAD_MS.
71    *
72    * @param aOp optionally takes an IO operation to report through the
73    * IOInterposer. Filename will be reported as NULL, and reference will be
74    * either "sqlite-mainthread" or "sqlite-otherthread".
75    */
IOThreadAutoTimer(Telemetry::HistogramID aId,IOInterposeObserver::Operation aOp=IOInterposeObserver::OpNone)76   explicit IOThreadAutoTimer(
77       Telemetry::HistogramID aId,
78       IOInterposeObserver::Operation aOp = IOInterposeObserver::OpNone)
79       : start(TimeStamp::Now()),
80         id(aId)
81 #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN)
82         ,
83         op(aOp)
84 #endif
85   {
86   }
87 
88   /**
89    * This constructor is for when we want to report an operation to
90    * IOInterposer but do not require a telemetry probe.
91    *
92    * @param aOp IO Operation to report through the IOInterposer.
93    */
IOThreadAutoTimer(IOInterposeObserver::Operation aOp)94   explicit IOThreadAutoTimer(IOInterposeObserver::Operation aOp)
95       : start(TimeStamp::Now()),
96         id(Telemetry::HistogramCount)
97 #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN)
98         ,
99         op(aOp)
100 #endif
101   {
102   }
103 
~IOThreadAutoTimer()104   ~IOThreadAutoTimer() {
105     TimeStamp end(TimeStamp::Now());
106     uint32_t mainThread = NS_IsMainThread() ? 1 : 0;
107     if (id != Telemetry::HistogramCount) {
108       Telemetry::AccumulateTimeDelta(
109           static_cast<Telemetry::HistogramID>(id + mainThread), start, end);
110     }
111     // We don't report SQLite I/O on Windows because we have a comprehensive
112     // mechanism for intercepting I/O on that platform that captures a superset
113     // of the data captured here.
114 #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN)
115     if (IOInterposer::IsObservedOperation(op)) {
116       const char* main_ref = "sqlite-mainthread";
117       const char* other_ref = "sqlite-otherthread";
118 
119       // Create observation
120       IOInterposeObserver::Observation ob(op, start, end,
121                                           (mainThread ? main_ref : other_ref));
122       // Report observation
123       IOInterposer::Report(ob);
124     }
125 #endif /* defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN) */
126   }
127 
128  private:
129   const TimeStamp start;
130   const Telemetry::HistogramID id;
131 #if defined(MOZ_GECKO_PROFILER) && !defined(XP_WIN)
132   IOInterposeObserver::Operation op;
133 #endif
134 };
135 
136 struct telemetry_file {
137   // Base class.  Must be first
138   sqlite3_file base;
139 
140   // histograms pertaining to this file
141   Histograms* histograms;
142 
143   // quota object for this file
144   RefPtr<QuotaObject> quotaObject;
145 
146   // The chunk size for this file. See the documentation for
147   // sqlite3_file_control() and FCNTL_CHUNK_SIZE.
148   int fileChunkSize;
149 
150   // The filename
151   char* location;
152 
153   // This contains the vfs that actually does work
154   sqlite3_file pReal[1];
155 };
156 
GetQuotaObjectFromName(const char * zName)157 already_AddRefed<QuotaObject> GetQuotaObjectFromName(const char* zName) {
158   MOZ_ASSERT(zName);
159 
160   const char* directoryLockIdParam =
161       sqlite3_uri_parameter(zName, "directoryLockId");
162   if (!directoryLockIdParam) {
163     return nullptr;
164   }
165 
166   nsresult rv;
167   const int64_t directoryLockId =
168       nsDependentCString(directoryLockIdParam).ToInteger64(&rv);
169   MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
170 
171   QuotaManager* quotaManager = QuotaManager::Get();
172   MOZ_ASSERT(quotaManager);
173 
174   return quotaManager->GetQuotaObject(directoryLockId,
175                                       NS_ConvertUTF8toUTF16(zName));
176 }
177 
MaybeEstablishQuotaControl(const char * zName,telemetry_file * pFile,int flags)178 void MaybeEstablishQuotaControl(const char* zName, telemetry_file* pFile,
179                                 int flags) {
180   MOZ_ASSERT(pFile);
181   MOZ_ASSERT(!pFile->quotaObject);
182 
183   if (!(flags & (SQLITE_OPEN_URI | SQLITE_OPEN_WAL))) {
184     return;
185   }
186   pFile->quotaObject = GetQuotaObjectFromName(zName);
187 }
188 
189 /*
190 ** Close a telemetry_file.
191 */
xClose(sqlite3_file * pFile)192 int xClose(sqlite3_file* pFile) {
193   telemetry_file* p = (telemetry_file*)pFile;
194   int rc;
195   {  // Scope for IOThreadAutoTimer
196     IOThreadAutoTimer ioTimer(IOInterposeObserver::OpClose);
197     rc = p->pReal->pMethods->xClose(p->pReal);
198   }
199   if (rc == SQLITE_OK) {
200     delete p->base.pMethods;
201     p->base.pMethods = nullptr;
202     p->quotaObject = nullptr;
203     delete[] p->location;
204 #ifdef DEBUG
205     p->fileChunkSize = 0;
206 #endif
207   }
208   return rc;
209 }
210 
211 /*
212 ** Read data from a telemetry_file.
213 */
xRead(sqlite3_file * pFile,void * zBuf,int iAmt,sqlite_int64 iOfst)214 int xRead(sqlite3_file* pFile, void* zBuf, int iAmt, sqlite_int64 iOfst) {
215   telemetry_file* p = (telemetry_file*)pFile;
216   IOThreadAutoTimer ioTimer(p->histograms->readMS, IOInterposeObserver::OpRead);
217   int rc;
218   rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
219   if (rc == SQLITE_OK && IOActivityMonitor::IsActive()) {
220     IOActivityMonitor::Read(nsDependentCString(p->location), iAmt);
221   }
222   // sqlite likes to read from empty files, this is normal, ignore it.
223   if (rc != SQLITE_IOERR_SHORT_READ)
224     Telemetry::Accumulate(p->histograms->readB, rc == SQLITE_OK ? iAmt : 0);
225   return rc;
226 }
227 
228 /*
229 ** Return the current file-size of a telemetry_file.
230 */
xFileSize(sqlite3_file * pFile,sqlite_int64 * pSize)231 int xFileSize(sqlite3_file* pFile, sqlite_int64* pSize) {
232   IOThreadAutoTimer ioTimer(IOInterposeObserver::OpStat);
233   telemetry_file* p = (telemetry_file*)pFile;
234   int rc;
235   rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
236   return rc;
237 }
238 
239 /*
240 ** Write data to a telemetry_file.
241 */
xWrite(sqlite3_file * pFile,const void * zBuf,int iAmt,sqlite_int64 iOfst)242 int xWrite(sqlite3_file* pFile, const void* zBuf, int iAmt,
243            sqlite_int64 iOfst) {
244   telemetry_file* p = (telemetry_file*)pFile;
245   IOThreadAutoTimer ioTimer(p->histograms->writeMS,
246                             IOInterposeObserver::OpWrite);
247   int rc;
248   if (p->quotaObject) {
249     MOZ_ASSERT(INT64_MAX - iOfst >= iAmt);
250     if (!p->quotaObject->MaybeUpdateSize(iOfst + iAmt, /* aTruncate */ false)) {
251       return SQLITE_FULL;
252     }
253   }
254   rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
255   if (rc == SQLITE_OK && IOActivityMonitor::IsActive()) {
256     IOActivityMonitor::Write(nsDependentCString(p->location), iAmt);
257   }
258 
259   Telemetry::Accumulate(p->histograms->writeB, rc == SQLITE_OK ? iAmt : 0);
260   if (p->quotaObject && rc != SQLITE_OK) {
261     NS_WARNING(
262         "xWrite failed on a quota-controlled file, attempting to "
263         "update its current size...");
264     sqlite_int64 currentSize;
265     if (xFileSize(pFile, &currentSize) == SQLITE_OK) {
266       DebugOnly<bool> res =
267           p->quotaObject->MaybeUpdateSize(currentSize, /* aTruncate */ true);
268       MOZ_ASSERT(res);
269     }
270   }
271   return rc;
272 }
273 
274 /*
275 ** Truncate a telemetry_file.
276 */
xTruncate(sqlite3_file * pFile,sqlite_int64 size)277 int xTruncate(sqlite3_file* pFile, sqlite_int64 size) {
278   IOThreadAutoTimer ioTimer(Telemetry::MOZ_SQLITE_TRUNCATE_MS);
279   telemetry_file* p = (telemetry_file*)pFile;
280   int rc;
281   Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_TRUNCATE_MS> timer;
282   if (p->quotaObject) {
283     if (p->fileChunkSize > 0) {
284       // Round up to the smallest multiple of the chunk size that will hold all
285       // the data.
286       size =
287           ((size + p->fileChunkSize - 1) / p->fileChunkSize) * p->fileChunkSize;
288     }
289     if (!p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true)) {
290       return SQLITE_FULL;
291     }
292   }
293   rc = p->pReal->pMethods->xTruncate(p->pReal, size);
294   if (p->quotaObject) {
295     if (rc == SQLITE_OK) {
296 #ifdef DEBUG
297       // Make sure xTruncate set the size exactly as we calculated above.
298       sqlite_int64 newSize;
299       MOZ_ASSERT(xFileSize(pFile, &newSize) == SQLITE_OK);
300       MOZ_ASSERT(newSize == size);
301 #endif
302     } else {
303       NS_WARNING(
304           "xTruncate failed on a quota-controlled file, attempting to "
305           "update its current size...");
306       if (xFileSize(pFile, &size) == SQLITE_OK) {
307         DebugOnly<bool> res =
308             p->quotaObject->MaybeUpdateSize(size, /* aTruncate */ true);
309         MOZ_ASSERT(res);
310       }
311     }
312   }
313   return rc;
314 }
315 
316 /*
317 ** Sync a telemetry_file.
318 */
xSync(sqlite3_file * pFile,int flags)319 int xSync(sqlite3_file* pFile, int flags) {
320   telemetry_file* p = (telemetry_file*)pFile;
321   IOThreadAutoTimer ioTimer(p->histograms->syncMS,
322                             IOInterposeObserver::OpFSync);
323   return p->pReal->pMethods->xSync(p->pReal, flags);
324 }
325 
326 /*
327 ** Lock a telemetry_file.
328 */
xLock(sqlite3_file * pFile,int eLock)329 int xLock(sqlite3_file* pFile, int eLock) {
330   telemetry_file* p = (telemetry_file*)pFile;
331   int rc;
332   rc = p->pReal->pMethods->xLock(p->pReal, eLock);
333   return rc;
334 }
335 
336 /*
337 ** Unlock a telemetry_file.
338 */
xUnlock(sqlite3_file * pFile,int eLock)339 int xUnlock(sqlite3_file* pFile, int eLock) {
340   telemetry_file* p = (telemetry_file*)pFile;
341   int rc;
342   rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
343   return rc;
344 }
345 
346 /*
347 ** Check if another file-handle holds a RESERVED lock on a telemetry_file.
348 */
xCheckReservedLock(sqlite3_file * pFile,int * pResOut)349 int xCheckReservedLock(sqlite3_file* pFile, int* pResOut) {
350   telemetry_file* p = (telemetry_file*)pFile;
351   int rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
352   return rc;
353 }
354 
355 /*
356 ** File control method. For custom operations on a telemetry_file.
357 */
xFileControl(sqlite3_file * pFile,int op,void * pArg)358 int xFileControl(sqlite3_file* pFile, int op, void* pArg) {
359   telemetry_file* p = (telemetry_file*)pFile;
360   int rc;
361   // Hook SQLITE_FCNTL_SIZE_HINT for quota-controlled files and do the necessary
362   // work before passing to the SQLite VFS.
363   if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject) {
364     sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg);
365     sqlite3_int64 currentSize;
366     rc = xFileSize(pFile, &currentSize);
367     if (rc != SQLITE_OK) {
368       return rc;
369     }
370     if (hintSize > currentSize) {
371       rc = xTruncate(pFile, hintSize);
372       if (rc != SQLITE_OK) {
373         return rc;
374       }
375     }
376   }
377   rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
378   // Grab the file chunk size after the SQLite VFS has approved.
379   if (op == SQLITE_FCNTL_CHUNK_SIZE && rc == SQLITE_OK) {
380     p->fileChunkSize = *static_cast<int*>(pArg);
381   }
382 #ifdef DEBUG
383   if (op == SQLITE_FCNTL_SIZE_HINT && p->quotaObject && rc == SQLITE_OK) {
384     sqlite3_int64 hintSize = *static_cast<sqlite3_int64*>(pArg);
385     if (p->fileChunkSize > 0) {
386       hintSize = ((hintSize + p->fileChunkSize - 1) / p->fileChunkSize) *
387                  p->fileChunkSize;
388     }
389     sqlite3_int64 currentSize;
390     MOZ_ASSERT(xFileSize(pFile, &currentSize) == SQLITE_OK);
391     MOZ_ASSERT(currentSize >= hintSize);
392   }
393 #endif
394   return rc;
395 }
396 
397 /*
398 ** Return the sector-size in bytes for a telemetry_file.
399 */
xSectorSize(sqlite3_file * pFile)400 int xSectorSize(sqlite3_file* pFile) {
401   telemetry_file* p = (telemetry_file*)pFile;
402   int rc;
403   rc = p->pReal->pMethods->xSectorSize(p->pReal);
404   return rc;
405 }
406 
407 /*
408 ** Return the device characteristic flags supported by a telemetry_file.
409 */
xDeviceCharacteristics(sqlite3_file * pFile)410 int xDeviceCharacteristics(sqlite3_file* pFile) {
411   telemetry_file* p = (telemetry_file*)pFile;
412   int rc;
413   rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
414   return rc;
415 }
416 
417 /*
418 ** Shared-memory operations.
419 */
xShmLock(sqlite3_file * pFile,int ofst,int n,int flags)420 int xShmLock(sqlite3_file* pFile, int ofst, int n, int flags) {
421   telemetry_file* p = (telemetry_file*)pFile;
422   return p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
423 }
424 
xShmMap(sqlite3_file * pFile,int iRegion,int szRegion,int isWrite,void volatile ** pp)425 int xShmMap(sqlite3_file* pFile, int iRegion, int szRegion, int isWrite,
426             void volatile** pp) {
427   telemetry_file* p = (telemetry_file*)pFile;
428   int rc;
429   rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
430   return rc;
431 }
432 
xShmBarrier(sqlite3_file * pFile)433 void xShmBarrier(sqlite3_file* pFile) {
434   telemetry_file* p = (telemetry_file*)pFile;
435   p->pReal->pMethods->xShmBarrier(p->pReal);
436 }
437 
xShmUnmap(sqlite3_file * pFile,int delFlag)438 int xShmUnmap(sqlite3_file* pFile, int delFlag) {
439   telemetry_file* p = (telemetry_file*)pFile;
440   int rc;
441   rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag);
442   return rc;
443 }
444 
xFetch(sqlite3_file * pFile,sqlite3_int64 iOff,int iAmt,void ** pp)445 int xFetch(sqlite3_file* pFile, sqlite3_int64 iOff, int iAmt, void** pp) {
446   telemetry_file* p = (telemetry_file*)pFile;
447   MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3);
448   return p->pReal->pMethods->xFetch(p->pReal, iOff, iAmt, pp);
449 }
450 
xUnfetch(sqlite3_file * pFile,sqlite3_int64 iOff,void * pResOut)451 int xUnfetch(sqlite3_file* pFile, sqlite3_int64 iOff, void* pResOut) {
452   telemetry_file* p = (telemetry_file*)pFile;
453   MOZ_ASSERT(p->pReal->pMethods->iVersion >= 3);
454   return p->pReal->pMethods->xUnfetch(p->pReal, iOff, pResOut);
455 }
456 
xOpen(sqlite3_vfs * vfs,const char * zName,sqlite3_file * pFile,int flags,int * pOutFlags)457 int xOpen(sqlite3_vfs* vfs, const char* zName, sqlite3_file* pFile, int flags,
458           int* pOutFlags) {
459   IOThreadAutoTimer ioTimer(Telemetry::MOZ_SQLITE_OPEN_MS,
460                             IOInterposeObserver::OpCreateOrOpen);
461   Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_OPEN_MS> timer;
462   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
463   int rc;
464   telemetry_file* p = (telemetry_file*)pFile;
465   Histograms* h = nullptr;
466   // check if the filename is one we are probing for
467   for (size_t i = 0; i < sizeof(gHistograms) / sizeof(gHistograms[0]); i++) {
468     h = &gHistograms[i];
469     // last probe is the fallback probe
470     if (!h->name) break;
471     if (!zName) continue;
472     const char* match = strstr(zName, h->name);
473     if (!match) continue;
474     char c = match[strlen(h->name)];
475     // include -wal/-journal too
476     if (!c || c == '-') break;
477   }
478   p->histograms = h;
479 
480   MaybeEstablishQuotaControl(zName, p, flags);
481 
482   rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags);
483   if (rc != SQLITE_OK) return rc;
484 
485   if (zName) {
486     p->location = new char[7 + strlen(zName) + 1];
487     strcpy(p->location, "file://");
488     strcpy(p->location + 7, zName);
489   } else {
490     p->location = new char[8];
491     strcpy(p->location, "file://");
492   }
493 
494   if (p->pReal->pMethods) {
495     sqlite3_io_methods* pNew = new sqlite3_io_methods;
496     const sqlite3_io_methods* pSub = p->pReal->pMethods;
497     memset(pNew, 0, sizeof(*pNew));
498     // If the io_methods version is higher than the last known one, you should
499     // update this VFS adding appropriate IO methods for any methods added in
500     // the version change.
501     pNew->iVersion = pSub->iVersion;
502     MOZ_ASSERT(pNew->iVersion <= LAST_KNOWN_IOMETHODS_VERSION);
503     pNew->xClose = xClose;
504     pNew->xRead = xRead;
505     pNew->xWrite = xWrite;
506     pNew->xTruncate = xTruncate;
507     pNew->xSync = xSync;
508     pNew->xFileSize = xFileSize;
509     pNew->xLock = xLock;
510     pNew->xUnlock = xUnlock;
511     pNew->xCheckReservedLock = xCheckReservedLock;
512     pNew->xFileControl = xFileControl;
513     pNew->xSectorSize = xSectorSize;
514     pNew->xDeviceCharacteristics = xDeviceCharacteristics;
515     if (pNew->iVersion >= 2) {
516       // Methods added in version 2.
517       pNew->xShmMap = pSub->xShmMap ? xShmMap : 0;
518       pNew->xShmLock = pSub->xShmLock ? xShmLock : 0;
519       pNew->xShmBarrier = pSub->xShmBarrier ? xShmBarrier : 0;
520       pNew->xShmUnmap = pSub->xShmUnmap ? xShmUnmap : 0;
521     }
522     if (pNew->iVersion >= 3) {
523       // Methods added in version 3.
524       // SQLite 3.7.17 calls these methods without checking for nullptr first,
525       // so we always define them.  Verify that we're not going to call
526       // nullptrs, though.
527       MOZ_ASSERT(pSub->xFetch);
528       pNew->xFetch = xFetch;
529       MOZ_ASSERT(pSub->xUnfetch);
530       pNew->xUnfetch = xUnfetch;
531     }
532     pFile->pMethods = pNew;
533   }
534   return rc;
535 }
536 
xDelete(sqlite3_vfs * vfs,const char * zName,int syncDir)537 int xDelete(sqlite3_vfs* vfs, const char* zName, int syncDir) {
538   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
539   int rc;
540   RefPtr<QuotaObject> quotaObject;
541 
542   if (StringEndsWith(nsDependentCString(zName), "-wal"_ns)) {
543     quotaObject = GetQuotaObjectFromName(zName);
544   }
545 
546   rc = orig_vfs->xDelete(orig_vfs, zName, syncDir);
547   if (rc == SQLITE_OK && quotaObject) {
548     MOZ_ALWAYS_TRUE(quotaObject->MaybeUpdateSize(0, /* aTruncate */ true));
549   }
550 
551   return rc;
552 }
553 
xAccess(sqlite3_vfs * vfs,const char * zName,int flags,int * pResOut)554 int xAccess(sqlite3_vfs* vfs, const char* zName, int flags, int* pResOut) {
555   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
556   return orig_vfs->xAccess(orig_vfs, zName, flags, pResOut);
557 }
558 
xFullPathname(sqlite3_vfs * vfs,const char * zName,int nOut,char * zOut)559 int xFullPathname(sqlite3_vfs* vfs, const char* zName, int nOut, char* zOut) {
560 #if defined(XP_WIN)
561   // SQLite uses GetFullPathnameW which also normailizes file path. If a file
562   // component ends with a dot, it would be removed. However, it's not desired.
563   //
564   // And that would result SQLite uses wrong database and quotaObject.
565   // Note that we are safe to avoid the GetFullPathnameW call for \\?\ prefixed
566   // paths.
567   // And note that this hack will be removed once the issue is fixed directly in
568   // SQLite.
569 
570   // zName that starts with "//?/" is the case when a file URI was passed and
571   // zName that starts with "\\?\" is the case when a normal path was passed
572   // (not file URI).
573   if (StaticPrefs::dom_quotaManager_overrideXFullPathname() &&
574       ((zName[0] == '/' && zName[1] == '/' && zName[2] == '?' &&
575         zName[3] == '/') ||
576        (zName[0] == '\\' && zName[1] == '\\' && zName[2] == '?' &&
577         zName[3] == '\\'))) {
578     MOZ_ASSERT(nOut >= vfs->mxPathname);
579     MOZ_ASSERT(nOut > strlen(zName));
580 
581     size_t index = 0;
582     while (zName[index] != '\0') {
583       if (zName[index] == '/') {
584         zOut[index] = '\\';
585       } else {
586         zOut[index] = zName[index];
587       }
588 
589       index++;
590     }
591     zOut[index] = '\0';
592 
593     return SQLITE_OK;
594   }
595 #endif
596 
597   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
598   return orig_vfs->xFullPathname(orig_vfs, zName, nOut, zOut);
599 }
600 
xDlOpen(sqlite3_vfs * vfs,const char * zFilename)601 void* xDlOpen(sqlite3_vfs* vfs, const char* zFilename) {
602   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
603   return orig_vfs->xDlOpen(orig_vfs, zFilename);
604 }
605 
xDlError(sqlite3_vfs * vfs,int nByte,char * zErrMsg)606 void xDlError(sqlite3_vfs* vfs, int nByte, char* zErrMsg) {
607   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
608   orig_vfs->xDlError(orig_vfs, nByte, zErrMsg);
609 }
610 
xDlSym(sqlite3_vfs * vfs,void * pHdle,const char * zSym)611 void (*xDlSym(sqlite3_vfs* vfs, void* pHdle, const char* zSym))(void) {
612   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
613   return orig_vfs->xDlSym(orig_vfs, pHdle, zSym);
614 }
615 
xDlClose(sqlite3_vfs * vfs,void * pHandle)616 void xDlClose(sqlite3_vfs* vfs, void* pHandle) {
617   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
618   orig_vfs->xDlClose(orig_vfs, pHandle);
619 }
620 
xRandomness(sqlite3_vfs * vfs,int nByte,char * zOut)621 int xRandomness(sqlite3_vfs* vfs, int nByte, char* zOut) {
622   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
623   return orig_vfs->xRandomness(orig_vfs, nByte, zOut);
624 }
625 
xSleep(sqlite3_vfs * vfs,int microseconds)626 int xSleep(sqlite3_vfs* vfs, int microseconds) {
627   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
628   return orig_vfs->xSleep(orig_vfs, microseconds);
629 }
630 
xCurrentTime(sqlite3_vfs * vfs,double * prNow)631 int xCurrentTime(sqlite3_vfs* vfs, double* prNow) {
632   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
633   return orig_vfs->xCurrentTime(orig_vfs, prNow);
634 }
635 
xGetLastError(sqlite3_vfs * vfs,int nBuf,char * zBuf)636 int xGetLastError(sqlite3_vfs* vfs, int nBuf, char* zBuf) {
637   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
638   return orig_vfs->xGetLastError(orig_vfs, nBuf, zBuf);
639 }
640 
xCurrentTimeInt64(sqlite3_vfs * vfs,sqlite3_int64 * piNow)641 int xCurrentTimeInt64(sqlite3_vfs* vfs, sqlite3_int64* piNow) {
642   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
643   return orig_vfs->xCurrentTimeInt64(orig_vfs, piNow);
644 }
645 
xSetSystemCall(sqlite3_vfs * vfs,const char * zName,sqlite3_syscall_ptr pFunc)646 static int xSetSystemCall(sqlite3_vfs* vfs, const char* zName,
647                           sqlite3_syscall_ptr pFunc) {
648   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
649   return orig_vfs->xSetSystemCall(orig_vfs, zName, pFunc);
650 }
651 
xGetSystemCall(sqlite3_vfs * vfs,const char * zName)652 static sqlite3_syscall_ptr xGetSystemCall(sqlite3_vfs* vfs, const char* zName) {
653   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
654   return orig_vfs->xGetSystemCall(orig_vfs, zName);
655 }
656 
xNextSystemCall(sqlite3_vfs * vfs,const char * zName)657 static const char* xNextSystemCall(sqlite3_vfs* vfs, const char* zName) {
658   sqlite3_vfs* orig_vfs = static_cast<sqlite3_vfs*>(vfs->pAppData);
659   return orig_vfs->xNextSystemCall(orig_vfs, zName);
660 }
661 
662 }  // namespace
663 
664 namespace mozilla {
665 namespace storage {
666 
GetTelemetryVFSName(bool exclusive)667 const char* GetTelemetryVFSName(bool exclusive) {
668   return exclusive ? "telemetry-vfs-excl" : "telemetry-vfs";
669 }
670 
ConstructTelemetryVFS(bool exclusive)671 UniquePtr<sqlite3_vfs> ConstructTelemetryVFS(bool exclusive) {
672 #if defined(XP_WIN)
673 #  define EXPECTED_VFS "win32"
674 #  define EXPECTED_VFS_EXCL "win32"
675 #else
676 #  define EXPECTED_VFS "unix"
677 #  define EXPECTED_VFS_EXCL "unix-excl"
678 #endif
679 
680   bool expected_vfs;
681   sqlite3_vfs* vfs;
682   if (!exclusive) {
683     // Use the non-exclusive VFS.
684     vfs = sqlite3_vfs_find(nullptr);
685     expected_vfs = vfs->zName && !strcmp(vfs->zName, EXPECTED_VFS);
686   } else {
687     vfs = sqlite3_vfs_find(EXPECTED_VFS_EXCL);
688     expected_vfs = (vfs != nullptr);
689   }
690   if (!expected_vfs) {
691     return nullptr;
692   }
693 
694   auto tvfs = MakeUnique<::sqlite3_vfs>();
695   memset(tvfs.get(), 0, sizeof(::sqlite3_vfs));
696   // If the VFS version is higher than the last known one, you should update
697   // this VFS adding appropriate methods for any methods added in the version
698   // change.
699   tvfs->iVersion = vfs->iVersion;
700   MOZ_ASSERT(vfs->iVersion <= LAST_KNOWN_VFS_VERSION);
701   tvfs->szOsFile =
702       sizeof(telemetry_file) - sizeof(sqlite3_file) + vfs->szOsFile;
703   tvfs->mxPathname = vfs->mxPathname;
704   tvfs->zName = GetTelemetryVFSName(exclusive);
705   tvfs->pAppData = vfs;
706   tvfs->xOpen = xOpen;
707   tvfs->xDelete = xDelete;
708   tvfs->xAccess = xAccess;
709   tvfs->xFullPathname = xFullPathname;
710   tvfs->xDlOpen = xDlOpen;
711   tvfs->xDlError = xDlError;
712   tvfs->xDlSym = xDlSym;
713   tvfs->xDlClose = xDlClose;
714   tvfs->xRandomness = xRandomness;
715   tvfs->xSleep = xSleep;
716   tvfs->xCurrentTime = xCurrentTime;
717   tvfs->xGetLastError = xGetLastError;
718   if (tvfs->iVersion >= 2) {
719     // Methods added in version 2.
720     tvfs->xCurrentTimeInt64 = xCurrentTimeInt64;
721   }
722   if (tvfs->iVersion >= 3) {
723     // Methods added in version 3.
724     tvfs->xSetSystemCall = xSetSystemCall;
725     tvfs->xGetSystemCall = xGetSystemCall;
726     tvfs->xNextSystemCall = xNextSystemCall;
727   }
728   return tvfs;
729 }
730 
GetQuotaObjectForFile(sqlite3_file * pFile)731 already_AddRefed<QuotaObject> GetQuotaObjectForFile(sqlite3_file* pFile) {
732   MOZ_ASSERT(pFile);
733 
734   telemetry_file* p = (telemetry_file*)pFile;
735   RefPtr<QuotaObject> result = p->quotaObject;
736   return result.forget();
737 }
738 
739 }  // namespace storage
740 }  // namespace mozilla
741