// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sql/mojo/mojo_vfs.h" #include "base/logging.h" #include "base/rand_util.h" #include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" #include "components/filesystem/public/interfaces/file.mojom.h" #include "components/filesystem/public/interfaces/file_system.mojom.h" #include "components/filesystem/public/interfaces/types.mojom.h" #include "mojo/public/cpp/bindings/lib/template_util.h" #include "mojo/util/capture_util.h" #include "third_party/sqlite/sqlite3.h" using mojo::Capture; namespace sql { sqlite3_vfs* GetParentVFS(sqlite3_vfs* mojo_vfs) { return static_cast(mojo_vfs->pAppData)->parent_; } filesystem::DirectoryPtr& GetRootDirectory(sqlite3_vfs* mojo_vfs) { return static_cast(mojo_vfs->pAppData)-> root_directory_; } namespace { // Implementation of the sqlite3 Mojo proxying vfs. // // This is a bunch of C callback objects which transparently proxy sqlite3's // filesystem reads/writes over the mojo:filesystem service. The main // entrypoint is sqlite3_mojovfs(), which proxies all the file open/delete/etc // operations. mojo:filesystem has support for passing a raw file descriptor // over the IPC barrier, and most of the implementation of sqlite3_io_methods // is derived from the default sqlite3 unix VFS and operates on the raw file // descriptors. const int kMaxPathName = 512; // A struct which extends the base sqlite3_file to also hold on to a file // pipe. We reinterpret_cast our sqlite3_file structs to this struct // instead. This is "safe" because this struct is really just a slab of // malloced memory, of which we tell sqlite how large we want with szOsFile. struct MojoVFSFile { // The "vtable" of our sqlite3_file "subclass". sqlite3_file base; // We keep an open pipe to the File object to keep it from cleaning itself // up. filesystem::FilePtr file_ptr; }; filesystem::FilePtr& GetFSFile(sqlite3_file* vfs_file) { return reinterpret_cast(vfs_file)->file_ptr; } int MojoVFSClose(sqlite3_file* file) { DVLOG(1) << "MojoVFSClose(*)"; TRACE_EVENT0("sql", "MojoVFSClose"); using filesystem::FilePtr; filesystem::FileError error = filesystem::FILE_ERROR_FAILED; // Must call File::Close explicitly instead of just deleting the file, since // otherwise we wouldn't have an object to wait on. GetFSFile(file)->Close(mojo::Capture(&error)); GetFSFile(file).WaitForIncomingResponse(); GetFSFile(file).~FilePtr(); return SQLITE_OK; } int MojoVFSRead(sqlite3_file* sql_file, void* buffer, int size, sqlite3_int64 offset) { DVLOG(1) << "MojoVFSRead (" << size << " @ " << offset << ")"; TRACE_EVENT0("sql", "MojoVFSRead"); filesystem::FileError error = filesystem::FILE_ERROR_FAILED; mojo::Array mojo_data; GetFSFile(sql_file)->Read(size, offset, filesystem::WHENCE_FROM_BEGIN, Capture(&error, &mojo_data)); GetFSFile(sql_file).WaitForIncomingResponse(); if (error != filesystem::FILE_ERROR_OK) { // TODO(erg): Better implementation here. NOTIMPLEMENTED(); return SQLITE_IOERR_READ; } if (mojo_data.size()) memcpy(buffer, &mojo_data.front(), mojo_data.size()); if (mojo_data.size() == static_cast(size)) return SQLITE_OK; // We didn't read the entire buffer. Fill the rest of the buffer with zeros. memset(reinterpret_cast(buffer) + mojo_data.size(), 0, size - mojo_data.size()); return SQLITE_IOERR_SHORT_READ; } int MojoVFSWrite(sqlite3_file* sql_file, const void* buffer, int size, sqlite_int64 offset) { DVLOG(1) << "MojoVFSWrite(*, " << size << ", " << offset << ")"; TRACE_EVENT0("sql", "MojoVFSWrite"); mojo::Array mojo_data(size); memcpy(&mojo_data.front(), buffer, size); filesystem::FileError error = filesystem::FILE_ERROR_FAILED; uint32_t num_bytes_written = 0; GetFSFile(sql_file)->Write(mojo_data.Pass(), offset, filesystem::WHENCE_FROM_BEGIN, Capture(&error, &num_bytes_written)); GetFSFile(sql_file).WaitForIncomingResponse(); if (error != filesystem::FILE_ERROR_OK) { // TODO(erg): Better implementation here. NOTIMPLEMENTED(); return SQLITE_IOERR_WRITE; } if (num_bytes_written != static_cast(size)) { NOTIMPLEMENTED(); return SQLITE_IOERR_WRITE; } return SQLITE_OK; } int MojoVFSTruncate(sqlite3_file* sql_file, sqlite_int64 size) { DVLOG(1) << "MojoVFSTruncate(*, " << size << ")"; TRACE_EVENT0("sql", "MojoVFSTruncate"); filesystem::FileError error = filesystem::FILE_ERROR_FAILED; GetFSFile(sql_file)->Truncate(size, Capture(&error)); GetFSFile(sql_file).WaitForIncomingResponse(); if (error != filesystem::FILE_ERROR_OK) { // TODO(erg): Better implementation here. NOTIMPLEMENTED(); return SQLITE_IOERR_TRUNCATE; } return SQLITE_OK; } int MojoVFSSync(sqlite3_file* sql_file, int flags) { DVLOG(1) << "MojoVFSSync(*, " << flags << ")"; TRACE_EVENT0("sql", "MojoVFSSync"); filesystem::FileError error = filesystem::FILE_ERROR_FAILED; GetFSFile(sql_file)->Flush(Capture(&error)); GetFSFile(sql_file).WaitForIncomingResponse(); if (error != filesystem::FILE_ERROR_OK) { // TODO(erg): Better implementation here. NOTIMPLEMENTED(); return SQLITE_IOERR_FSYNC; } return SQLITE_OK; } int MojoVFSFileSize(sqlite3_file* sql_file, sqlite_int64* size) { DVLOG(1) << "MojoVFSFileSize(*)"; TRACE_EVENT0("sql", "MojoVFSFileSize"); filesystem::FileError err = filesystem::FILE_ERROR_FAILED; filesystem::FileInformationPtr file_info; GetFSFile(sql_file)->Stat(Capture(&err, &file_info)); GetFSFile(sql_file).WaitForIncomingResponse(); if (err != filesystem::FILE_ERROR_OK) { // TODO(erg): Better implementation here. NOTIMPLEMENTED(); return SQLITE_IOERR_FSTAT; } *size = file_info->size; return SQLITE_OK; } // TODO(erg): The current base::File interface isn't sufficient to handle // sqlite's locking primitives, which are done on byte ranges in the file. (See // "File Locking Notes" in sqlite3.c.) int MojoVFSLock(sqlite3_file* pFile, int eLock) { DVLOG(1) << "MojoVFSLock(*, " << eLock << ")"; return SQLITE_OK; } int MojoVFSUnlock(sqlite3_file* pFile, int eLock) { DVLOG(1) << "MojoVFSUnlock(*, " << eLock << ")"; return SQLITE_OK; } int MojoVFSCheckReservedLock(sqlite3_file* pFile, int* pResOut) { DVLOG(1) << "MojoVFSCheckReservedLock(*)"; *pResOut = 0; return SQLITE_OK; } // TODO(erg): This is the minimal implementation to get a few tests passing; // lots more needs to be done here. int MojoVFSFileControl(sqlite3_file* pFile, int op, void* pArg) { DVLOG(1) << "MojoVFSFileControl(*, " << op << ", *)"; if (op == SQLITE_FCNTL_PRAGMA) { // Returning NOTFOUND tells sqlite that we aren't doing any processing. return SQLITE_NOTFOUND; } return SQLITE_OK; } int MojoVFSSectorSize(sqlite3_file* pFile) { DVLOG(1) << "MojoVFSSectorSize(*)"; // Use the default sector size. return 0; } int MojoVFSDeviceCharacteristics(sqlite3_file* pFile) { DVLOG(1) << "MojoVFSDeviceCharacteristics(*)"; // TODO(erg): Figure out what to return here. (This function is super spammy, // so not leaving a NOTIMPLEMENTED().) return 0; } static sqlite3_io_methods mojo_vfs_io_methods = { 1, /* iVersion */ MojoVFSClose, /* xClose */ MojoVFSRead, /* xRead */ MojoVFSWrite, /* xWrite */ MojoVFSTruncate, /* xTruncate */ MojoVFSSync, /* xSync */ MojoVFSFileSize, /* xFileSize */ MojoVFSLock, /* xLock */ MojoVFSUnlock, /* xUnlock */ MojoVFSCheckReservedLock, /* xCheckReservedLock */ MojoVFSFileControl, /* xFileControl */ MojoVFSSectorSize, /* xSectorSize */ MojoVFSDeviceCharacteristics, /* xDeviceCharacteristics */ }; int MojoVFSOpen(sqlite3_vfs* mojo_vfs, const char* name, sqlite3_file* file, int flags, int* pOutFlags) { DVLOG(1) << "MojoVFSOpen(*, " << name << ", *, " << flags << ")"; TRACE_EVENT2("sql", "MojoVFSOpen", "name", name, "flags", flags); int open_flags = 0; if (flags & SQLITE_OPEN_EXCLUSIVE) { DCHECK(flags & SQLITE_OPEN_CREATE); open_flags = filesystem::kFlagCreate; } else if (flags & SQLITE_OPEN_CREATE) { DCHECK(flags & SQLITE_OPEN_READWRITE); open_flags = filesystem::kFlagOpenAlways; } else { open_flags = filesystem::kFlagOpen; } open_flags |= filesystem::kFlagRead; if (flags & SQLITE_OPEN_READWRITE) open_flags |= filesystem::kFlagWrite; if (flags & SQLITE_OPEN_DELETEONCLOSE) open_flags |= filesystem::kDeleteOnClose; mojo::String mojo_name; if (name) { // Don't let callers open the pattern of our temporary databases. When we // open with a null name and SQLITE_OPEN_DELETEONCLOSE, we unlink the // database after we open it. If we create a database here, close it // normally, and then open the same file through the other path, we could // delete the database. CHECK(strncmp("Temp_", name, 5) != 0); mojo_name = name; } else { DCHECK(flags & SQLITE_OPEN_DELETEONCLOSE); static int temp_number = 0; mojo_name = base::StringPrintf("Temp_%d.db", temp_number++); } // Grab the incoming file filesystem::FilePtr file_ptr; filesystem::FileError error = filesystem::FILE_ERROR_FAILED; GetRootDirectory(mojo_vfs)->OpenFile(mojo_name, GetProxy(&file_ptr), open_flags, Capture(&error)); GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); if (error != filesystem::FILE_ERROR_OK) { // TODO(erg): Translate more of the mojo error codes into sqlite error // codes. return SQLITE_CANTOPEN; } // Set the method table so we can be closed (and run the manual dtor call to // match the following placement news). file->pMethods = &mojo_vfs_io_methods; // |file| is actually a malloced buffer of size szOsFile. This means that we // need to manually use placement new to construct the C++ object which owns // the pipe to our file. new (&GetFSFile(file)) filesystem::FilePtr(file_ptr.Pass()); return SQLITE_OK; } int MojoVFSDelete(sqlite3_vfs* mojo_vfs, const char* filename, int sync_dir) { DVLOG(1) << "MojoVFSDelete(*, " << filename << ", " << sync_dir << ")"; TRACE_EVENT2("sql", "MojoVFSDelete", "name", filename, "sync_dir", sync_dir); // TODO(erg): The default windows sqlite VFS has retry code to work around // antivirus software keeping files open. We'll probably have to do something // like that in the far future if we ever support Windows. filesystem::FileError error = filesystem::FILE_ERROR_FAILED; GetRootDirectory(mojo_vfs)->Delete(filename, 0, Capture(&error)); GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); if (error == filesystem::FILE_ERROR_OK && sync_dir) { GetRootDirectory(mojo_vfs)->Flush(Capture(&error)); GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); } return error == filesystem::FILE_ERROR_OK ? SQLITE_OK : SQLITE_IOERR_DELETE; } int MojoVFSAccess(sqlite3_vfs* mojo_vfs, const char* filename, int flags, int* result) { DVLOG(1) << "MojoVFSAccess(*, " << filename << ", " << flags << ", *)"; TRACE_EVENT2("sql", "MojoVFSAccess", "name", filename, "flags", flags); filesystem::FileError error = filesystem::FILE_ERROR_FAILED; if (flags == SQLITE_ACCESS_READWRITE || flags == SQLITE_ACCESS_READ) { bool is_writable = false; GetRootDirectory(mojo_vfs) ->IsWritable(filename, Capture(&error, &is_writable)); GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); *result = is_writable; return SQLITE_OK; } if (flags == SQLITE_ACCESS_EXISTS) { bool exists = false; GetRootDirectory(mojo_vfs)->Exists(filename, Capture(&error, &exists)); GetRootDirectory(mojo_vfs).WaitForIncomingResponse(); *result = exists; return SQLITE_OK; } return SQLITE_IOERR; } int MojoVFSFullPathname(sqlite3_vfs* mojo_vfs, const char* relative_path, int absolute_path_size, char* absolute_path) { // The sandboxed process doesn't need to know the absolute path of the file. sqlite3_snprintf(absolute_path_size, absolute_path, "%s", relative_path); return SQLITE_OK; } // Don't let SQLite dynamically load things. (If we are using the // mojo:filesystem proxying VFS, then it's highly likely that we are sandboxed // and that any attempt to dlopen() a shared object is folly.) void* MojoVFSDlOpen(sqlite3_vfs*, const char*) { return 0; } void MojoVFSDlError(sqlite3_vfs*, int buf_size, char* error_msg) { sqlite3_snprintf(buf_size, error_msg, "Dynamic loading not supported"); } void (*MojoVFSDlSym(sqlite3_vfs*, void*, const char*))(void) { return 0; } void MojoVFSDlClose(sqlite3_vfs*, void*) { return; } int MojoVFSRandomness(sqlite3_vfs* mojo_vfs, int size, char* out) { base::RandBytes(out, size); return size; } // Proxy the rest of the calls down to the OS specific handler. int MojoVFSSleep(sqlite3_vfs* mojo_vfs, int micro) { return GetParentVFS(mojo_vfs)->xSleep(GetParentVFS(mojo_vfs), micro); } int MojoVFSCurrentTime(sqlite3_vfs* mojo_vfs, double* time) { return GetParentVFS(mojo_vfs)->xCurrentTime(GetParentVFS(mojo_vfs), time); } int MojoVFSGetLastError(sqlite3_vfs* mojo_vfs, int a, char* b) { return 0; } int MojoVFSCurrentTimeInt64(sqlite3_vfs* mojo_vfs, sqlite3_int64* out) { return GetParentVFS(mojo_vfs)->xCurrentTimeInt64(GetParentVFS(mojo_vfs), out); } static sqlite3_vfs mojo_vfs = { 1, /* iVersion */ sizeof(MojoVFSFile), /* szOsFile */ kMaxPathName, /* mxPathname */ 0, /* pNext */ "mojo", /* zName */ 0, /* pAppData */ MojoVFSOpen, /* xOpen */ MojoVFSDelete, /* xDelete */ MojoVFSAccess, /* xAccess */ MojoVFSFullPathname, /* xFullPathname */ MojoVFSDlOpen, /* xDlOpen */ MojoVFSDlError, /* xDlError */ MojoVFSDlSym, /* xDlSym */ MojoVFSDlClose, /* xDlClose */ MojoVFSRandomness, /* xRandomness */ MojoVFSSleep, /* xSleep */ MojoVFSCurrentTime, /* xCurrentTime */ MojoVFSGetLastError, /* xGetLastError */ MojoVFSCurrentTimeInt64 /* xCurrentTimeInt64 */ }; } // namespace ScopedMojoFilesystemVFS::ScopedMojoFilesystemVFS( filesystem::DirectoryPtr root_directory) : parent_(sqlite3_vfs_find(NULL)), root_directory_(root_directory.Pass()) { CHECK(!mojo_vfs.pAppData); mojo_vfs.pAppData = this; mojo_vfs.mxPathname = parent_->mxPathname; CHECK(sqlite3_vfs_register(&mojo_vfs, 1) == SQLITE_OK); } ScopedMojoFilesystemVFS::~ScopedMojoFilesystemVFS() { CHECK(mojo_vfs.pAppData); mojo_vfs.pAppData = nullptr; CHECK(sqlite3_vfs_register(parent_, 1) == SQLITE_OK); CHECK(sqlite3_vfs_unregister(&mojo_vfs) == SQLITE_OK); } filesystem::DirectoryPtr& ScopedMojoFilesystemVFS::GetDirectory() { return root_directory_; } } // namespace sql