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