Chromium Code Reviews| 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..c38f2c74e67c33ad9a6bf5d5516fc7be5fb5339e |
| --- /dev/null |
| +++ b/native_client_sdk/src/libraries/nacl_io/googledrivefs/googledrivefs_node.cc |
| @@ -0,0 +1,628 @@ |
| +// 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 <stdint.h> |
| +#include <stdio.h> |
| +#include <stdlib.h> |
| +#include <string.h> |
| + |
| +#include <algorithm> |
| +#include <limits> |
| + |
| +#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(".."))); |
| + |
| + 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; |
| + } |
| + |
| + if (attr.offs > file_size || |
| + attr.offs >= std::numeric_limits<int32_t>::max()) { |
| + return EFBIG; |
| + } |
| + |
| + // a Google Drive file has a max size of max of int32_t by design |
|
binji
2016/09/20 01:22:05
This comment doesn't make much sense to me. Really
chanpatorikku
2016/09/21 16:41:09
Quick fix on the comments.
Less time is going to
|
| + // so the program logic is simpler when off_t in some platform is |
| + // 32 bit and off_t in another platform is 64 bit. |
| + // *out_bytes is in int. Suppose int is >= 32 bits, figure the number of |
| + // necessary bytes to write so after GoogleDriveFsNode::Write(..), |
| + // the Google Drive file size is <= max of int32_t. |
| + int bytes_to_write = |
| + std::min<size_t>(count, std::numeric_limits<int32_t>::max()); |
| + bytes_to_write = std::min<int32_t>( |
| + bytes_to_write, std::numeric_limits<int32_t>::max() - attr.offs); |
| + |
| + int32_t file_buffer_size = |
| + std::max<int32_t>(file_size, attr.offs + bytes_to_write); |
| + |
| + // 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) { |
| + int32_t read_helper_out_bytes; |
|
binji
2016/09/20 01:22:04
this isn't used? Is it not possible for ReadHelper
chanpatorikku
2016/09/21 16:41:09
This isn't used here, but it is possible for ReadH
|
| + error = |
| + ReadHelper(0, file_size - 1, &file_buffer[0], &read_helper_out_bytes); |
|
binji
2016/09/20 01:22:04
This looks a bit strange since the end is inclusiv
binji
2016/09/20 01:22:05
what if the file size changes between the call to
chanpatorikku
2016/09/21 16:41:09
No, http mount does not do the same thing.
The en
chanpatorikku
2016/09/21 16:41:09
GoogleDriveFs is not going to correctly work.
The
|
| + if (error) { |
| + return error; |
| + } |
| + } |
| + |
| + memcpy(&file_buffer[0] + attr.offs, buf, bytes_to_write); |
|
binji
2016/09/20 01:22:04
Can you add an assertion before this memcpy that:
chanpatorikku
2016/09/21 16:41:09
Yes. An assertion, however, does not have to be a
binji
2016/09/26 19:11:12
Yes, but in this case it was not as clear to me th
|
| + |
| + error = WriteHelper(file_buffer.c_str(), file_buffer_size); |
| + if (error) { |
| + return error; |
| + } |
| + |
| + *out_bytes = bytes_to_write; |
| + |
| + return 0; |
| +} |
| + |
| +Error GoogleDriveFsNode::FTruncate(off_t length) { |
| + if (IsaDir()) { |
| + return EISDIR; |
| + } |
| + |
| + // A Google Drive file size is <= max of int32_t by design |
| + // so the program logic is simpler when off_t in some platform is |
| + // 32 bit and off_t in another platform is 64 bit. |
| + // Make sure length <= max of int32_t so after |
| + // GoogleDriveFsNode::FTruncate(..), the Google Drive file size |
| + // is <= max of int32_t. |
| + if (length > std::numeric_limits<int32_t>::max()) { |
| + return EFBIG; |
| + } |
| + |
| + off_t file_size; |
| + Error error = GetSize(&file_size); |
| + if (error) { |
| + return error; |
| + } |
| + |
| + std::string file_buffer(length, '\0'); |
| + |
| + if (file_size > 0) { |
| + int32_t read_helper_out_bytes; |
| + int32_t read_end = std::min<int32_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; |
| + } |
| + |
| + if (count == 0) { |
|
chanpatorikku
2016/09/06 14:49:38
Return 0 when count == 0 so the later code:
Err
|
| + return 0; |
| + } |
| + |
| + // a Google Drive file has a max size of max of int32_t by design |
| + // so the program logic is simpler when off_t in some platform is |
| + // 32 bit and off_t in another platform is 64 bit. |
| + // Google Drive files written from nacl_io can be only up |
| + // to INT32_MAX bytes. |
| + if (attr.offs >= std::numeric_limits<int32_t>::max()) { |
| + return 0; |
| + } |
| + |
| + // Suppose int is >= 32 bits, bytes_to_read is adjusted to |
| + // be in the range of a Google Drive file. |
| + int bytes_to_read = |
| + std::min<size_t>(count, std::numeric_limits<int32_t>::max()); |
| + bytes_to_read = std::min<int32_t>( |
| + bytes_to_read, std::numeric_limits<int32_t>::max() - attr.offs); |
| + |
| + int32_t read_helper_out_bytes; |
| + Error error = ReadHelper(attr.offs, attr.offs + bytes_to_read - 1, buf, |
| + &read_helper_out_bytes); |
| + if (error) { |
| + return error; |
| + } |
| + |
| + *out_bytes = read_helper_out_bytes; |
| + |
| + 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(), |
| + std::numeric_limits<int32_t>::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; |
| + } |
| + |
| + // a Google Drive file has a max size of max of int32_t by design |
| + // so the program logic is simpler when off_t in some platform is |
| + // 32 bit and off_t in another platform is 64 bit. |
| + *out_size = atoi(size_value.c_str()); |
|
binji
2016/09/20 01:22:04
you should validate this string. Try using strtoul
chanpatorikku
2016/09/21 16:41:09
strtoul(..) does not always validate the input cor
binji
2016/09/26 19:11:12
No, strtoul doesn't validate but you can tell wher
|
| + |
| + 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(int32_t start, |
| + int32_t end, |
| + void* out_buffer, |
| + int32_t* out_bytes) { |
| + memset(out_buffer, 0, 1); |
|
binji
2016/09/20 01:22:05
why?
chanpatorikku
2016/09/21 16:41:09
The out variables of many functions in the nacl_io
binji
2016/09/26 19:11:12
OK, but it seems strange to null-terminate out_buf
|
| + *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]; |
| + snprintf(range_value_buffer, sizeof(range_value_buffer), "bytes=%i-%i", start, |
| + end); |
| + |
| + 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; |
| + } |
| + |
| + memcpy(out_buffer, &output[0], output.size()); |
| + *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, int32_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(), |
| + std::numeric_limits<int32_t>::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, |
|
binji
2016/09/20 01:22:04
I think a nicer API would be:
p.AddHeaders(...);
chanpatorikku
2016/09/21 16:41:09
AddHeaders, AddBody, RequestUrlParams, and so on a
binji
2016/09/26 19:11:12
No, this isn't necessary, just mentioned it becaus
|
| + &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(), |
| + std::numeric_limits<int32_t>::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 |