blob: 9c0b2153d7d41c6390987b3b53dc5db0808262fe [file] [log] [blame]
// Copyright 2014 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 "nacl_io/jsfs/js_fs.h"
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include "nacl_io/ioctl.h"
#include "nacl_io/jsfs/js_fs_node.h"
#include "nacl_io/kernel_handle.h"
#include "nacl_io/log.h"
#include "nacl_io/osdirent.h"
#include "nacl_io/pepper_interface.h"
#include "sdk_util/macros.h"
namespace nacl_io {
JsFs::JsFs()
: messaging_iface_(NULL),
array_iface_(NULL),
buffer_iface_(NULL),
dict_iface_(NULL),
var_iface_(NULL),
request_id_(0) {
}
Error JsFs::Init(const FsInitArgs& args) {
Error error = Filesystem::Init(args);
if (error)
return error;
pthread_cond_init(&response_cond_, NULL);
messaging_iface_ = ppapi_->GetMessagingInterface();
array_iface_ = ppapi_->GetVarArrayInterface();
buffer_iface_ = ppapi_->GetVarArrayBufferInterface();
dict_iface_ = ppapi_->GetVarDictionaryInterface();
var_iface_ = ppapi_->GetVarInterface();
if (!messaging_iface_ || !array_iface_ || !buffer_iface_ || !dict_iface_ ||
!var_iface_) {
LOG_ERROR("Got 1+ NULL interface(s): %s%s%s%s%s",
messaging_iface_ ? "" : "Messaging ",
array_iface_ ? "" : "VarArray ",
buffer_iface_ ? "" : "VarArrayBuffer ",
dict_iface_ ? "" : "VarDictionary ",
var_iface_ ? "" : "Var ");
return ENOSYS;
}
return 0;
}
void JsFs::Destroy() {
pthread_cond_destroy(&response_cond_);
}
bool JsFs::SetDictVar(PP_Var dict, const char* key, PP_Var value) {
PP_Var key_var = var_iface_->VarFromUtf8(key, strlen(key));
ScopedVar scoped_key(ppapi_, key_var);
if (key_var.type != PP_VARTYPE_STRING) {
LOG_ERROR("Unable to create string key \"%s\".", key);
return false;
}
PP_Bool success = dict_iface_->Set(dict, key_var, value);
if (!success) {
LOG_ERROR("Unable to set \"%s\" key of dictionary.", key);
return false;
}
return true;
}
PP_Var JsFs::GetDictVar(PP_Var dict, const char* key) {
PP_Var key_var = var_iface_->VarFromUtf8(key, strlen(key));
ScopedVar scoped_key(ppapi_, key_var);
if (key_var.type != PP_VARTYPE_STRING) {
LOG_ERROR("Unable to create string key \"%s\".", key);
return PP_MakeUndefined();
}
return dict_iface_->Get(dict, key_var);
}
bool JsFs::GetVarInt32(PP_Var var, int32_t* out_value) {
switch (var.type) {
case PP_VARTYPE_INT32:
*out_value = var.value.as_int;
return true;
case PP_VARTYPE_DOUBLE:
*out_value = static_cast<int32_t>(var.value.as_double);
return true;
default:
return false;
}
}
bool JsFs::GetVarUint32(PP_Var var, uint32_t* out_value) {
switch (var.type) {
case PP_VARTYPE_INT32:
*out_value = static_cast<uint32_t>(var.value.as_int);
return true;
case PP_VARTYPE_DOUBLE:
*out_value = static_cast<uint32_t>(var.value.as_double);
return true;
default:
return false;
}
}
bool JsFs::GetVarInt64(PP_Var var, int64_t* out_value) {
switch (var.type) {
case PP_VARTYPE_INT32:
*out_value = var.value.as_int;
return true;
case PP_VARTYPE_DOUBLE:
*out_value = static_cast<int64_t>(var.value.as_double);
return true;
case PP_VARTYPE_ARRAY: {
uint32_t len = array_iface_->GetLength(var);
if (len != 2) {
LOG_ERROR("Expected int64 array type to have 2 elements, not %d", len);
return false;
}
PP_Var high_int_var = array_iface_->Get(var, 0);
ScopedVar scoped_high_int_var(ppapi_, high_int_var);
uint32_t high_int;
if (!GetVarUint32(high_int_var, &high_int))
return false;
PP_Var low_int_var = array_iface_->Get(var, 1);
ScopedVar scoped_low_int_var(ppapi_, low_int_var);
uint32_t low_int;
if (!GetVarUint32(low_int_var, &low_int))
return false;
*out_value = static_cast<int64_t>(
(static_cast<uint64_t>(high_int) << 32) | low_int);
return true;
}
default:
return false;
}
}
PP_Var JsFs::VMakeRequest(RequestId request_id,
const char* format,
va_list args) {
PP_Var dict = dict_iface_->Create();
ScopedVar scoped_dict(ppapi_, dict);
if (!SetDictVar(dict, "id", PP_MakeInt32(request_id)))
return PP_MakeNull();
const char* p = format;
while (*p) {
assert(*p == '%');
++p;
const char* key = va_arg(args, const char*);
PP_Var value_var = PP_MakeUndefined();
switch(*p) {
case 'd':
value_var = PP_MakeInt32(va_arg(args, int32_t));
break;
case 'u':
value_var = PP_MakeInt32(va_arg(args, uint32_t));
break;
case 's': {
const char* value = va_arg(args, const char*);
value_var = var_iface_->VarFromUtf8(value, strlen(value));
if (value_var.type != PP_VARTYPE_STRING) {
LOG_ERROR("Unable to create \"%s\" string var.", value);
return PP_MakeNull();
}
break;
}
case 'p':
value_var = *va_arg(args, const PP_Var*);
var_iface_->AddRef(value_var);
break;
case 'l': {
// Only '%lld' is supported.
++p;
assert(*p == 'l');
++p;
assert(*p == 'd');
int64_t value = va_arg(args, int64_t);
if (value >= INT_MIN && value <= INT_MAX) {
// Send as an int.
value_var = PP_MakeInt32(static_cast<int32_t>(value));
} else {
// Send as an array of two ints: [high int32, low int32].
value_var = array_iface_->Create();
if (!array_iface_->SetLength(value_var, 2)) {
LOG_ERROR("Unable to set length of s64 array.");
return PP_MakeNull();
}
if (!array_iface_->Set(value_var, 0, PP_MakeInt32(value >> 32))) {
LOG_ERROR("Unable to set of high int32 of s64 array.");
return PP_MakeNull();
}
if (!array_iface_->Set(
value_var, 1, PP_MakeInt32(value & 0xffffffff))) {
LOG_ERROR("Unable to set of low int32 of s64 array.");
return PP_MakeNull();
}
}
break;
}
default:
LOG_ERROR("Unknown format specifier %%\"%s\"", p);
assert(0);
return PP_MakeNull();
}
++p;
if (!SetDictVar(dict, key, value_var))
return PP_MakeNull();
// Unconditionally release the value var. It is legal to do this even for
// non-refcounted types.
var_iface_->Release(value_var);
}
return scoped_dict.Release();
}
JsFs::RequestId JsFs::VSendRequest(const char* format, va_list args) {
AUTO_LOCK(lock_);
RequestId id = ++request_id_;
// Skip 0 (the invalid request id) in the very unlikely case that the request
// id wraps.
if (id == 0)
id = ++request_id_;
PP_Var dict_var = VMakeRequest(id, format, args);
ScopedVar scoped_dict_var(ppapi_, dict_var);
if (dict_var.type != PP_VARTYPE_DICTIONARY)
return 0;
messaging_iface_->PostMessage(ppapi_->GetInstance(), dict_var);
return id;
}
bool JsFs::VSendRequestAndWait(ScopedVar* out_response,
const char* format,
va_list args) {
RequestId id = VSendRequest(format, args);
if (id == 0)
return false;
out_response->Reset(WaitForResponse(id));
return true;
}
bool JsFs::SendRequestAndWait(ScopedVar* out_response,
const char* format,
...) {
va_list args;
va_start(args, format);
bool result = VSendRequestAndWait(out_response, format, args);
va_end(args);
return result;
}
Error JsFs::ErrorFromResponse(const ScopedVar& response) {
int32_t error;
if (ScanVar(response.pp_var(), "%d", "error", &error) != 1) {
LOG_ERROR("Expected \"error\" field in response.");
return EINVAL;
}
return error;
}
int JsFs::ScanVar(PP_Var var, const char* format, ...) {
va_list args;
va_start(args, format);
int result = VScanVar(var, format, args);
va_end(args);
return result;
}
int JsFs::VScanVar(PP_Var dict_var, const char* format, va_list args) {
if (dict_var.type != PP_VARTYPE_DICTIONARY) {
LOG_ERROR("Expected var of type dictionary, not %d.", dict_var.type);
return 0;
}
int num_values = 0;
const char* p = format;
while (*p) {
assert(*p == '%');
++p;
const char* key = va_arg(args, const char*);
PP_Var value_var = GetDictVar(dict_var, key);
ScopedVar scoped_value_var(ppapi_, value_var);
if (value_var.type == PP_VARTYPE_UNDEFINED)
break;
bool ok = true;
switch (*p) {
case 'd': {
int32_t* value = va_arg(args, int32_t*);
if (!GetVarInt32(value_var, value)) {
LOG_ERROR("Expected int32_t value for key \"%s\"", key);
ok = false;
}
break;
}
case 'u': {
uint32_t* value = va_arg(args, uint32_t*);
if (!GetVarUint32(value_var, value)) {
LOG_ERROR("Expected uint32_t value for key \"%s\"", key);
ok = false;
}
break;
}
case 'l': {
// Only '%lld' is supported.
++p;
assert(*p == 'l');
++p;
assert(*p == 'd');
int64_t* value = va_arg(args, int64_t*);
if (!GetVarInt64(value_var, value)) {
LOG_ERROR("Expected int64_t value for key \"%s\"", key);
ok = false;
}
break;
}
case 'p': {
PP_Var* value = va_arg(args, PP_Var*);
*value = scoped_value_var.Release();
break;
}
default:
LOG_ERROR("Unknown format specifier %%\"%s\"", p);
assert(0);
ok = false;
break;
}
if (!ok)
break;
p++;
num_values++;
}
return num_values;
}
PP_Var JsFs::WaitForResponse(RequestId request_id) {
AUTO_LOCK(lock_);
while (1) {
ResponseMap_t::iterator iter = responses_.find(request_id);
if (iter != responses_.end()) {
PP_Var response = iter->second;
responses_.erase(iter);
return response;
}
pthread_cond_wait(&response_cond_, lock_.mutex());
}
}
Error JsFs::Access(const Path& path, int a_mode) {
ScopedVar response(ppapi_);
if (!SendRequestAndWait(&response, "%s%s%d",
"cmd", "access",
"path", path.Join().c_str(),
"amode", a_mode)) {
LOG_ERROR("Failed to send request.");
return EINVAL;
}
return ErrorFromResponse(response);
}
Error JsFs::Open(const Path& path, int open_flags, ScopedNode* out_node) {
out_node->reset(NULL);
ScopedVar response(ppapi_);
if (!SendRequestAndWait(&response, "%s%s%d",
"cmd", "open",
"path", path.Join().c_str(),
"oflag", open_flags)) {
LOG_ERROR("Failed to send request.");
return EINVAL;
}
int32_t error;
int32_t fd;
int result = ScanVar(response.pp_var(), "%d%d", "error", &error, "fd", &fd);
if (result >= 1 && error)
return error;
if (result != 2) {
LOG_ERROR("Expected \"error\" and \"fd\" fields in response.");
return EINVAL;
}
out_node->reset(new JsFsNode(this, fd));
return 0;
}
Error JsFs::Unlink(const Path& path) {
ScopedVar response(ppapi_);
if (!SendRequestAndWait(
&response, "%s%s", "cmd", "unlink", "path", path.Join().c_str())) {
LOG_ERROR("Failed to send request.");
return EINVAL;
}
return ErrorFromResponse(response);
}
Error JsFs::Mkdir(const Path& path, int perm) {
ScopedVar response(ppapi_);
if (!SendRequestAndWait(&response, "%s%s%d",
"cmd", "mkdir",
"path", path.Join().c_str(),
"mode", perm)) {
LOG_ERROR("Failed to send request.");
return EINVAL;
}
return ErrorFromResponse(response);
}
Error JsFs::Rmdir(const Path& path) {
ScopedVar response(ppapi_);
if (!SendRequestAndWait(
&response, "%s%s", "cmd", "rmdir", "path", path.Join().c_str())) {
LOG_ERROR("Failed to send request.");
return EINVAL;
}
return ErrorFromResponse(response);
}
Error JsFs::Remove(const Path& path) {
ScopedVar response(ppapi_);
if (!SendRequestAndWait(
&response, "%s%s", "cmd", "remove", "path", path.Join().c_str())) {
LOG_ERROR("Failed to send request.");
return EINVAL;
}
return ErrorFromResponse(response);
}
Error JsFs::Rename(const Path& path, const Path& newpath) {
ScopedVar response(ppapi_);
if (!SendRequestAndWait(&response, "%s%s%s",
"cmd", "rename",
"old", path.Join().c_str(),
"new", newpath.Join().c_str())) {
LOG_ERROR("Failed to send request.");
return EINVAL;
}
return ErrorFromResponse(response);
}
Error JsFs::Filesystem_VIoctl(int request, va_list args) {
if (request != NACL_IOC_HANDLEMESSAGE) {
LOG_ERROR("Unknown ioctl: %#x", request);
return EINVAL;
}
PP_Var response = *va_arg(args, PP_Var*);
AUTO_LOCK(lock_);
RequestId response_id;
if (ScanVar(response, "%d", "id", &response_id) != 1) {
LOG_TRACE("ioctl with no \"id\", ignoring.\n");
return EINVAL;
}
responses_.insert(ResponseMap_t::value_type(response_id, response));
pthread_cond_broadcast(&response_cond_);
return 0;
}
} // namespace nacl_io