| // 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 |