1 // Copyright (c) 2012 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 "ppapi/proxy/file_io_resource.h"
6
7 #include "base/bind.h"
8 #include "base/task_runner_util.h"
9 #include "ipc/ipc_message.h"
10 #include "ppapi/c/pp_errors.h"
11 #include "ppapi/proxy/ppapi_messages.h"
12 #include "ppapi/shared_impl/array_writer.h"
13 #include "ppapi/shared_impl/file_ref_create_info.h"
14 #include "ppapi/shared_impl/file_system_util.h"
15 #include "ppapi/shared_impl/file_type_conversion.h"
16 #include "ppapi/shared_impl/ppapi_globals.h"
17 #include "ppapi/shared_impl/proxy_lock.h"
18 #include "ppapi/shared_impl/resource_tracker.h"
19 #include "ppapi/thunk/enter.h"
20 #include "ppapi/thunk/ppb_file_ref_api.h"
21 #include "ppapi/thunk/ppb_file_system_api.h"
22
23 using ppapi::thunk::EnterResourceNoLock;
24 using ppapi::thunk::PPB_FileIO_API;
25 using ppapi::thunk::PPB_FileRef_API;
26 using ppapi::thunk::PPB_FileSystem_API;
27
28 namespace {
29
30 // We must allocate a buffer sized according to the request of the plugin. To
31 // reduce the chance of out-of-memory errors, we cap the read and write size to
32 // 32MB. This is OK since the API specifies that it may perform a partial read
33 // or write.
34 static const int32_t kMaxReadWriteSize = 32 * 1024 * 1024; // 32MB
35
36 // An adapter to let Read() share the same implementation with ReadToArray().
DummyGetDataBuffer(void * user_data,uint32_t count,uint32_t size)37 void* DummyGetDataBuffer(void* user_data, uint32_t count, uint32_t size) {
38 return user_data;
39 }
40
41 // File thread task to close the file handle.
DoClose(base::PlatformFile file)42 void DoClose(base::PlatformFile file) {
43 base::ClosePlatformFile(file);
44 }
45
46 } // namespace
47
48 namespace ppapi {
49 namespace proxy {
50
QueryOp(scoped_refptr<FileHandleHolder> file_handle)51 FileIOResource::QueryOp::QueryOp(scoped_refptr<FileHandleHolder> file_handle)
52 : file_handle_(file_handle) {
53 DCHECK(file_handle_);
54 }
55
~QueryOp()56 FileIOResource::QueryOp::~QueryOp() {
57 }
58
DoWork()59 int32_t FileIOResource::QueryOp::DoWork() {
60 return base::GetPlatformFileInfo(file_handle_->raw_handle(), &file_info_) ?
61 PP_OK : PP_ERROR_FAILED;
62 }
63
ReadOp(scoped_refptr<FileHandleHolder> file_handle,int64_t offset,int32_t bytes_to_read)64 FileIOResource::ReadOp::ReadOp(scoped_refptr<FileHandleHolder> file_handle,
65 int64_t offset,
66 int32_t bytes_to_read)
67 : file_handle_(file_handle),
68 offset_(offset),
69 bytes_to_read_(bytes_to_read) {
70 DCHECK(file_handle_);
71 }
72
~ReadOp()73 FileIOResource::ReadOp::~ReadOp() {
74 }
75
DoWork()76 int32_t FileIOResource::ReadOp::DoWork() {
77 DCHECK(!buffer_.get());
78 buffer_.reset(new char[bytes_to_read_]);
79 return base::ReadPlatformFile(
80 file_handle_->raw_handle(), offset_, buffer_.get(), bytes_to_read_);
81 }
82
FileIOResource(Connection connection,PP_Instance instance)83 FileIOResource::FileIOResource(Connection connection, PP_Instance instance)
84 : PluginResource(connection, instance),
85 file_system_type_(PP_FILESYSTEMTYPE_INVALID),
86 called_close_(false) {
87 SendCreate(BROWSER, PpapiHostMsg_FileIO_Create());
88 }
89
~FileIOResource()90 FileIOResource::~FileIOResource() {
91 Close();
92 }
93
AsPPB_FileIO_API()94 PPB_FileIO_API* FileIOResource::AsPPB_FileIO_API() {
95 return this;
96 }
97
Open(PP_Resource file_ref,int32_t open_flags,scoped_refptr<TrackedCallback> callback)98 int32_t FileIOResource::Open(PP_Resource file_ref,
99 int32_t open_flags,
100 scoped_refptr<TrackedCallback> callback) {
101 EnterResourceNoLock<PPB_FileRef_API> enter_file_ref(file_ref, true);
102 if (enter_file_ref.failed())
103 return PP_ERROR_BADRESOURCE;
104
105 PPB_FileRef_API* file_ref_api = enter_file_ref.object();
106 const FileRefCreateInfo& create_info = file_ref_api->GetCreateInfo();
107 if (!FileSystemTypeIsValid(create_info.file_system_type)) {
108 NOTREACHED();
109 return PP_ERROR_FAILED;
110 }
111 int32_t rv = state_manager_.CheckOperationState(
112 FileIOStateManager::OPERATION_EXCLUSIVE, false);
113 if (rv != PP_OK)
114 return rv;
115
116 file_system_type_ = create_info.file_system_type;
117
118 if (create_info.file_system_plugin_resource) {
119 EnterResourceNoLock<PPB_FileSystem_API> enter_file_system(
120 create_info.file_system_plugin_resource, true);
121 if (enter_file_system.failed())
122 return PP_ERROR_FAILED;
123 // Take a reference on the FileSystem resource. The FileIO host uses the
124 // FileSystem host for running tasks and checking quota.
125 file_system_resource_ = enter_file_system.resource();
126 }
127
128 // Take a reference on the FileRef resource while we're opening the file; we
129 // don't want the plugin destroying it during the Open operation.
130 file_ref_ = enter_file_ref.resource();
131
132 Call<PpapiPluginMsg_FileIO_OpenReply>(BROWSER,
133 PpapiHostMsg_FileIO_Open(
134 file_ref,
135 open_flags),
136 base::Bind(&FileIOResource::OnPluginMsgOpenFileComplete, this,
137 callback));
138
139 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
140 return PP_OK_COMPLETIONPENDING;
141 }
142
Query(PP_FileInfo * info,scoped_refptr<TrackedCallback> callback)143 int32_t FileIOResource::Query(PP_FileInfo* info,
144 scoped_refptr<TrackedCallback> callback) {
145 int32_t rv = state_manager_.CheckOperationState(
146 FileIOStateManager::OPERATION_EXCLUSIVE, true);
147 if (rv != PP_OK)
148 return rv;
149 if (!info)
150 return PP_ERROR_BADARGUMENT;
151 if (!FileHandleHolder::IsValid(file_handle_))
152 return PP_ERROR_FAILED;
153
154 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
155
156 // If the callback is blocking, perform the task on the calling thread.
157 if (callback->is_blocking()) {
158 int32_t result = PP_ERROR_FAILED;
159 base::PlatformFileInfo file_info;
160 // The plugin could release its reference to this instance when we release
161 // the proxy lock below.
162 scoped_refptr<FileIOResource> protect(this);
163 {
164 // Release the proxy lock while making a potentially slow file call.
165 ProxyAutoUnlock unlock;
166 if (base::GetPlatformFileInfo(file_handle_->raw_handle(), &file_info))
167 result = PP_OK;
168 }
169 if (result == PP_OK) {
170 // This writes the file info into the plugin's PP_FileInfo struct.
171 ppapi::PlatformFileInfoToPepperFileInfo(file_info,
172 file_system_type_,
173 info);
174 }
175 state_manager_.SetOperationFinished();
176 return result;
177 }
178
179 // For the non-blocking case, post a task to the file thread and add a
180 // completion task to write the result.
181 scoped_refptr<QueryOp> query_op(new QueryOp(file_handle_));
182 base::PostTaskAndReplyWithResult(
183 PpapiGlobals::Get()->GetFileTaskRunner(),
184 FROM_HERE,
185 Bind(&FileIOResource::QueryOp::DoWork, query_op),
186 RunWhileLocked(Bind(&TrackedCallback::Run, callback)));
187 callback->set_completion_task(
188 Bind(&FileIOResource::OnQueryComplete, this, query_op, info));
189
190 return PP_OK_COMPLETIONPENDING;
191 }
192
Touch(PP_Time last_access_time,PP_Time last_modified_time,scoped_refptr<TrackedCallback> callback)193 int32_t FileIOResource::Touch(PP_Time last_access_time,
194 PP_Time last_modified_time,
195 scoped_refptr<TrackedCallback> callback) {
196 int32_t rv = state_manager_.CheckOperationState(
197 FileIOStateManager::OPERATION_EXCLUSIVE, true);
198 if (rv != PP_OK)
199 return rv;
200
201 Call<PpapiPluginMsg_FileIO_GeneralReply>(BROWSER,
202 PpapiHostMsg_FileIO_Touch(last_access_time, last_modified_time),
203 base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
204 callback));
205
206 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
207 return PP_OK_COMPLETIONPENDING;
208 }
209
Read(int64_t offset,char * buffer,int32_t bytes_to_read,scoped_refptr<TrackedCallback> callback)210 int32_t FileIOResource::Read(int64_t offset,
211 char* buffer,
212 int32_t bytes_to_read,
213 scoped_refptr<TrackedCallback> callback) {
214 int32_t rv = state_manager_.CheckOperationState(
215 FileIOStateManager::OPERATION_READ, true);
216 if (rv != PP_OK)
217 return rv;
218
219 PP_ArrayOutput output_adapter;
220 output_adapter.GetDataBuffer = &DummyGetDataBuffer;
221 output_adapter.user_data = buffer;
222 return ReadValidated(offset, bytes_to_read, output_adapter, callback);
223 }
224
ReadToArray(int64_t offset,int32_t max_read_length,PP_ArrayOutput * array_output,scoped_refptr<TrackedCallback> callback)225 int32_t FileIOResource::ReadToArray(int64_t offset,
226 int32_t max_read_length,
227 PP_ArrayOutput* array_output,
228 scoped_refptr<TrackedCallback> callback) {
229 DCHECK(array_output);
230 int32_t rv = state_manager_.CheckOperationState(
231 FileIOStateManager::OPERATION_READ, true);
232 if (rv != PP_OK)
233 return rv;
234
235 return ReadValidated(offset, max_read_length, *array_output, callback);
236 }
237
Write(int64_t offset,const char * buffer,int32_t bytes_to_write,scoped_refptr<TrackedCallback> callback)238 int32_t FileIOResource::Write(int64_t offset,
239 const char* buffer,
240 int32_t bytes_to_write,
241 scoped_refptr<TrackedCallback> callback) {
242 int32_t rv = state_manager_.CheckOperationState(
243 FileIOStateManager::OPERATION_WRITE, true);
244 if (rv != PP_OK)
245 return rv;
246
247 // TODO(brettw) it would be nice to use a shared memory buffer for large
248 // writes rather than having to copy to a string (which will involve a number
249 // of extra copies to serialize over IPC).
250 bytes_to_write = std::min(bytes_to_write, kMaxReadWriteSize);
251 Call<PpapiPluginMsg_FileIO_GeneralReply>(BROWSER,
252 PpapiHostMsg_FileIO_Write(offset, std::string(buffer, bytes_to_write)),
253 base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
254 callback));
255
256 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_WRITE);
257 return PP_OK_COMPLETIONPENDING;
258 }
259
SetLength(int64_t length,scoped_refptr<TrackedCallback> callback)260 int32_t FileIOResource::SetLength(int64_t length,
261 scoped_refptr<TrackedCallback> callback) {
262 int32_t rv = state_manager_.CheckOperationState(
263 FileIOStateManager::OPERATION_EXCLUSIVE, true);
264 if (rv != PP_OK)
265 return rv;
266
267 Call<PpapiPluginMsg_FileIO_GeneralReply>(BROWSER,
268 PpapiHostMsg_FileIO_SetLength(length),
269 base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
270 callback));
271
272 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
273 return PP_OK_COMPLETIONPENDING;
274 }
275
Flush(scoped_refptr<TrackedCallback> callback)276 int32_t FileIOResource::Flush(scoped_refptr<TrackedCallback> callback) {
277 int32_t rv = state_manager_.CheckOperationState(
278 FileIOStateManager::OPERATION_EXCLUSIVE, true);
279 if (rv != PP_OK)
280 return rv;
281
282 Call<PpapiPluginMsg_FileIO_GeneralReply>(BROWSER,
283 PpapiHostMsg_FileIO_Flush(),
284 base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
285 callback));
286
287 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
288 return PP_OK_COMPLETIONPENDING;
289 }
290
Close()291 void FileIOResource::Close() {
292 if (called_close_)
293 return;
294
295 called_close_ = true;
296 if (file_handle_)
297 file_handle_ = NULL;
298
299 Post(BROWSER, PpapiHostMsg_FileIO_Close());
300 }
301
RequestOSFileHandle(PP_FileHandle * handle,scoped_refptr<TrackedCallback> callback)302 int32_t FileIOResource::RequestOSFileHandle(
303 PP_FileHandle* handle,
304 scoped_refptr<TrackedCallback> callback) {
305 int32_t rv = state_manager_.CheckOperationState(
306 FileIOStateManager::OPERATION_EXCLUSIVE, true);
307 if (rv != PP_OK)
308 return rv;
309
310 Call<PpapiPluginMsg_FileIO_RequestOSFileHandleReply>(BROWSER,
311 PpapiHostMsg_FileIO_RequestOSFileHandle(),
312 base::Bind(&FileIOResource::OnPluginMsgRequestOSFileHandleComplete, this,
313 callback, handle));
314
315 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
316 return PP_OK_COMPLETIONPENDING;
317 }
318
FileHandleHolder(PP_FileHandle file_handle)319 FileIOResource::FileHandleHolder::FileHandleHolder(PP_FileHandle file_handle)
320 : raw_handle_(file_handle) {
321 }
322
323 // static
IsValid(const scoped_refptr<FileIOResource::FileHandleHolder> & handle)324 bool FileIOResource::FileHandleHolder::IsValid(
325 const scoped_refptr<FileIOResource::FileHandleHolder>& handle) {
326 return handle && (handle->raw_handle() != base::kInvalidPlatformFileValue);
327 }
328
~FileHandleHolder()329 FileIOResource::FileHandleHolder::~FileHandleHolder() {
330 if (raw_handle_ != base::kInvalidPlatformFileValue) {
331 base::TaskRunner* file_task_runner =
332 PpapiGlobals::Get()->GetFileTaskRunner();
333 file_task_runner->PostTask(FROM_HERE,
334 base::Bind(&DoClose, raw_handle_));
335 }
336 }
337
ReadValidated(int64_t offset,int32_t bytes_to_read,const PP_ArrayOutput & array_output,scoped_refptr<TrackedCallback> callback)338 int32_t FileIOResource::ReadValidated(int64_t offset,
339 int32_t bytes_to_read,
340 const PP_ArrayOutput& array_output,
341 scoped_refptr<TrackedCallback> callback) {
342 if (bytes_to_read < 0)
343 return PP_ERROR_FAILED;
344 if (!FileHandleHolder::IsValid(file_handle_))
345 return PP_ERROR_FAILED;
346
347 state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_READ);
348
349 bytes_to_read = std::min(bytes_to_read, kMaxReadWriteSize);
350 if (callback->is_blocking()) {
351 char* buffer = static_cast<char*>(
352 array_output.GetDataBuffer(array_output.user_data, bytes_to_read, 1));
353 int32_t result = PP_ERROR_FAILED;
354 // The plugin could release its reference to this instance when we release
355 // the proxy lock below.
356 scoped_refptr<FileIOResource> protect(this);
357 if (buffer) {
358 // Release the proxy lock while making a potentially slow file call.
359 ProxyAutoUnlock unlock;
360 result = base::ReadPlatformFile(
361 file_handle_->raw_handle(), offset, buffer, bytes_to_read);
362 if (result < 0)
363 result = PP_ERROR_FAILED;
364 }
365 state_manager_.SetOperationFinished();
366 return result;
367 }
368
369 // For the non-blocking case, post a task to the file thread.
370 scoped_refptr<ReadOp> read_op(
371 new ReadOp(file_handle_, offset, bytes_to_read));
372 base::PostTaskAndReplyWithResult(
373 PpapiGlobals::Get()->GetFileTaskRunner(),
374 FROM_HERE,
375 Bind(&FileIOResource::ReadOp::DoWork, read_op),
376 RunWhileLocked(Bind(&TrackedCallback::Run, callback)));
377 callback->set_completion_task(
378 Bind(&FileIOResource::OnReadComplete, this, read_op, array_output));
379
380 return PP_OK_COMPLETIONPENDING;
381 }
382
OnQueryComplete(scoped_refptr<QueryOp> query_op,PP_FileInfo * info,int32_t result)383 int32_t FileIOResource::OnQueryComplete(scoped_refptr<QueryOp> query_op,
384 PP_FileInfo* info,
385 int32_t result) {
386 DCHECK(state_manager_.get_pending_operation() ==
387 FileIOStateManager::OPERATION_EXCLUSIVE);
388
389 if (result == PP_OK) {
390 // This writes the file info into the plugin's PP_FileInfo struct.
391 ppapi::PlatformFileInfoToPepperFileInfo(query_op->file_info(),
392 file_system_type_,
393 info);
394 }
395 state_manager_.SetOperationFinished();
396 return result;
397 }
398
OnReadComplete(scoped_refptr<ReadOp> read_op,PP_ArrayOutput array_output,int32_t result)399 int32_t FileIOResource::OnReadComplete(scoped_refptr<ReadOp> read_op,
400 PP_ArrayOutput array_output,
401 int32_t result) {
402 DCHECK(state_manager_.get_pending_operation() ==
403 FileIOStateManager::OPERATION_READ);
404 if (result >= 0) {
405 ArrayWriter output;
406 output.set_pp_array_output(array_output);
407 if (output.is_valid())
408 output.StoreArray(read_op->buffer(), result);
409 else
410 result = PP_ERROR_FAILED;
411 } else {
412 // The read operation failed.
413 result = PP_ERROR_FAILED;
414 }
415 state_manager_.SetOperationFinished();
416 return result;
417 }
418
OnPluginMsgGeneralComplete(scoped_refptr<TrackedCallback> callback,const ResourceMessageReplyParams & params)419 void FileIOResource::OnPluginMsgGeneralComplete(
420 scoped_refptr<TrackedCallback> callback,
421 const ResourceMessageReplyParams& params) {
422 DCHECK(state_manager_.get_pending_operation() ==
423 FileIOStateManager::OPERATION_EXCLUSIVE ||
424 state_manager_.get_pending_operation() ==
425 FileIOStateManager::OPERATION_WRITE);
426 // End this operation now, so the user's callback can execute another FileIO
427 // operation, assuming there are no other pending operations.
428 state_manager_.SetOperationFinished();
429 callback->Run(params.result());
430 }
431
OnPluginMsgOpenFileComplete(scoped_refptr<TrackedCallback> callback,const ResourceMessageReplyParams & params)432 void FileIOResource::OnPluginMsgOpenFileComplete(
433 scoped_refptr<TrackedCallback> callback,
434 const ResourceMessageReplyParams& params) {
435 DCHECK(state_manager_.get_pending_operation() ==
436 FileIOStateManager::OPERATION_EXCLUSIVE);
437
438 // Release the FileRef resource.
439 file_ref_ = NULL;
440 if (params.result() == PP_OK)
441 state_manager_.SetOpenSucceed();
442
443 int32_t result = params.result();
444 IPC::PlatformFileForTransit transit_file;
445 if ((result == PP_OK) && params.TakeFileHandleAtIndex(0, &transit_file)) {
446 file_handle_ = new FileHandleHolder(
447 IPC::PlatformFileForTransitToPlatformFile(transit_file));
448 }
449 // End this operation now, so the user's callback can execute another FileIO
450 // operation, assuming there are no other pending operations.
451 state_manager_.SetOperationFinished();
452 callback->Run(result);
453 }
454
OnPluginMsgRequestOSFileHandleComplete(scoped_refptr<TrackedCallback> callback,PP_FileHandle * output_handle,const ResourceMessageReplyParams & params)455 void FileIOResource::OnPluginMsgRequestOSFileHandleComplete(
456 scoped_refptr<TrackedCallback> callback,
457 PP_FileHandle* output_handle,
458 const ResourceMessageReplyParams& params) {
459 DCHECK(state_manager_.get_pending_operation() ==
460 FileIOStateManager::OPERATION_EXCLUSIVE);
461
462 if (!TrackedCallback::IsPending(callback)) {
463 state_manager_.SetOperationFinished();
464 return;
465 }
466
467 int32_t result = params.result();
468 IPC::PlatformFileForTransit transit_file;
469 if (!params.TakeFileHandleAtIndex(0, &transit_file))
470 result = PP_ERROR_FAILED;
471 *output_handle = IPC::PlatformFileForTransitToPlatformFile(transit_file);
472
473 // End this operation now, so the user's callback can execute another FileIO
474 // operation, assuming there are no other pending operations.
475 state_manager_.SetOperationFinished();
476 callback->Run(result);
477 }
478
479 } // namespace proxy
480 } // namespace ppapi
481