OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "nacl_io/googledrivefs/googledrivefs_node.h" | |
6 | |
7 #include <assert.h> | |
8 #include <limits.h> | |
9 #include <stdio.h> | |
10 #include <string.h> | |
11 | |
12 #include <algorithm> | |
13 | |
14 #include "ppapi/c/pp_completion_callback.h" | |
15 | |
16 #include "nacl_io/error.h" | |
17 #include "nacl_io/filesystem.h" | |
18 #include "nacl_io/getdents_helper.h" | |
19 #include "nacl_io/hash.h" | |
20 #include "nacl_io/kernel_handle.h" | |
21 #include "nacl_io/statuscode.h" | |
22 #include "nacl_io/googledrivefs/googledrivefs.h" | |
23 #include "nacl_io/googledrivefs/googledrivefs_util.h" | |
24 | |
25 namespace nacl_io { | |
26 | |
27 GoogleDriveFsNode::GoogleDriveFsNode(Filesystem* filesystem, Path path) | |
28 : Node(filesystem), path_(path) {} | |
29 | |
30 Error GoogleDriveFsNode::GetDents(size_t offs, | |
31 struct dirent* pdir, | |
32 size_t size, | |
33 int* out_bytes) { | |
34 *out_bytes = 0; | |
35 | |
36 if (!IsaDir()) { | |
37 return ENOTDIR; | |
38 } | |
39 | |
40 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.
| |
41 | |
42 std::vector<std::string> dirent_names; | |
43 Error error = RequestDirent("", &dirent_names); | |
44 if (error) { | |
45 return error; | |
46 } | |
47 | |
48 for (size_t i = 0; i < dirent_names.size(); ++i) { | |
49 Path child_path(path_); | |
50 child_path = child_path.Append("/" + dirent_names[i]); | |
51 ino_t child_ino = HashPath(child_path); | |
52 | |
53 helper.AddDirent(child_ino, dirent_names[i].c_str(), | |
54 dirent_names[i].size()); | |
55 } | |
56 | |
57 return helper.GetDents(offs, pdir, size, out_bytes); | |
58 } | |
59 | |
60 Error GoogleDriveFsNode::GetStat(struct stat* pstat) { | |
61 Error error = GetSize(&pstat->st_size); | |
62 if (error) { | |
63 return error; | |
64 } | |
65 | |
66 error = GetModifiedTime(&pstat->st_mtime); | |
67 if (error) { | |
68 return error; | |
69 } | |
70 | |
71 pstat->st_atime = 0; | |
72 pstat->st_ctime = 0; | |
73 | |
74 pstat->st_mode = stat_.st_mode; | |
75 | |
76 return 0; | |
77 } | |
78 | |
79 Error GoogleDriveFsNode::Write(const HandleAttr& attr, | |
80 const void* buf, | |
81 size_t count, | |
82 int* out_bytes) { | |
83 *out_bytes = 0; | |
84 | |
85 if (IsaDir()) { | |
86 return EISDIR; | |
87 } | |
88 if ((GetMode() & S_IWRITE) == 0) { | |
89 return EACCES; | |
90 } | |
91 | |
92 off_t file_size; | |
93 Error error = GetSize(&file_size); | |
94 if (error) { | |
95 return error; | |
96 } | |
97 | |
98 // file_size is <= UINT_MAX. Google Drive API v3 supports only | |
99 // file overwrite, and URLRequestInfoInterface::AppendDataToBody(..) | |
100 // can write up to the max number of uint32_t, so a Google Drive | |
101 // file has a max size of UINT_MAX. | |
102 // Assert attr.offs + count <= UINT_MAX so after | |
103 // GoogleDriveFsNode::Write(..), the Google Drive file size is <= UINT_MAX. | |
104 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
| |
105 | |
106 uint32_t file_buffer_size = std::max<uint32_t>(file_size, attr.offs + count); | |
107 | |
108 // use std::string for storing data in the heap, as the size of stack | |
109 // is measured in megabytes, disallowing files larger than that. | |
110 std::string file_buffer(file_buffer_size, '\0'); | |
111 | |
112 if (file_size > 0) { | |
113 uint32_t read_helper_out_bytes; | |
114 error = | |
115 ReadHelper(0, file_size - 1, &file_buffer[0], &read_helper_out_bytes); | |
116 if (error) { | |
117 return error; | |
118 } | |
119 } | |
120 | |
121 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.
| |
122 | |
123 error = WriteHelper(file_buffer.c_str(), file_buffer_size); | |
124 if (error) { | |
125 return error; | |
126 } | |
127 | |
128 *out_bytes = count; | |
129 | |
130 return 0; | |
131 } | |
132 | |
133 Error GoogleDriveFsNode::FTruncate(off_t length) { | |
134 if (IsaDir()) { | |
135 return EISDIR; | |
136 } | |
137 | |
138 off_t file_size; | |
139 Error error = GetSize(&file_size); | |
140 if (error) { | |
141 return error; | |
142 } | |
143 | |
144 // a Google Drive file size is <= UINT_MAX. Google Drive API v3 | |
145 // supports only file overwrite, and | |
146 // URLRequestInfoInterface::AppendDataToBody(..) | |
147 // can write up to the max number of uint32_t, so a Google Drive | |
148 // file has a max size of UINT_MAX. | |
149 // Assert length <= UINT_MAX so after GoogleDriveFsNode::FTruncate(..), | |
150 // the Google Drive file size is <= UINT_MAX. | |
151 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
| |
152 | |
153 std::string file_buffer(length, '\0'); | |
154 | |
155 if (file_size > 0) { | |
156 uint32_t read_helper_out_bytes; | |
157 uint32_t read_end = std::min<uint32_t>(length, file_size); | |
158 error = | |
159 ReadHelper(0, read_end - 1, &file_buffer[0], &read_helper_out_bytes); | |
160 if (error) { | |
161 return error; | |
162 } | |
163 } | |
164 | |
165 error = WriteHelper(file_buffer.c_str(), length); | |
166 if (error) { | |
167 return error; | |
168 } | |
169 | |
170 return 0; | |
171 } | |
172 | |
173 Error GoogleDriveFsNode::Read(const HandleAttr& attr, | |
174 void* buf, | |
175 size_t count, | |
176 int* out_bytes) { | |
177 *out_bytes = 0; | |
178 | |
179 if (IsaDir()) { | |
180 return EISDIR; | |
181 } | |
182 if ((GetMode() & S_IREAD) == 0) { | |
183 return EACCES; | |
184 } | |
185 | |
186 // GoogleDriveFsNode::ReadHelper(..) can read only up to UINT_MAX bytes. | |
187 if (attr.offs > UINT_MAX) { | |
188 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
| |
189 } | |
190 | |
191 // GoogleDriveFsNode::Read(..) can read only up to INT_MAX bytes. | |
192 int bytes_to_read = std::min<size_t>(INT_MAX, count); | |
193 | |
194 // GoogleDriveFsNode::ReadHelper(..) can read only up to UINT_MAX bytes. | |
195 uint32_t read_end = | |
196 std::min<uint64_t>(attr.offs + bytes_to_read - 1, UINT_MAX); | |
197 | |
198 Error error = | |
199 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.
| |
200 if (error) { | |
201 return error; | |
202 } | |
203 | |
204 return 0; | |
205 } | |
206 | |
207 Error GoogleDriveFsNode::GetSize(off_t* out_size) { | |
208 *out_size = 0; | |
209 | |
210 if (IsaDir()) { | |
211 return 0; | |
212 } | |
213 | |
214 GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); | |
215 | |
216 RequestUrlParams p; | |
217 | |
218 p.url = DRIVE_URL; | |
219 AddUrlPath(item_id_, &p.url); | |
220 AddUrlFirstQueryParameter("fields", "size", &p.url); | |
221 | |
222 p.method = "GET"; | |
223 | |
224 AddHeaders("Content-type", "application/json", &p.headers); | |
225 AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); | |
226 | |
227 ScopedResource url_response_info_resource(googledrivefs->ppapi()); | |
228 Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); | |
229 if (error) { | |
230 return error; | |
231 } | |
232 | |
233 if (ReadStatusCode(googledrivefs->ppapi(), | |
234 url_response_info_resource.pp_resource()) != | |
235 STATUSCODE_OK) { | |
236 return EPERM; | |
237 } | |
238 | |
239 std::string output; | |
240 error = ReadResponseBody(googledrivefs->ppapi(), | |
241 url_response_info_resource.pp_resource(), UINT_MAX, | |
242 &output); | |
243 if (error) { | |
244 return error; | |
245 } | |
246 | |
247 std::string size_value; | |
248 size_t size_index; | |
249 error = | |
250 GetValueStringAndValuePos(output, "size", 0, &size_value, &size_index); | |
251 if (error == EINVAL) { | |
252 size_value = "0"; | |
253 } else if (error) { | |
254 return error; | |
255 } | |
256 | |
257 *out_size = (off_t)atoi(size_value.c_str()); | |
258 | |
259 return 0; | |
260 } | |
261 | |
262 Error GoogleDriveFsNode::Init(int open_flags) { | |
263 Error error = Node::Init(open_flags); | |
264 if (error) { | |
265 return error; | |
266 } | |
267 | |
268 if (!filesystem_->ppapi()) { | |
269 return ENOSYS; | |
270 } | |
271 | |
272 if (path_.IsRoot()) { | |
273 item_id_ = "root"; | |
274 SetType(S_IFDIR); | |
275 SetMode(S_IREAD); | |
276 return 0; | |
277 } | |
278 | |
279 error = RequestParentDirId(path_, filesystem_, &parent_dir_id_); | |
280 if (error) { | |
281 return error; | |
282 } | |
283 | |
284 // Request the ID of an item, which is a file or a directory, | |
285 // and the item type. | |
286 bool is_dir_type; | |
287 error = RequestItemIdAndItemType(parent_dir_id_, path_.Basename(), | |
288 filesystem_, &item_id_, &is_dir_type); | |
289 | |
290 if (error == ENOENT) { | |
291 // Only files are open as mode O_CREAT | |
292 if ((open_flags & O_CREAT) != 0) { | |
293 error = CreateEmptyFile(); | |
294 if (error) { | |
295 return error; | |
296 } | |
297 error = RequestItemIdAndItemType(parent_dir_id_, path_.Basename(), | |
298 filesystem_, &item_id_, &is_dir_type); | |
299 if (error) { | |
300 return error; | |
301 } | |
302 SetType(S_IFREG); | |
303 if ((open_flags & O_RDWR) != 0) { | |
304 SetMode(S_IREAD | S_IWRITE); | |
305 } else if ((open_flags & O_WRONLY) != 0) { | |
306 SetMode(S_IWRITE); | |
307 } else { | |
308 SetMode(S_IREAD); | |
309 } | |
310 } else { | |
311 return ENOENT; | |
312 } | |
313 } else if (error) { | |
314 return error; | |
315 } else { | |
316 if (is_dir_type) { | |
317 SetType(S_IFDIR); | |
318 SetMode(S_IREAD); | |
319 } else { | |
320 SetType(S_IFREG); | |
321 if ((open_flags & O_RDWR) != 0) { | |
322 SetMode(S_IREAD | S_IWRITE); | |
323 } else if ((open_flags & O_WRONLY) != 0) { | |
324 SetMode(S_IWRITE); | |
325 } else { | |
326 SetMode(S_IREAD); | |
327 } | |
328 } | |
329 | |
330 if (open_flags == 0) { | |
331 // open_flags == 0 for file opened on fopen with mode r and | |
332 // directory opened on opendir | |
333 return 0; | |
334 } else if (IsaDir()) { | |
335 return EPERM; | |
336 } else if ((open_flags & O_TRUNC) != 0) { | |
337 error = WriteHelper(NULL, 0); | |
338 if (error) { | |
339 return error; | |
340 } | |
341 } | |
342 } | |
343 | |
344 return 0; | |
345 } | |
346 | |
347 Error GoogleDriveFsNode::ReadHelper(uint32_t start, | |
348 uint32_t end, | |
349 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.
| |
350 uint32_t* out_bytes) { | |
351 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.
| |
352 *out_bytes = 0; | |
353 | |
354 GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); | |
355 | |
356 RequestUrlParams p; | |
357 | |
358 p.url = DOWNLOAD_DRIVE_URL; | |
359 AddUrlPath(item_id_, &p.url); | |
360 AddUrlFirstQueryParameter("alt", "media", &p.url); | |
361 | |
362 p.method = "GET"; | |
363 | |
364 AddHeaders("Content-type", "application/json", &p.headers); | |
365 AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); | |
366 | |
367 char range_value_buffer[1024]; | |
368 int char_written = snprintf(range_value_buffer, sizeof(range_value_buffer), | |
369 "bytes=%u-%u", start, end); | |
370 | |
371 if (char_written < 0 || | |
372 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.
| |
373 return EPERM; | |
374 } | |
375 | |
376 AddHeaders("Range", range_value_buffer, &p.headers); | |
377 | |
378 ScopedResource url_response_info_resource(googledrivefs->ppapi()); | |
379 Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); | |
380 if (error) { | |
381 return error; | |
382 } | |
383 | |
384 int32_t status_code = ReadStatusCode( | |
385 googledrivefs->ppapi(), url_response_info_resource.pp_resource()); | |
386 if (status_code == STATUSCODE_OK || | |
387 status_code == STATUSCODE_PARTIAL_CONTENT) { | |
388 std::string output; | |
389 error = ReadResponseBody(googledrivefs->ppapi(), | |
390 url_response_info_resource.pp_resource(), | |
391 end - start + 1, &output); | |
392 if (error) { | |
393 return error; | |
394 } | |
395 | |
396 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
| |
397 *out_bytes = output.size(); | |
398 | |
399 return 0; | |
400 } else if (status_code == STATUSCODE_REQUESTED_RANGE_NOT_SATISFIABLE) { | |
401 return 0; | |
402 } | |
403 | |
404 return EPERM; | |
405 } | |
406 | |
407 Error GoogleDriveFsNode::WriteHelper(const char* body_data, | |
408 uint32_t body_size) { | |
409 GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); | |
410 | |
411 RequestUrlParams p; | |
412 | |
413 p.url = UPLOAD_DRIVE_URL; | |
414 AddUrlPath(item_id_, &p.url); | |
415 AddUrlFirstQueryParameter("uploadType", "media", &p.url); | |
416 | |
417 p.method = "PATCH"; | |
418 | |
419 AddHeaders("Content-type", "application/json", &p.headers); | |
420 AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); | |
421 | |
422 p.body = std::string(body_data, body_size); | |
423 | |
424 ScopedResource url_response_info_resource(googledrivefs->ppapi()); | |
425 Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); | |
426 if (error) { | |
427 return error; | |
428 } | |
429 | |
430 if (ReadStatusCode(googledrivefs->ppapi(), | |
431 url_response_info_resource.pp_resource()) != | |
432 STATUSCODE_OK) { | |
433 return EPERM; | |
434 } | |
435 | |
436 return 0; | |
437 } | |
438 | |
439 Error GoogleDriveFsNode::GetModifiedTime(time_t* out_mtime) { | |
440 GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); | |
441 | |
442 RequestUrlParams p; | |
443 | |
444 p.url = DRIVE_URL; | |
445 AddUrlPath(item_id_, &p.url); | |
446 AddUrlFirstQueryParameter("fields", "modifiedTime", &p.url); | |
447 | |
448 p.method = "GET"; | |
449 | |
450 AddHeaders("Content-type", "application/json", &p.headers); | |
451 AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); | |
452 | |
453 ScopedResource url_response_info_resource(googledrivefs->ppapi()); | |
454 Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); | |
455 if (error) { | |
456 return error; | |
457 } | |
458 | |
459 if (ReadStatusCode(googledrivefs->ppapi(), | |
460 url_response_info_resource.pp_resource()) != | |
461 STATUSCODE_OK) { | |
462 return EPERM; | |
463 } | |
464 | |
465 std::string output; | |
466 error = ReadResponseBody(googledrivefs->ppapi(), | |
467 url_response_info_resource.pp_resource(), UINT_MAX, | |
468 &output); | |
469 if (error) { | |
470 return error; | |
471 } | |
472 | |
473 std::string modified_time_value; | |
474 size_t modified_time_index; | |
475 error = GetValueStringAndValuePos(output, "modifiedTime", 0, | |
476 &modified_time_value, &modified_time_index); | |
477 if (error) { | |
478 return EPERM; | |
479 } | |
480 | |
481 *out_mtime = | |
482 ConvertDateTimeToEpochTime(ExtractYearFromRFC3339(modified_time_value), | |
483 ExtractMonthFromRFC3339(modified_time_value), | |
484 ExtractDayFromRFC3339(modified_time_value), | |
485 ExtractHourFromRFC3339(modified_time_value), | |
486 ExtractMinuteFromRFC3339(modified_time_value), | |
487 ExtractSecondFromRFC3339(modified_time_value)); | |
488 | |
489 return 0; | |
490 } | |
491 | |
492 Error GoogleDriveFsNode::CreateEmptyFile() { | |
493 GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); | |
494 | |
495 RequestUrlParams p; | |
496 std::string BOUNDARY_VALUE = "foo_bar_baz"; | |
497 | |
498 p.url = UPLOAD_DRIVE_URL; | |
499 AddUrlFirstQueryParameter("uploadType", "multipart", &p.url); | |
500 | |
501 p.method = "POST"; | |
502 | |
503 AddHeaders("Content-type", "multipart/related; boundary=" + BOUNDARY_VALUE, | |
504 &p.headers); | |
505 AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); | |
506 | |
507 AddBody("--" + BOUNDARY_VALUE, &p.body); | |
508 AddBody("Content-Type: application/json; charset=UTF-8", &p.body); | |
509 AddBody("", &p.body); | |
510 AddBody("{", &p.body); | |
511 AddBody(" \"name\": \"" + path_.Basename() + "\",", &p.body); | |
512 AddBody(" \"parents\": [", &p.body); | |
513 AddBody(" \"" + parent_dir_id_ + "\"", &p.body); | |
514 AddBody(" ]", &p.body); | |
515 AddBody("}", &p.body); | |
516 AddBody("", &p.body); | |
517 AddBody("--" + BOUNDARY_VALUE, &p.body); | |
518 AddBody("Content-Type: text/plain", &p.body); | |
519 AddBody("", &p.body); | |
520 AddBody("", &p.body); | |
521 AddBody("--" + BOUNDARY_VALUE + "--", &p.body); | |
522 | |
523 ScopedResource url_response_info_resource(googledrivefs->ppapi()); | |
524 Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); | |
525 if (error) { | |
526 return error; | |
527 } | |
528 | |
529 if (ReadStatusCode(googledrivefs->ppapi(), | |
530 url_response_info_resource.pp_resource()) != | |
531 STATUSCODE_OK) { | |
532 return EPERM; | |
533 } | |
534 | |
535 return 0; | |
536 } | |
537 | |
538 Error GoogleDriveFsNode::RequestDirent( | |
539 const std::string& optional_page_token, | |
540 std::vector<std::string>* out_dirent_names) { | |
541 GoogleDriveFs* googledrivefs = static_cast<GoogleDriveFs*>(filesystem_); | |
542 | |
543 RequestUrlParams p; | |
544 | |
545 p.url = DRIVE_URL; | |
546 AddUrlFirstQueryParameter("q", ParentEqualClause(item_id_), &p.url); | |
547 | |
548 if (!optional_page_token.empty()) { | |
549 AddUrlNextQueryParameter("pageToken", optional_page_token, &p.url); | |
550 } | |
551 | |
552 p.method = "GET"; | |
553 | |
554 AddHeaders("Content-type", "application/json", &p.headers); | |
555 AddHeaders("Authorization", "Bearer " + googledrivefs->token(), &p.headers); | |
556 | |
557 ScopedResource url_response_info_resource(googledrivefs->ppapi()); | |
558 Error error = LoadUrl(googledrivefs->ppapi(), p, &url_response_info_resource); | |
559 if (error) { | |
560 return error; | |
561 } | |
562 | |
563 if (ReadStatusCode(googledrivefs->ppapi(), | |
564 url_response_info_resource.pp_resource()) != | |
565 STATUSCODE_OK) { | |
566 return EPERM; | |
567 } | |
568 | |
569 std::string output; | |
570 error = ReadResponseBody(googledrivefs->ppapi(), | |
571 url_response_info_resource.pp_resource(), UINT_MAX, | |
572 &output); | |
573 if (error) { | |
574 return error; | |
575 } | |
576 | |
577 std::string name_value; | |
578 size_t name_index; | |
579 error = | |
580 GetValueStringAndValuePos(output, "name", 0, &name_value, &name_index); | |
581 if (error && error != EINVAL) { | |
582 return error; | |
583 } | |
584 | |
585 while (!error) { | |
586 out_dirent_names->push_back(name_value); | |
587 | |
588 error = GetValueStringAndValuePos(output, "name", name_index, &name_value, | |
589 &name_index); | |
590 if (error && error != EINVAL) { | |
591 return error; | |
592 } | |
593 } | |
594 | |
595 std::string next_page_token_value; | |
596 size_t next_page_token_index; | |
597 error = | |
598 GetValueStringAndValuePos(output, "nextPageToken", 0, | |
599 &next_page_token_value, &next_page_token_index); | |
600 if (!error) { | |
601 return RequestDirent(next_page_token_value, out_dirent_names); | |
602 } else if (error != EINVAL) { | |
603 return error; | |
604 } | |
605 | |
606 return 0; | |
607 } | |
608 | |
609 } // namespace nacl_io | |
OLD | NEW |