blob: e2fe138e505b589b6682f8c176de7cb8d71765fe [file] [log] [blame]
// 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/util/capture_util.h"
#include "third_party/mojo/src/mojo/public/cpp/bindings/lib/template_util.h"
#include "third_party/sqlite/sqlite3.h"
using mojo::Capture;
namespace sql {
sqlite3_vfs* GetParentVFS(sqlite3_vfs* mojo_vfs) {
return static_cast<ScopedMojoFilesystemVFS*>(mojo_vfs->pAppData)->parent_;
}
filesystem::DirectoryPtr& GetRootDirectory(sqlite3_vfs* mojo_vfs) {
return static_cast<ScopedMojoFilesystemVFS*>(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<MojoVFSFile*>(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<uint8_t> 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_t>(size))
return SQLITE_OK;
// We didn't read the entire buffer. Fill the rest of the buffer with zeros.
memset(reinterpret_cast<char*>(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<uint8_t> 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<uint32_t>(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