blob: 8dbb5a9bb475cec8d19c7c29f918edf7a9d4794b [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 "posix_translation/pepper_file.h"
#include <string.h> // memset
#include <sys/ioctl.h>
#include "base/containers/mru_cache.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/safe_strerror_posix.h"
#include "base/strings/stringprintf.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "common/arc_strace.h"
#include "common/danger.h"
#include "common/trace_event.h"
#include "posix_translation/dir.h"
#include "posix_translation/directory_file_stream.h"
#include "posix_translation/directory_manager.h"
#include "posix_translation/path_util.h"
#include "posix_translation/statfs.h"
#include "posix_translation/virtual_file_system.h"
#include "posix_translation/wrap.h"
#include "ppapi/c/pp_errors.h"
#include "ppapi/c/ppb_file_io.h"
#include "ppapi/cpp/directory_entry.h"
#include "ppapi/cpp/file_ref.h"
#include "ppapi/cpp/private/file_io_private.h"
namespace posix_translation {
namespace {
const size_t kMaxFSCacheEntries = 1024;
const blksize_t kBlockSize = 4096;
#if !defined(NDEBUG)
// TODO(crbug.com/358440): Fix the issue and remove the very ARC specific
// hack from posix_translation/.
bool IsWhitelistedFile(const std::string& name) {
// dexZipGetEntryInfo in dalvik/libdex/ZipArchive.cpp reads mmaped
// (with PROT_WRITE) region so we need to allow all .jar files.
if (EndsWith(name, ".jar", true))
return true;
// This allows the App's APK to be read/mmap'd as well as APKs passed to
// aapt and during testing.
if (EndsWith(name, ".apk", true)) {
return true;
}
if (EndsWith(name, ".dex", true)) {
return StartsWithASCII(name, "/data/dalvik-cache/", true) ||
StartsWithASCII(name, "/data/data/", true);
}
// Secondary dex files are loaded by the same code as .jar from mmaped region.
if (EndsWith(name, ".zip", true)) {
return StartsWithASCII(name, "/data/data/", true);
}
return false;
}
#endif
// Returns true if it is allowed to read/write |pathname| with |inode|. This
// function may return false if the file associated with the |inode| was/is
// mmapped. Note that "mmap(PROT_READ), munmap, then read/write" is allowed,
// but other ways of mixing mmap and read are not allowed. For production
// (when NDEBUG is defined), this function does nothing and always returns
// true.
bool IsReadWriteAllowed(const std::string& pathname, ino_t inode,
const std::string& operation_str) {
#if !defined(NDEBUG)
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
const bool is_write_mapped = sys->IsWriteMapped(inode);
const bool is_currently_mapped =
// Do not call IsCurrentlyMapped() when |is_write_mapped| is true
// for (slightly) better performance.
is_write_mapped ? false : sys->IsCurrentlyMapped(inode);
if (!is_write_mapped && !is_currently_mapped)
return true;
static const char kWarnWriteMapped[] = "was/is mmapped with PROT_WRITE";
static const char kWarnMapped[] = "is currently mmapped";
const std::string log_str = base::StringPrintf(
"%s(\"%s\") might not be safe on non-Linux environment since the file %s",
operation_str.c_str(), pathname.c_str(),
(is_write_mapped ? kWarnWriteMapped : kWarnMapped));
ALOGI("%s", log_str.c_str());
// TODO(crbug.com/358440): Stop calling IsWhitelistedFile().
if (IsWhitelistedFile(pathname))
return true;
sys->mutex().AssertAcquired(); // touching |s_show_mmap_warning| is safe.
static bool s_show_mmap_warning = true;
if (s_show_mmap_warning) {
// Show a big warning with ALOGE only once to notify developers that the
// current APK is not 100% compatible with non-Linux environment.
s_show_mmap_warning = false;
ALOGE("********* MMAP COMPATIBILITY ERROR (crbug.com/357780) *********");
ALOGE("********* %s *********", log_str.c_str());
}
return false;
#else
// For production, do not check anything for performance (crbug.com/373645).
return true;
#endif
}
} // namespace
#if defined(DEBUG_POSIX_TRANSLATION)
namespace ipc_stats {
// |VirtualFileSystem::mutex_| must be held before updating these variables.
size_t g_delete;
size_t g_fdatasync;
size_t g_fsync;
size_t g_make_directory;
size_t g_open;
size_t g_query;
size_t g_read_directory_entries;
size_t g_rename;
size_t g_set_length;
size_t g_touch;
uint64_t g_write_bytes;
uint64_t g_read_bytes;
std::string GetIPCStatsAsStringLocked() {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
const size_t total = g_delete + g_make_directory + g_open + g_query +
g_read_directory_entries + g_rename + g_set_length + g_touch;
return base::StringPrintf("PepperFile: Delete:%zu MakeDirectory:%zu Open:%zu "
"Query:%zu ReadDirectoryEntries:%zu Rename:%zu "
"SetLength:%zu Touch:%zu TOTAL:%zu, "
"FSync:%zu FDataSync: %zu, "
"BytesWritten: %llu BytesRead: %llu",
g_delete, g_make_directory, g_open, g_query,
g_read_directory_entries, g_rename, g_set_length,
g_touch, total, g_fsync, g_fdatasync,
g_write_bytes, g_read_bytes);
}
} // namespace ipc_stats
#endif
// A MRU cache to avoid doing extra calls to access/stat.
// Access is currently implemented in terms of the same function that stat is
// using. Several applications open files by calling access, followed by stat
// and open. This causes one extra superfluous call to Pepper, that can be
// avoided.
class PepperFileCache {
public:
explicit PepperFileCache(size_t size) : size_(size), cache_(size) {}
bool Get(const std::string& path, PP_FileInfo* file_info, bool* exists) {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
if (!IsCacheEnabled())
return false;
std::string key(path);
RemoveTrailingSlash(&key);
const MRUCache::iterator it = cache_.Get(key);
if (it == cache_.end()) {
ARC_STRACE_REPORT("PepperFileCache: Cache miss for %s", path.c_str());
return false;
}
ARC_STRACE_REPORT("PepperFileCache: Cache hit for %s", path.c_str());
if (file_info)
*file_info = it->second.file_info;
if (exists)
*exists = it->second.exists;
return true;
}
// Returns true when the |path| is definitely non-existent. When it exists or
// when it is unknown, returns false.
bool IsNonExistent(const std::string& path) {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
if (!IsCacheEnabled())
return false;
bool exists = false;
if (!Get(path, NULL, &exists))
return false; // unknown
return !exists;
}
void Set(const std::string& path, const PP_FileInfo& file_info, bool exists) {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
if (!IsCacheEnabled())
return;
ARC_STRACE_REPORT("PepperFileCache: Adding to cache %s, exists: %s",
path.c_str(), exists ? "true" : "false");
const CacheEntry entry = { exists, file_info };
std::string key(path);
RemoveTrailingSlash(&key);
cache_.Put(key, entry);
}
void SetNotExistent(const std::string& path) {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
if (!IsCacheEnabled())
return;
PP_FileInfo dummy = {};
Set(path, dummy, false);
}
void SetNotExistentDirectory(const std::string& path) {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
if (!IsCacheEnabled())
return;
std::string key(path);
if (!util::EndsWithSlash(key))
key.append("/");
PP_FileInfo dummy = {};
const CacheEntry entry = { false, dummy };
for (MRUCache::iterator i = cache_.begin(); i != cache_.end(); ++i) {
if (StartsWithASCII(i->first, key, true))
i->second = entry;
}
}
void Invalidate(const std::string& path) {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
if (!IsCacheEnabled())
return;
ARC_STRACE_REPORT("PepperFileCache: Cache invalidation for %s",
path.c_str());
std::string key(path);
RemoveTrailingSlash(&key);
const MRUCache::iterator it = cache_.Get(key);
if (it != cache_.end())
cache_.Erase(it);
}
void Clear() {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
if (!IsCacheEnabled())
return;
ARC_STRACE_REPORT("PepperFileCache: Invalidate all cache entries");
cache_.Clear();
}
void DisableForTesting() {
VirtualFileSystem::GetVirtualFileSystem()->mutex().AssertAcquired();
Clear();
size_ = 0;
}
private:
bool IsCacheEnabled() const {
return size_ > 0;
}
static void RemoveTrailingSlash(std::string* in_out_path) {
ALOG_ASSERT(in_out_path);
const size_t len = in_out_path->length();
if (len < 2 || !util::EndsWithSlash(*in_out_path))
return;
in_out_path->erase(len - 1);
}
struct CacheEntry {
bool exists;
PP_FileInfo file_info;
};
typedef base::MRUCache<std::string, CacheEntry> MRUCache;
size_t size_;
MRUCache cache_;
DISALLOW_COPY_AND_ASSIGN(PepperFileCache);
};
class FileIOWrapper {
public:
FileIOWrapper(pp::FileIO* file_io, PP_FileHandle native_handle,
const std::string& filename)
: file_io_(file_io), native_handle_(native_handle) {
ALOG_ASSERT(native_handle_ >= 0);
}
~FileIOWrapper() {
if (native_handle_ != PP_kInvalidFileHandle) {
if (real_close(native_handle_)) {
ALOGW("libc_close failed: native_handle_=%d: %s",
native_handle_, safe_strerror(errno).c_str());
}
}
}
pp::FileIO* file_io() { return file_io_.get(); }
PP_FileHandle native_handle() { return native_handle_; }
private:
scoped_ptr<pp::FileIO> file_io_;
PP_FileHandle native_handle_;
DISALLOW_COPY_AND_ASSIGN(FileIOWrapper);
};
PepperFileHandler::PepperFileHandler()
: FileSystemHandler("PepperFileHandler"),
factory_(this),
cache_(new PepperFileCache(kMaxFSCacheEntries)) {
}
PepperFileHandler::PepperFileHandler(const char* name, size_t max_cache_size)
: FileSystemHandler(name),
factory_(this),
cache_(new PepperFileCache(max_cache_size)) {
}
PepperFileHandler::~PepperFileHandler() {
}
void PepperFileHandler::OpenPepperFileSystem(pp::Instance* instance) {
// Since Chrome ignores |kExpectedUsage|, the actual value is not important.
static const uint64_t kExpectedUsage = 16ULL * 1024 * 1024 * 1024;
ALOG_ASSERT(pp::Module::Get()->core()->IsMainThread());
scoped_ptr<pp::FileSystem> file_system(
new pp::FileSystem(instance, PP_FILESYSTEMTYPE_LOCALPERSISTENT));
TRACE_EVENT_ASYNC_BEGIN1(ARC_TRACE_CATEGORY,
"PepperFileHandler::OpenPepperFileSystem",
this, "type", PP_FILESYSTEMTYPE_LOCALPERSISTENT);
const int32_t result = file_system->Open(
kExpectedUsage,
factory_.NewCallback(&PepperFileHandler::OnFileSystemOpen,
file_system.release()));
ALOG_ASSERT(result == PP_OK_COMPLETIONPENDING,
"Failed to create pp::FileSystem, error: %d", result);
}
void PepperFileHandler::DisableCacheForTesting() {
cache_->DisableForTesting();
}
void PepperFileHandler::OnFileSystemOpen(
int32_t result,
pp::FileSystem* file_system_ptr) {
scoped_ptr<pp::FileSystem> file_system(file_system_ptr);
TRACE_EVENT_ASYNC_END1(ARC_TRACE_CATEGORY,
"PepperFileHandler::OpenPepperFileSystem",
this, "result", result);
if (result != PP_OK)
LOG_FATAL("Failed to open pp::FileSystem, error: %d", result);
SetPepperFileSystem(file_system.Pass(), "/", "/");
}
bool PepperFileHandler::IsInitialized() const {
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
sys->mutex().AssertAcquired();
return file_system_.get() && sys->IsBrowserReadyLocked();
}
void PepperFileHandler::Initialize() {
ALOG_ASSERT(!IsInitialized());
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::Initialize");
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
sys->mutex().AssertAcquired();
ALOG_ASSERT(!IsInitialized());
while (!IsInitialized())
sys->Wait();
}
std::string PepperFileHandler::SetPepperFileSystem(
scoped_ptr<pp::FileSystem> pepper_file_system,
const std::string& mount_source_in_pepper_file_system,
const std::string& mount_dest_in_vfs) {
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
base::AutoLock lock(sys->mutex());
ALOG_ASSERT(pepper_file_system);
ALOG_ASSERT(!file_system_);
file_system_ = pepper_file_system.Pass();
ARC_STRACE_REPORT("Mounting %s in pp::FileSystem %p to %s in VFS",
mount_source_in_pepper_file_system.c_str(),
file_system_.get(),
mount_dest_in_vfs.c_str());
sys->Broadcast();
return mount_dest_in_vfs;
}
bool PepperFileHandler::IsWorldWritable(const std::string& pathname) {
// Calling this->stat() every time when VFS::GetFileSystemHandlerLocked() is
// invoked is too expensive for this handler (and this handler's stat() does
// not fill the permission part of st_mode anyway). Just returning false
// would be just fine.
return false;
}
scoped_refptr<FileStream> PepperFileHandler::open(
int unused_fd, const std::string& pathname, int oflag, mode_t cmode) {
// TODO(crbug.com/242355): Use |cmode|.
TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::open",
"pathname", pathname, "oflag", oflag);
// First, check the cache if O_CREAT is not in |oflag|.
if (pathname.empty() ||
(!(oflag & O_CREAT) && cache_->IsNonExistent(pathname))) {
errno = ENOENT;
return NULL;
}
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::open - Pepper");
const int access_mode = (oflag & O_ACCMODE);
// When needed, invalidate the |cache_| before calling "new PepperFile" which
// might unlock the |mutex_|. Note that 'O_RDONLY|O_CREAT' is allowed at least
// on Linux and it may actually create the file. Just in case, do the same for
// 'O_RDONLY|O_TRUNC' which may also truncate the file at least on Linux (even
// though pp::FileIO seems to refuse the latter).
if ((access_mode != O_RDONLY) || (oflag & (O_CREAT | O_TRUNC)))
cache_->Invalidate(pathname);
TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFile::open",
"pathname", pathname);
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
const int open_flags = ConvertNativeOpenFlagsToPepper(oflag);
int32_t result;
scoped_ptr<pp::FileIO_Private> file_io;
PP_FileHandle file_handle;
{
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileRef file_ref(*file_system_, pathname.c_str());
file_io.reset(new pp::FileIO_Private(sys->instance()));
result = file_io->Open(file_ref, open_flags, pp::BlockUntilComplete());
if (result == PP_OK) {
pp::CompletionCallbackWithOutput<pp::PassFileHandle> cb(&file_handle);
result = file_io->RequestOSFileHandle(cb);
if (result != PP_OK) {
ALOGE("PPB_FileIO_Private::RequestOSFileHandle failed! This usually "
"means that your app does not have unlimitedStorage permission.");
}
}
}
ARC_STRACE_REPORT_PP_ERROR(result);
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_open;
#endif
scoped_refptr<FileStream> stream = NULL;
if (result == PP_OK) {
if (oflag & O_DIRECTORY) {
errno = ENOTDIR;
return NULL;
}
stream = new PepperFile(
oflag, cache_.get(), pathname,
new FileIOWrapper(file_io.release(), file_handle, pathname));
} else if (result == PP_ERROR_NOTAFILE) {
// A directory is opened.
if (access_mode != O_RDONLY) {
errno = EISDIR;
return NULL;
}
stream = new DirectoryFileStream("pepper", pathname, this);
} else {
errno = ConvertPepperErrorToErrno(result);
}
return stream;
}
Dir* PepperFileHandler::OnDirectoryContentsNeeded(const std::string& name) {
TRACE_EVENT1(ARC_TRACE_CATEGORY,
"PepperFileHandler::OnDirectoryContentsNeeded", "name", name);
// First, check the cache.
if (name.empty() || cache_->IsNonExistent(name)) {
errno = ENOENT;
return NULL;
}
TRACE_EVENT0(ARC_TRACE_CATEGORY,
"PepperFileHandler::OnDirectoryContentsNeeded - Pepper");
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
int32_t result;
pp::internal::DirectoryEntryArrayOutputAdapterWithStorage adapter;
pp::CompletionCallbackWithOutput<std::vector<pp::DirectoryEntry> > cb(
&adapter);
{
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileRef file_ref(*file_system_, name.c_str());
result = file_ref.ReadDirectoryEntries(cb);
}
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_read_directory_entries;
#endif
ARC_STRACE_REPORT_PP_ERROR(result);
if (result != PP_OK) {
errno = ConvertPepperErrorToErrno(result);
// getdents should not return these values.
if (errno == EEXIST || errno == EISDIR ||
errno == ENOSPC || errno == EPERM) {
ALOG_ASSERT(false, "errno=%d", errno);
errno = ENOENT;
}
return NULL;
}
const std::vector<pp::DirectoryEntry>& directories = adapter.output();
const base::FilePath base_path(name);
DirectoryManager directory_manager;
// We have already confirmed the directory exists. Make sure
// OpenDirectory will succeed for empty directories by adding the
// directory we are checking.
directory_manager.MakeDirectories(name);
for (size_t i = 0; i < directories.size(); ++i) {
const pp::DirectoryEntry& entry = directories[i];
const pp::FileRef& ref = entry.file_ref();
std::string filename = base_path.Append(ref.GetName().AsString()).value();
if (entry.file_type() == PP_FILETYPE_DIRECTORY) {
directory_manager.MakeDirectories(filename);
} else {
bool result = directory_manager.AddFile(filename);
ALOG_ASSERT(result);
}
}
return directory_manager.OpenDirectory(name);
}
int PepperFileHandler::stat(const std::string& pathname, struct stat* out) {
TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFileHandler::stat",
"pathname", pathname);
PP_FileInfo file_info = {};
bool exists = false;
if (!cache_->Get(pathname, &file_info, &exists)) {
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::stat - Pepper");
int32_t result = QueryRefLocked(pathname, &file_info);
ARC_STRACE_REPORT_PP_ERROR(result);
exists = result == PP_OK;
cache_->Set(pathname, file_info, exists);
}
if (!exists) {
errno = ENOENT;
return -1;
}
if (file_info.type == PP_FILETYPE_DIRECTORY) {
DirectoryFileStream::FillStatData(pathname, out);
// Do not fill st_mtime for a directory to be consistent with
// DirectoryFileStream::fstat.
} else {
memset(out, 0, sizeof(struct stat));
// Always assigning 0 (or another constant) to |st_ino| does not always
// work. For example, since SQLite3 manages the current file lock status
// per inode (see unixLock() in sqlite/dist/sqlite3.c), always using
// 0 for |st_ino| may cause deadlock.
out->st_ino =
VirtualFileSystem::GetVirtualFileSystem()->GetInodeLocked(pathname);
out->st_mode = S_IFREG;
out->st_nlink = 1;
out->st_size = file_info.size;
out->st_blksize = kBlockSize;
// We do not support atime and ctime. See PepperFile::fstat().
out->st_mtime = static_cast<time_t>(file_info.last_modified_time);
}
return 0;
}
int PepperFileHandler::statfs(const std::string& pathname, struct statfs* out) {
// TODO(crbug.com/242832): Return real values by apps v2 API.
// http://developer.chrome.com/extensions/experimental.systemInfo.storage.html
struct stat st;
if (this->stat(pathname, &st) == 0)
return DoStatFsForData(out);
errno = ENOENT;
return -1;
}
int PepperFileHandler::mkdir(const std::string& pathname, mode_t mode) {
TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::mkdir",
"pathname", pathname, "mode", mode);
// First, check the cache.
PP_FileInfo file_info = {};
bool exists = false;
if (cache_->Get(pathname, &file_info, &exists) && exists) {
// |pathname| already exists (either file or directory).
errno = EEXIST;
return -1;
}
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::mkdir - Pepper");
cache_->Invalidate(pathname); // call this before unlocking the |mutex_|.
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
int32_t result;
{
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileRef file_ref(*file_system_, pathname.c_str());
result = file_ref.MakeDirectory(PP_MAKEDIRECTORYFLAG_EXCLUSIVE,
pp::BlockUntilComplete());
}
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_make_directory;
#endif
ARC_STRACE_REPORT_PP_ERROR(result);
if (result == PP_OK)
return 0;
errno = ConvertPepperErrorToErrno(result);
// mkdir should not return EISDIR.
if (errno == EISDIR) {
ALOG_ASSERT(false, "errno=%d", errno);
errno = ENOENT;
}
return -1;
}
int PepperFileHandler::remove(const std::string& pathname) {
// Remove an empty directory or a file specified by |pathname|.
TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFileHandler::remove",
"pathname", pathname);
// First, check the cache.
if (cache_->IsNonExistent(pathname)) {
errno = ENOENT;
return -1;
}
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::remove - Pepper");
cache_->Invalidate(pathname); // call this before unlocking the |mutex_|.
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
int32_t result;
{
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileRef file_ref(*file_system_, pathname.c_str());
result = file_ref.Delete(pp::BlockUntilComplete());
}
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_delete;
#endif
ARC_STRACE_REPORT_PP_ERROR(result);
if (result == PP_ERROR_FILENOTFOUND) {
errno = ENOENT;
return -1;
}
if (result != PP_OK) {
// TODO(crbug.com/180985): ARC running on Windows might return PP_ERROR
// to Remove. We might have to add a "delete later" logic here for Windows.
// Use ConvertPepperErrorToErrno once this issue is resolved.
errno = EISDIR;
return -1;
}
sys->RemoveInodeLocked(pathname);
// No need to call SetNotExistentDirectory since remove() can remove only
// empty directory.
cache_->SetNotExistent(pathname);
return 0;
}
int PepperFileHandler::rename(const std::string& oldpath,
const std::string& newpath) {
TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::rename",
"oldpath", oldpath, "newpath", newpath);
// First, check the cache.
if (cache_->IsNonExistent(oldpath)) {
errno = ENOENT;
return -1;
}
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::rename - Pepper");
PP_FileInfo old_file_info = {};
bool old_file_has_metadata = cache_->Get(oldpath, &old_file_info, NULL);
cache_->Invalidate(oldpath); // call this before unlocking the |mutex_|.
cache_->Invalidate(newpath); // call this before unlocking the |mutex_|.
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
int32_t result;
{
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileRef old_file_ref(*file_system_, oldpath.c_str());
pp::FileRef new_file_ref(*file_system_, newpath.c_str());
result = old_file_ref.Rename(
new_file_ref, pp::BlockUntilComplete());
}
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_rename;
#endif
ARC_STRACE_REPORT_PP_ERROR(result);
if (result != PP_OK) {
errno = ConvertPepperErrorToErrno(result);
return -1;
}
if (oldpath != newpath)
cache_->SetNotExistentDirectory(oldpath);
if (old_file_has_metadata)
cache_->Set(newpath, old_file_info, true); // rename preserves metadata.
sys->ReassignInodeLocked(oldpath, newpath);
// rename() should not change inode.
return 0;
}
int PepperFileHandler::rmdir(const std::string& pathname) {
// TODO(crbug.com/190550): Implement this properly. Note that we should
// return ENOTDIR if |pathname| is a file, but right now we do not have a
// good way to perform the check without unlocking the |mutex|. For now,
// just call remove() since some apps require this API and a file name is
// usually not passed to rmdir(). To fix this issue properly, we likely
// have to add an API to pp::FileRef.
ALOGW("PepperFileHandler::rmdir is not fully POSIX compatible and may"
" delete a file: %s", pathname.c_str());
return this->remove(pathname);
}
int PepperFileHandler::truncate(const std::string& pathname,
off64_t length) {
TRACE_EVENT2(ARC_TRACE_CATEGORY, "PepperFileHandler::truncate",
"pathname", pathname, "length", length);
// First, check the cache.
if (cache_->IsNonExistent(pathname)) {
errno = ENOENT;
return -1;
}
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::truncate - Pepper");
scoped_refptr<FileStream> stream = this->open(-1, pathname, O_WRONLY, 0);
if (stream == NULL) {
// truncate should not return these errno values.
if (errno == EEXIST || errno == ENOMEM || errno == ENOSPC) {
ALOG_ASSERT(false, "errno=%d", errno);
errno = ENOENT;
}
return -1;
}
return stream->ftruncate(length);
}
int PepperFileHandler::unlink(const std::string& pathname) {
// TODO(crbug.com/190550): Return EISDIR if |pathname| is a directory. Right
// now, we do not have a good way to perform the check without unlocking the
// |mutex|.
return this->remove(pathname);
}
int PepperFileHandler::utimes(const std::string& pathname,
const struct timeval times[2]) {
TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFileHandler::utimes",
"pathname", pathname);
// First, check the cache.
if (cache_->IsNonExistent(pathname)) {
errno = ENOENT;
return -1;
}
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFileHandler::utimes - Pepper");
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
if (!times) {
errno = EACCES;
return -1;
}
cache_->Invalidate(pathname); // call this before unlocking the |mutex_|.
int32_t result;
{
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileRef file_ref(*file_system_, pathname.c_str());
result = file_ref.Touch(
times[0].tv_sec, times[1].tv_sec, pp::BlockUntilComplete());
}
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_touch;
#endif
ARC_STRACE_REPORT_PP_ERROR(result);
if (result != PP_OK) {
errno = ConvertPepperErrorToErrno(result);
// utimes should not return these errno values.
if (errno == EEXIST || errno == EISDIR ||
errno == ENOMEM || errno == ENOSPC) {
ALOG_ASSERT(false, "errno=%d", errno);
errno = ENOENT;
}
return -1;
}
return 0;
}
void PepperFileHandler::InvalidateCache() {
cache_->Clear();
}
void PepperFileHandler::AddToCache(const std::string& path,
const PP_FileInfo& file_info,
bool exists) {
cache_->Set(path, file_info, exists);
}
void PepperFileHandler::OnMounted(const std::string& path) {
// Check if |path| being mounted exists. If this function is called on the
// main thread, do not check the existence. There are two cases when this
// function is called on the main thread: during handler initialization, the
// library user mounts a static set of paths that are known to be validn.
// The other case is that the external file handler mounts an existing
// external file.
// Note: It is better to move this check to MountPointManager::Add, but doing
// so breaks many unit tests outside this library.
PP_FileInfo info;
ALOG_ASSERT(pp::Module::Get()->core()->IsMainThread() ||
(QueryRefLocked(path, &info) == PP_OK),
"Unknown path '%s' is mounted", path.c_str());
// Update the cache when possible.
if (!util::EndsWithSlash(path)) {
// Ignore OnMounted calls against files since it is difficult to fill the
// cache for files. Note that chown("/path/to/pepper/file", ..) may end up
// taking this path.
return;
}
PP_FileInfo file_info = {};
file_info.size = 4096;
file_info.type = PP_FILETYPE_DIRECTORY;
// For directories, we do not have to fill mtime. See DirectoryFileStream.cc.
cache_->Set(path, file_info, true);
}
void PepperFileHandler::OnUnmounted(const std::string& path) {
cache_->Invalidate(path);
}
int32_t PepperFileHandler::QueryRefLocked(const std::string& pathname,
PP_FileInfo* out_file_info) {
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
sys->mutex().AssertAcquired();
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_query;
#endif
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileRef file_ref(*file_system_, pathname.c_str());
pp::CompletionCallbackWithOutput<PP_FileInfo> cb(out_file_info);
return file_ref.Query(cb);
}
// static
int PepperFileHandler::ConvertPepperErrorToErrno(int pp_error) {
switch (pp_error) {
case PP_ERROR_FILENOTFOUND:
return ENOENT;
case PP_ERROR_FILEEXISTS:
return EEXIST;
case PP_ERROR_NOACCESS:
// This error code is returned when the system tries to write
// something to CRX file system. As the CRX file system is
// read-only, EPERM is more appropriate than EACCES.
return EPERM;
case PP_ERROR_NOMEMORY:
return ENOMEM;
case PP_ERROR_NOQUOTA:
case PP_ERROR_NOSPACE:
return ENOSPC;
case PP_ERROR_NOTAFILE:
return EISDIR;
case PP_ERROR_BADRESOURCE:
return EBADF;
default:
// TODO(crbug.com/293953): Some of PP_ERROR_FAILED should be ENOTDIR.
DANGERF("Unknown Pepper error code: %d", pp_error);
return ENOENT;
}
}
// static
int PepperFileHandler::ConvertNativeOpenFlagsToPepper(int native_flags) {
int pepper_flags = 0;
if ((native_flags & O_ACCMODE) == O_WRONLY) {
pepper_flags = PP_FILEOPENFLAG_WRITE;
} else if ((native_flags & O_ACCMODE) == O_RDONLY) {
pepper_flags = PP_FILEOPENFLAG_READ;
} else if ((native_flags & O_ACCMODE) == O_RDWR) {
pepper_flags = PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE;
} else {
ALOGW("Unknown open flags %o, falling back to O_RDONLY", native_flags);
pepper_flags = PP_FILEOPENFLAG_READ;
}
if (native_flags & O_CREAT)
pepper_flags |= PP_FILEOPENFLAG_CREATE;
if (native_flags & O_EXCL)
pepper_flags |= PP_FILEOPENFLAG_EXCLUSIVE;
if (native_flags & O_TRUNC)
pepper_flags |= PP_FILEOPENFLAG_TRUNCATE;
if (native_flags & O_NOCTTY)
ALOGW("O_NOCTTY is not supported");
if (native_flags & O_NONBLOCK)
ALOGW("O_NONBLOCK is not supported");
if (native_flags & O_SYNC)
ALOGW("O_SYNC is not supported");
if (native_flags & O_ASYNC)
ALOGW("O_ASYNC is not supported");
if (native_flags & O_NOFOLLOW)
ALOGW("O_NOFOLLOW is not supported");
if (native_flags & O_CLOEXEC)
ALOGW("O_CLOEXEC is not supported");
if (native_flags & O_NOATIME)
ALOGW("O_NOATIME is not supported");
if (native_flags & O_APPEND) {
if (pepper_flags & PP_FILEOPENFLAG_TRUNCATE) {
// TODO(crbug.com/308809): Support O_APPEND | O_TRUNC file open.
ALOGW("O_TRUNC with O_APPEND is not supported.");
}
if (pepper_flags & PP_FILEOPENFLAG_WRITE) {
// _WRITE and _APPEND flags are exclusive in Pepper.
pepper_flags |= PP_FILEOPENFLAG_APPEND;
pepper_flags &= ~PP_FILEOPENFLAG_WRITE;
} else {
ALOGW("O_APPEND is specified with O_RDONLY. Ignored.");
}
}
return pepper_flags;
}
//------------------------------------------------------------------------------
PepperFile::PepperFile(int oflag,
PepperFileCache* cache,
const std::string& pathname,
FileIOWrapper* file_wrapper)
: FileStream(oflag, pathname),
factory_(this),
cache_(cache),
file_(file_wrapper) {
ALOG_ASSERT(cache);
ALOG_ASSERT(file_wrapper);
}
PepperFile::~PepperFile() {}
void* PepperFile::mmap(
void* addr, size_t length, int prot, int flags, off_t offset) {
void* result =
::mmap(addr, length, prot, flags, file_->native_handle(), offset);
if (prot & PROT_WRITE)
cache_->Invalidate(pathname());
return result;
}
int PepperFile::munmap(void* addr, size_t length) {
int result = ::munmap(addr, length);
if ((oflag() & O_ACCMODE) != O_RDONLY)
cache_->Invalidate(pathname());
return result;
}
ssize_t PepperFile::read(void* buf, size_t count) {
// Detect non-portable read attempts like mmap(W)-munmap-read and
// mmap(W)-read. For more details, see crbug.com/357780.
if (!IsReadWriteAllowed(pathname(), inode(), "read")) {
errno = EFAULT;
return -1;
}
const ssize_t result = real_read(file_->native_handle(), buf, count);
#if defined(DEBUG_POSIX_TRANSLATION)
if (result > 0)
ipc_stats::g_read_bytes += result;
#endif
return result;
}
// Note for atomicity of the write/pread/pwrite operations below:
//
// PepperFile::write(), PepperFile::pread(), and PepperFile::pwrite() calls
// lseek() to emulate Linux kernel's behavior. The
// "lseek-lseek-read/write-lseek" (for emulating pread and pwrite) sequence
// is safe for the following reasons.
//
// * Only the PPAPI (or NaCl) process for the app and HTML5 FS code in browser
// process access files for the app in the FS.
// * For each app, only one PPAPI (or NaCl) process is started.
// * All POSIX compatible functions in this file are synchronized. For example,
// VirtualFileSystem::write locks the |mutex_| before calling into
// PepperFile::write.
// * All operations that might change the file offset of a file descriptor,
// PepperFile::lseek, PepperFile::read, PepperFile::write, PepperFile::pread,
// and PepperFile::pwrite, are done within this process. They never issues an
// IPC.
// * Other asynchronous operations, such as PepperFileHandler::unlink,
// PepperFileHandler::truncate, and PepperFile::ftruncate could be done in the
// browser process in parallel to the lseek, read, write, pread, and pwrite
// operations above, but the operations in the browser never change the offset
// of a descriptor.
ssize_t PepperFile::write(const void* buf, size_t count) {
// Detect non-portable write attempts like mmap(W)-write and
// mmap(W)-munmap-write. For more details, see crbug.com/357780.
if (!IsReadWriteAllowed(pathname(), inode(), "write")) {
errno = EFAULT;
return -1;
}
cache_->Invalidate(pathname());
const ssize_t result = real_write(file_->native_handle(), buf, count);
#if defined(DEBUG_POSIX_TRANSLATION)
if (result > 0)
ipc_stats::g_write_bytes += result;
#endif
return result;
}
off64_t PepperFile::lseek(off64_t offset, int whence) {
return real_lseek64(file_->native_handle(), offset, whence);
}
int PepperFile::fdatasync() {
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFile::fdatasync");
// TODO(crbug.com/242349): Call NaCl IRT or pp::FileIO::Flush().
ARC_STRACE_REPORT("not implemented yet");
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_fdatasync;
#endif
return 0;
}
int PepperFile::fstat(struct stat* out) {
int result = real_fstat(file_->native_handle(), out);
if (!result) {
// If we expose the values got from host filesystem, the result
// will be inconsistent with stat and lstat. Let VirtualFileSystem set
// permission bits.
out->st_mode &= ~0777;
out->st_ino = inode();
// Overwrite the real dev/rdev numbers with zero. This is necessary for e.g.
// dexopt to work. dvmOpenCachedDexFile() in DexPrepare.cpp checks if st_dev
// numbers returned from ::stat(path) and ::fstat(fd_for_the_path) are the
// same, and retries until they return the same st_dev numbers.
out->st_dev = out->st_rdev = 0;
// We do not support atime and ctime. Note that java.io.File does not
// provide a way to access them.
out->st_atime = out->st_ctime = 0;
// TODO(crbug.com/242337): Fill this value?
out->st_blocks = 0;
out->st_blksize = kBlockSize;
}
return result;
}
int PepperFile::fsync() {
TRACE_EVENT0(ARC_TRACE_CATEGORY, "PepperFile::fsync");
// TODO(crbug.com/242349): Call NaCl IRT or pp::FileIO::Flush().
ARC_STRACE_REPORT("not implemented yet");
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_fsync;
#endif
return 0;
}
int PepperFile::ftruncate(off64_t length) {
TRACE_EVENT1(ARC_TRACE_CATEGORY, "PepperFile::ftruncate", "length", length);
if ((oflag() & O_ACCMODE) == O_RDONLY) {
errno = EBADF;
return -1;
}
cache_->Invalidate(pathname());
VirtualFileSystem* sys = VirtualFileSystem::GetVirtualFileSystem();
int32_t result;
{
// TODO(crbug.com/225152): Fix 225152 and remove |unlock|.
base::AutoUnlock unlock(sys->mutex());
pp::FileIO* file_io = file_->file_io();
result = file_io->SetLength(length, pp::BlockUntilComplete());
}
#if defined(DEBUG_POSIX_TRANSLATION)
++ipc_stats::g_set_length;
#endif
ARC_STRACE_REPORT_PP_ERROR(result);
if (result != PP_OK) {
DANGERF("ftruncate failed with Pepper error code: %d", result);
errno = EACCES;
return -1;
}
return 0;
}
int PepperFile::ioctl(int request, va_list ap) {
if (request == FIONREAD) {
// According to "man ioctl_list", FIONREAD stores its value as an int*.
int* argp = va_arg(ap, int*);
*argp = 0;
off64_t pos = this->lseek(0, SEEK_CUR);
if (pos == -1) {
ALOGE("lseek(cur) returned error %d", errno);
errno = EINVAL;
return -1;
}
struct stat st;
if (this->fstat(&st)) {
ALOGE("fstat() returned error %d", errno);
errno = EINVAL;
return -1;
}
if (pos < st.st_size)
*argp = (st.st_size - pos);
return 0;
}
ALOGE("ioctl command %d not supported\n", request);
errno = EINVAL;
return -1;
}
const char* PepperFile::GetStreamType() const {
return "pepper";
}
size_t PepperFile::GetSize() const {
struct stat st;
if (const_cast<PepperFile*>(this)->fstat(&st))
return 0; // unknown size
return st.st_size;
}
} // namespace posix_translation