Index: native_client_sdk/src/libraries/nacl_io/googledrivefs/googledrivefs_node.cc |
diff --git a/native_client_sdk/src/libraries/nacl_io/googledrivefs/googledrivefs_node.cc b/native_client_sdk/src/libraries/nacl_io/googledrivefs/googledrivefs_node.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..af16c81e08de51f4ded1b040546f10ab503f9e08 |
--- /dev/null |
+++ b/native_client_sdk/src/libraries/nacl_io/googledrivefs/googledrivefs_node.cc |
@@ -0,0 +1,609 @@ |
+// Copyright 2016 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/googledrivefs/googledrivefs_node.h" |
+ |
+#include <assert.h> |
+#include <limits.h> |
+#include <stdio.h> |
+#include <string.h> |
+ |
+#include <algorithm> |
+ |
+#include "ppapi/c/pp_completion_callback.h" |
+ |
+#include "nacl_io/error.h" |
+#include "nacl_io/filesystem.h" |
+#include "nacl_io/getdents_helper.h" |
+#include "nacl_io/hash.h" |
+#include "nacl_io/kernel_handle.h" |
+#include "nacl_io/statuscode.h" |
+#include "nacl_io/googledrivefs/googledrivefs.h" |
+#include "nacl_io/googledrivefs/googledrivefs_util.h" |
+ |
+namespace nacl_io { |
+ |
+GoogleDriveFsNode::GoogleDriveFsNode(Filesystem* filesystem, Path path) |
+ : Node(filesystem), path_(path) {} |
+ |
+Error GoogleDriveFsNode::GetDents(size_t offs, |
+ struct dirent* pdir, |
+ size_t size, |
+ int* out_bytes) { |
+ *out_bytes = 0; |
+ |
+ if (!IsaDir()) { |
+ return ENOTDIR; |
+ } |
+ |
+ GetDentsHelper helper(HashPath(Path(".")), HashPath(Path(".."))); |
chanpatorikku
2016/08/29 17:14:04
The code handles the TODO of html5_fs_node.cc:
binji
2016/08/30 01:39:35
This is fine.
chanpatorikku
2016/09/06 14:49:38
Acknowledged.
|
+ |
+ std::vector<std::string> dirent_names; |
+ Error error = RequestDirent("", &dirent_names); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ for (size_t i = 0; i < dirent_names.size(); ++i) { |
+ Path child_path(path_); |
+ child_path = child_path.Append("/" + dirent_names[i]); |
+ ino_t child_ino = HashPath(child_path); |
+ |
+ helper.AddDirent(child_ino, dirent_names[i].c_str(), |
+ dirent_names[i].size()); |
+ } |
+ |
+ return helper.GetDents(offs, pdir, size, out_bytes); |
+} |
+ |
+Error GoogleDriveFsNode::GetStat(struct stat* pstat) { |
+ Error error = GetSize(&pstat->st_size); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ error = GetModifiedTime(&pstat->st_mtime); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ pstat->st_atime = 0; |
+ pstat->st_ctime = 0; |
+ |
+ pstat->st_mode = stat_.st_mode; |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::Write(const HandleAttr& attr, |
+ const void* buf, |
+ size_t count, |
+ int* out_bytes) { |
+ *out_bytes = 0; |
+ |
+ if (IsaDir()) { |
+ return EISDIR; |
+ } |
+ if ((GetMode() & S_IWRITE) == 0) { |
+ return EACCES; |
+ } |
+ |
+ off_t file_size; |
+ Error error = GetSize(&file_size); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ // file_size is <= UINT_MAX. Google Drive API v3 supports only |
+ // file overwrite, and URLRequestInfoInterface::AppendDataToBody(..) |
+ // can write up to the max number of uint32_t, so a Google Drive |
+ // file has a max size of UINT_MAX. |
+ // Assert attr.offs + count <= UINT_MAX so after |
+ // GoogleDriveFsNode::Write(..), the Google Drive file size is <= UINT_MAX. |
+ assert(attr.offs + count <= UINT_MAX); |
binji
2016/08/30 01:39:35
I don't think an assertion is correct here; the us
chanpatorikku
2016/09/06 14:49:38
The boundary values of the variables have been rec
|
+ |
+ uint32_t file_buffer_size = std::max<uint32_t>(file_size, attr.offs + count); |
+ |
+ // use std::string for storing data in the heap, as the size of stack |
+ // is measured in megabytes, disallowing files larger than that. |
+ std::string file_buffer(file_buffer_size, '\0'); |
+ |
+ if (file_size > 0) { |
+ uint32_t read_helper_out_bytes; |
+ error = |
+ ReadHelper(0, file_size - 1, &file_buffer[0], &read_helper_out_bytes); |
+ if (error) { |
+ return error; |
+ } |
+ } |
+ |
+ strncpy(&file_buffer[0] + attr.offs, (char*)buf, count); |
binji
2016/08/30 01:39:34
don't use C-style casts
chanpatorikku
2016/09/06 14:49:38
Done.
|
+ |
+ error = WriteHelper(file_buffer.c_str(), file_buffer_size); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ *out_bytes = count; |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::FTruncate(off_t length) { |
+ if (IsaDir()) { |
+ return EISDIR; |
+ } |
+ |
+ off_t file_size; |
+ Error error = GetSize(&file_size); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ // a Google Drive file size is <= UINT_MAX. Google Drive API v3 |
+ // supports only file overwrite, and |
+ // URLRequestInfoInterface::AppendDataToBody(..) |
+ // can write up to the max number of uint32_t, so a Google Drive |
+ // file has a max size of UINT_MAX. |
+ // Assert length <= UINT_MAX so after GoogleDriveFsNode::FTruncate(..), |
+ // the Google Drive file size is <= UINT_MAX. |
+ assert(length <= UINT_MAX); |
binji
2016/08/30 01:39:34
I don't think an assertion is correct here; the us
chanpatorikku
2016/09/06 14:49:38
The reply of this comment's going to be the same a
|
+ |
+ std::string file_buffer(length, '\0'); |
+ |
+ if (file_size > 0) { |
+ uint32_t read_helper_out_bytes; |
+ uint32_t read_end = std::min<uint32_t>(length, file_size); |
+ error = |
+ ReadHelper(0, read_end - 1, &file_buffer[0], &read_helper_out_bytes); |
+ if (error) { |
+ return error; |
+ } |
+ } |
+ |
+ error = WriteHelper(file_buffer.c_str(), length); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::Read(const HandleAttr& attr, |
+ void* buf, |
+ size_t count, |
+ int* out_bytes) { |
+ *out_bytes = 0; |
+ |
+ if (IsaDir()) { |
+ return EISDIR; |
+ } |
+ if ((GetMode() & S_IREAD) == 0) { |
+ return EACCES; |
+ } |
+ |
+ // GoogleDriveFsNode::ReadHelper(..) can read only up to UINT_MAX bytes. |
+ if (attr.offs > UINT_MAX) { |
+ return 0; |
binji
2016/08/30 01:39:35
shouldn't this return an error?
chanpatorikku
2016/09/06 14:49:38
I answered a similar question in patch set 2.
The
|
+ } |
+ |
+ // GoogleDriveFsNode::Read(..) can read only up to INT_MAX bytes. |
+ int bytes_to_read = std::min<size_t>(INT_MAX, count); |
+ |
+ // GoogleDriveFsNode::ReadHelper(..) can read only up to UINT_MAX bytes. |
+ uint32_t read_end = |
+ std::min<uint64_t>(attr.offs + bytes_to_read - 1, UINT_MAX); |
+ |
+ Error error = |
+ ReadHelper(attr.offs, read_end, (char*)buf, (uint32_t*)out_bytes); |
binji
2016/08/30 01:39:34
don't use C-style casts
binji
2016/08/30 01:39:35
don't cast int* to uint32_t*, instead use a local
chanpatorikku
2016/09/06 14:49:38
Done.
chanpatorikku
2016/09/06 14:49:38
Done.
|
+ if (error) { |
+ return error; |
+ } |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::GetSize(off_t* out_size) { |
+ *out_size = 0; |
+ |
+ if (IsaDir()) { |
+ return 0; |
+ } |
+ |
+ GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); |
+ |
+ RequestUrlParams p; |
+ |
+ p.url = DRIVE_URL; |
+ AddUrlPath(item_id_, &p.url); |
+ AddUrlFirstQueryParameter("fields", "size", &p.url); |
+ |
+ p.method = "GET"; |
+ |
+ AddHeaders("Content-type", "application/json", &p.headers); |
+ AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); |
+ |
+ ScopedResource url_response_info_resource(googledrivefs->ppapi()); |
+ Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ if (ReadStatusCode(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource()) != |
+ STATUSCODE_OK) { |
+ return EPERM; |
+ } |
+ |
+ std::string output; |
+ error = ReadResponseBody(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource(), UINT_MAX, |
+ &output); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ std::string size_value; |
+ size_t size_index; |
+ error = |
+ GetValueStringAndValuePos(output, "size", 0, &size_value, &size_index); |
+ if (error == EINVAL) { |
+ size_value = "0"; |
+ } else if (error) { |
+ return error; |
+ } |
+ |
+ *out_size = (off_t)atoi(size_value.c_str()); |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::Init(int open_flags) { |
+ Error error = Node::Init(open_flags); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ if (!filesystem_->ppapi()) { |
+ return ENOSYS; |
+ } |
+ |
+ if (path_.IsRoot()) { |
+ item_id_ = "root"; |
+ SetType(S_IFDIR); |
+ SetMode(S_IREAD); |
+ return 0; |
+ } |
+ |
+ error = RequestParentDirId(path_, filesystem_, &parent_dir_id_); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ // Request the ID of an item, which is a file or a directory, |
+ // and the item type. |
+ bool is_dir_type; |
+ error = RequestItemIdAndItemType(parent_dir_id_, path_.Basename(), |
+ filesystem_, &item_id_, &is_dir_type); |
+ |
+ if (error == ENOENT) { |
+ // Only files are open as mode O_CREAT |
+ if ((open_flags & O_CREAT) != 0) { |
+ error = CreateEmptyFile(); |
+ if (error) { |
+ return error; |
+ } |
+ error = RequestItemIdAndItemType(parent_dir_id_, path_.Basename(), |
+ filesystem_, &item_id_, &is_dir_type); |
+ if (error) { |
+ return error; |
+ } |
+ SetType(S_IFREG); |
+ if ((open_flags & O_RDWR) != 0) { |
+ SetMode(S_IREAD | S_IWRITE); |
+ } else if ((open_flags & O_WRONLY) != 0) { |
+ SetMode(S_IWRITE); |
+ } else { |
+ SetMode(S_IREAD); |
+ } |
+ } else { |
+ return ENOENT; |
+ } |
+ } else if (error) { |
+ return error; |
+ } else { |
+ if (is_dir_type) { |
+ SetType(S_IFDIR); |
+ SetMode(S_IREAD); |
+ } else { |
+ SetType(S_IFREG); |
+ if ((open_flags & O_RDWR) != 0) { |
+ SetMode(S_IREAD | S_IWRITE); |
+ } else if ((open_flags & O_WRONLY) != 0) { |
+ SetMode(S_IWRITE); |
+ } else { |
+ SetMode(S_IREAD); |
+ } |
+ } |
+ |
+ if (open_flags == 0) { |
+ // open_flags == 0 for file opened on fopen with mode r and |
+ // directory opened on opendir |
+ return 0; |
+ } else if (IsaDir()) { |
+ return EPERM; |
+ } else if ((open_flags & O_TRUNC) != 0) { |
+ error = WriteHelper(NULL, 0); |
+ if (error) { |
+ return error; |
+ } |
+ } |
+ } |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::ReadHelper(uint32_t start, |
+ uint32_t end, |
+ char* out_buffer, |
binji
2016/08/30 01:39:34
if you make the other changes below, you can just
chanpatorikku
2016/09/06 14:49:38
Done.
|
+ uint32_t* out_bytes) { |
+ out_buffer[0] = '\0'; |
binji
2016/08/30 01:39:35
This buffer is not a string, just raw data.
chanpatorikku
2016/09/06 14:49:38
Done.
|
+ *out_bytes = 0; |
+ |
+ GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); |
+ |
+ RequestUrlParams p; |
+ |
+ p.url = DOWNLOAD_DRIVE_URL; |
+ AddUrlPath(item_id_, &p.url); |
+ AddUrlFirstQueryParameter("alt", "media", &p.url); |
+ |
+ p.method = "GET"; |
+ |
+ AddHeaders("Content-type", "application/json", &p.headers); |
+ AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); |
+ |
+ char range_value_buffer[1024]; |
+ int char_written = snprintf(range_value_buffer, sizeof(range_value_buffer), |
+ "bytes=%u-%u", start, end); |
+ |
+ if (char_written < 0 || |
+ char_written >= (signed int)sizeof(range_value_buffer)) { |
binji
2016/08/30 01:39:34
no need to check this, it's impossible for the len
chanpatorikku
2016/09/06 14:49:38
Done.
|
+ return EPERM; |
+ } |
+ |
+ AddHeaders("Range", range_value_buffer, &p.headers); |
+ |
+ ScopedResource url_response_info_resource(googledrivefs->ppapi()); |
+ Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ int32_t status_code = ReadStatusCode( |
+ googledrivefs->ppapi(), url_response_info_resource.pp_resource()); |
+ if (status_code == STATUSCODE_OK || |
+ status_code == STATUSCODE_PARTIAL_CONTENT) { |
+ std::string output; |
+ error = ReadResponseBody(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource(), |
+ end - start + 1, &output); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ strncpy(out_buffer, &output[0], output.size()); |
binji
2016/08/30 01:39:34
use memcpy, this data could include \0
chanpatorikku
2016/09/06 14:49:38
Done. Thank you so much for the catch.
Other fi
|
+ *out_bytes = output.size(); |
+ |
+ return 0; |
+ } else if (status_code == STATUSCODE_REQUESTED_RANGE_NOT_SATISFIABLE) { |
+ return 0; |
+ } |
+ |
+ return EPERM; |
+} |
+ |
+Error GoogleDriveFsNode::WriteHelper(const char* body_data, |
+ uint32_t body_size) { |
+ GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); |
+ |
+ RequestUrlParams p; |
+ |
+ p.url = UPLOAD_DRIVE_URL; |
+ AddUrlPath(item_id_, &p.url); |
+ AddUrlFirstQueryParameter("uploadType", "media", &p.url); |
+ |
+ p.method = "PATCH"; |
+ |
+ AddHeaders("Content-type", "application/json", &p.headers); |
+ AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); |
+ |
+ p.body = std::string(body_data, body_size); |
+ |
+ ScopedResource url_response_info_resource(googledrivefs->ppapi()); |
+ Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ if (ReadStatusCode(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource()) != |
+ STATUSCODE_OK) { |
+ return EPERM; |
+ } |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::GetModifiedTime(time_t* out_mtime) { |
+ GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); |
+ |
+ RequestUrlParams p; |
+ |
+ p.url = DRIVE_URL; |
+ AddUrlPath(item_id_, &p.url); |
+ AddUrlFirstQueryParameter("fields", "modifiedTime", &p.url); |
+ |
+ p.method = "GET"; |
+ |
+ AddHeaders("Content-type", "application/json", &p.headers); |
+ AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); |
+ |
+ ScopedResource url_response_info_resource(googledrivefs->ppapi()); |
+ Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ if (ReadStatusCode(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource()) != |
+ STATUSCODE_OK) { |
+ return EPERM; |
+ } |
+ |
+ std::string output; |
+ error = ReadResponseBody(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource(), UINT_MAX, |
+ &output); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ std::string modified_time_value; |
+ size_t modified_time_index; |
+ error = GetValueStringAndValuePos(output, "modifiedTime", 0, |
+ &modified_time_value, &modified_time_index); |
+ if (error) { |
+ return EPERM; |
+ } |
+ |
+ *out_mtime = |
+ ConvertDateTimeToEpochTime(ExtractYearFromRFC3339(modified_time_value), |
+ ExtractMonthFromRFC3339(modified_time_value), |
+ ExtractDayFromRFC3339(modified_time_value), |
+ ExtractHourFromRFC3339(modified_time_value), |
+ ExtractMinuteFromRFC3339(modified_time_value), |
+ ExtractSecondFromRFC3339(modified_time_value)); |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::CreateEmptyFile() { |
+ GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); |
+ |
+ RequestUrlParams p; |
+ std::string BOUNDARY_VALUE = "foo_bar_baz"; |
+ |
+ p.url = UPLOAD_DRIVE_URL; |
+ AddUrlFirstQueryParameter("uploadType", "multipart", &p.url); |
+ |
+ p.method = "POST"; |
+ |
+ AddHeaders("Content-type", "multipart/related; boundary=" + BOUNDARY_VALUE, |
+ &p.headers); |
+ AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); |
+ |
+ AddBody("--" + BOUNDARY_VALUE, &p.body); |
+ AddBody("Content-Type: application/json; charset=UTF-8", &p.body); |
+ AddBody("", &p.body); |
+ AddBody("{", &p.body); |
+ AddBody(" \"name\": \"" + path_.Basename() + "\",", &p.body); |
+ AddBody(" \"parents\": [", &p.body); |
+ AddBody(" \"" + parent_dir_id_ + "\"", &p.body); |
+ AddBody(" ]", &p.body); |
+ AddBody("}", &p.body); |
+ AddBody("", &p.body); |
+ AddBody("--" + BOUNDARY_VALUE, &p.body); |
+ AddBody("Content-Type: text/plain", &p.body); |
+ AddBody("", &p.body); |
+ AddBody("", &p.body); |
+ AddBody("--" + BOUNDARY_VALUE + "--", &p.body); |
+ |
+ ScopedResource url_response_info_resource(googledrivefs->ppapi()); |
+ Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ if (ReadStatusCode(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource()) != |
+ STATUSCODE_OK) { |
+ return EPERM; |
+ } |
+ |
+ return 0; |
+} |
+ |
+Error GoogleDriveFsNode::RequestDirent( |
+ const std::string& optional_page_token, |
+ std::vector<std::string>* out_dirent_names) { |
+ GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); |
+ |
+ RequestUrlParams p; |
+ |
+ p.url = DRIVE_URL; |
+ AddUrlFirstQueryParameter("q", ParentEqualClause(item_id_), &p.url); |
+ |
+ if (!optional_page_token.empty()) { |
+ AddUrlNextQueryParameter("pageToken", optional_page_token, &p.url); |
+ } |
+ |
+ p.method = "GET"; |
+ |
+ AddHeaders("Content-type", "application/json", &p.headers); |
+ AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); |
+ |
+ ScopedResource url_response_info_resource(googledrivefs->ppapi()); |
+ Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ if (ReadStatusCode(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource()) != |
+ STATUSCODE_OK) { |
+ return EPERM; |
+ } |
+ |
+ std::string output; |
+ error = ReadResponseBody(googledrivefs->ppapi(), |
+ url_response_info_resource.pp_resource(), UINT_MAX, |
+ &output); |
+ if (error) { |
+ return error; |
+ } |
+ |
+ std::string name_value; |
+ size_t name_index; |
+ error = |
+ GetValueStringAndValuePos(output, "name", 0, &name_value, &name_index); |
+ if (error && error != EINVAL) { |
+ return error; |
+ } |
+ |
+ while (!error) { |
+ out_dirent_names->push_back(name_value); |
+ |
+ error = GetValueStringAndValuePos(output, "name", name_index, &name_value, |
+ &name_index); |
+ if (error && error != EINVAL) { |
+ return error; |
+ } |
+ } |
+ |
+ std::string next_page_token_value; |
+ size_t next_page_token_index; |
+ error = |
+ GetValueStringAndValuePos(output, "nextPageToken", 0, |
+ &next_page_token_value, &next_page_token_index); |
+ if (!error) { |
+ return RequestDirent(next_page_token_value, out_dirent_names); |
+ } else if (error != EINVAL) { |
+ return error; |
+ } |
+ |
+ return 0; |
+} |
+ |
+} // namespace nacl_io |