file_io_resource.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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(PP_FileHandle file_handle)
47    : file_handle_(file_handle) {
48}
49
50FileIOResource::QueryOp::~QueryOp() {
51}
52
53int32_t FileIOResource::QueryOp::DoWork() {
54  return base::GetPlatformFileInfo(file_handle_, &file_info_) ?
55      PP_OK : PP_ERROR_FAILED;
56}
57
58FileIOResource::ReadOp::ReadOp(PP_FileHandle file_handle,
59                               int64_t offset,
60                               int32_t bytes_to_read)
61  : file_handle_(file_handle),
62    offset_(offset),
63    bytes_to_read_(bytes_to_read) {
64}
65
66FileIOResource::ReadOp::~ReadOp() {
67}
68
69int32_t FileIOResource::ReadOp::DoWork() {
70  DCHECK(!buffer_.get());
71  buffer_.reset(new char[bytes_to_read_]);
72  return base::ReadPlatformFile(
73      file_handle_, offset_, buffer_.get(), bytes_to_read_);
74}
75
76FileIOResource::FileIOResource(Connection connection, PP_Instance instance)
77    : PluginResource(connection, instance),
78      file_handle_(base::kInvalidPlatformFileValue),
79      file_system_type_(PP_FILESYSTEMTYPE_INVALID) {
80  SendCreate(RENDERER, PpapiHostMsg_FileIO_Create());
81}
82
83FileIOResource::~FileIOResource() {
84  CloseFileHandle();
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 (file_handle_ == base::kInvalidPlatformFileValue)
138    return PP_ERROR_FAILED;
139
140  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
141  scoped_refptr<QueryOp> query_op(new QueryOp(file_handle_));
142
143  // If the callback is blocking, perform the task on the calling thread.
144  if (callback->is_blocking()) {
145    int32_t result;
146    {
147      // Release the proxy lock while making a potentially slow file call.
148      ProxyAutoUnlock unlock;
149      result = query_op->DoWork();
150    }
151    return OnQueryComplete(query_op, info, result);
152  }
153
154  // For the non-blocking case, post a task to the file thread and add a
155  // completion task to write the result.
156  base::PostTaskAndReplyWithResult(
157      PpapiGlobals::Get()->GetFileTaskRunner(pp_instance()),
158      FROM_HERE,
159      Bind(&FileIOResource::QueryOp::DoWork, query_op),
160      RunWhileLocked(Bind(&TrackedCallback::Run, callback)));
161  callback->set_completion_task(
162      Bind(&FileIOResource::OnQueryComplete, this, query_op, info));
163
164  return PP_OK_COMPLETIONPENDING;
165}
166
167int32_t FileIOResource::Touch(PP_Time last_access_time,
168                              PP_Time last_modified_time,
169                              scoped_refptr<TrackedCallback> callback) {
170  int32_t rv = state_manager_.CheckOperationState(
171      FileIOStateManager::OPERATION_EXCLUSIVE, true);
172  if (rv != PP_OK)
173    return rv;
174
175  Call<PpapiPluginMsg_FileIO_GeneralReply>(RENDERER,
176      PpapiHostMsg_FileIO_Touch(last_access_time, last_modified_time),
177      base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
178                 callback));
179
180  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
181  return PP_OK_COMPLETIONPENDING;
182}
183
184int32_t FileIOResource::Read(int64_t offset,
185                             char* buffer,
186                             int32_t bytes_to_read,
187                             scoped_refptr<TrackedCallback> callback) {
188  int32_t rv = state_manager_.CheckOperationState(
189      FileIOStateManager::OPERATION_READ, true);
190  if (rv != PP_OK)
191    return rv;
192
193  PP_ArrayOutput output_adapter;
194  output_adapter.GetDataBuffer = &DummyGetDataBuffer;
195  output_adapter.user_data = buffer;
196  return ReadValidated(offset, bytes_to_read, output_adapter, callback);
197}
198
199int32_t FileIOResource::ReadToArray(int64_t offset,
200                                    int32_t max_read_length,
201                                    PP_ArrayOutput* array_output,
202                                    scoped_refptr<TrackedCallback> callback) {
203  DCHECK(array_output);
204  int32_t rv = state_manager_.CheckOperationState(
205      FileIOStateManager::OPERATION_READ, true);
206  if (rv != PP_OK)
207    return rv;
208
209  return ReadValidated(offset, max_read_length, *array_output, callback);
210}
211
212int32_t FileIOResource::Write(int64_t offset,
213                              const char* buffer,
214                              int32_t bytes_to_write,
215                              scoped_refptr<TrackedCallback> callback) {
216  int32_t rv = state_manager_.CheckOperationState(
217      FileIOStateManager::OPERATION_WRITE, true);
218  if (rv != PP_OK)
219    return rv;
220
221  // TODO(brettw) it would be nice to use a shared memory buffer for large
222  // writes rather than having to copy to a string (which will involve a number
223  // of extra copies to serialize over IPC).
224  Call<PpapiPluginMsg_FileIO_GeneralReply>(RENDERER,
225      PpapiHostMsg_FileIO_Write(offset, std::string(buffer, bytes_to_write)),
226      base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
227                 callback));
228
229  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_WRITE);
230  return PP_OK_COMPLETIONPENDING;
231}
232
233int32_t FileIOResource::SetLength(int64_t length,
234                                  scoped_refptr<TrackedCallback> callback) {
235  int32_t rv = state_manager_.CheckOperationState(
236      FileIOStateManager::OPERATION_EXCLUSIVE, true);
237  if (rv != PP_OK)
238    return rv;
239
240  Call<PpapiPluginMsg_FileIO_GeneralReply>(RENDERER,
241      PpapiHostMsg_FileIO_SetLength(length),
242      base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
243                 callback));
244
245  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
246  return PP_OK_COMPLETIONPENDING;
247}
248
249int32_t FileIOResource::Flush(scoped_refptr<TrackedCallback> callback) {
250  int32_t rv = state_manager_.CheckOperationState(
251      FileIOStateManager::OPERATION_EXCLUSIVE, true);
252  if (rv != PP_OK)
253    return rv;
254
255  Call<PpapiPluginMsg_FileIO_GeneralReply>(RENDERER,
256      PpapiHostMsg_FileIO_Flush(),
257      base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this,
258                 callback));
259
260  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
261  return PP_OK_COMPLETIONPENDING;
262}
263
264void FileIOResource::Close() {
265  CloseFileHandle();
266  Post(RENDERER, PpapiHostMsg_FileIO_Close());
267}
268
269int32_t FileIOResource::GetOSFileDescriptor() {
270  int32_t file_descriptor;
271  // Only available when running in process.
272  SyncCall<PpapiPluginMsg_FileIO_GetOSFileDescriptorReply>(
273      RENDERER, PpapiHostMsg_FileIO_GetOSFileDescriptor(), &file_descriptor);
274  return file_descriptor;
275}
276
277int32_t FileIOResource::RequestOSFileHandle(
278    PP_FileHandle* handle,
279    scoped_refptr<TrackedCallback> callback) {
280  int32_t rv = state_manager_.CheckOperationState(
281      FileIOStateManager::OPERATION_EXCLUSIVE, true);
282  if (rv != PP_OK)
283    return rv;
284
285  Call<PpapiPluginMsg_FileIO_RequestOSFileHandleReply>(RENDERER,
286      PpapiHostMsg_FileIO_RequestOSFileHandle(),
287      base::Bind(&FileIOResource::OnPluginMsgRequestOSFileHandleComplete, this,
288                 callback, handle));
289
290  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
291  return PP_OK_COMPLETIONPENDING;
292}
293
294int32_t FileIOResource::WillWrite(int64_t offset,
295                                  int32_t bytes_to_write,
296                                  scoped_refptr<TrackedCallback> callback) {
297  Call<PpapiPluginMsg_FileIO_GeneralReply>(RENDERER,
298      PpapiHostMsg_FileIO_WillWrite(offset, bytes_to_write),
299      base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this, callback));
300
301  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
302  return PP_OK_COMPLETIONPENDING;
303}
304
305int32_t FileIOResource::WillSetLength(int64_t length,
306                                      scoped_refptr<TrackedCallback> callback) {
307  Call<PpapiPluginMsg_FileIO_GeneralReply>(RENDERER,
308      PpapiHostMsg_FileIO_WillSetLength(length),
309      base::Bind(&FileIOResource::OnPluginMsgGeneralComplete, this, callback));
310
311  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE);
312  return PP_OK_COMPLETIONPENDING;
313}
314
315int32_t FileIOResource::ReadValidated(int64_t offset,
316                                      int32_t bytes_to_read,
317                                      const PP_ArrayOutput& array_output,
318                                      scoped_refptr<TrackedCallback> callback) {
319  if (bytes_to_read < 0)
320    return PP_ERROR_FAILED;
321  if (file_handle_ == base::kInvalidPlatformFileValue)
322    return PP_ERROR_FAILED;
323
324  state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_READ);
325
326  bytes_to_read = std::min(bytes_to_read, kMaxReadSize);
327  scoped_refptr<ReadOp> read_op(
328      new ReadOp(file_handle_, offset, bytes_to_read));
329  if (callback->is_blocking()) {
330    int32_t result;
331    {
332      // Release the proxy lock while making a potentially slow file call.
333      ProxyAutoUnlock unlock;
334      result = read_op->DoWork();
335    }
336    return OnReadComplete(read_op, array_output, result);
337  }
338
339  // For the non-blocking case, post a task to the file thread.
340  base::PostTaskAndReplyWithResult(
341      PpapiGlobals::Get()->GetFileTaskRunner(pp_instance()),
342      FROM_HERE,
343      Bind(&FileIOResource::ReadOp::DoWork, read_op),
344      RunWhileLocked(Bind(&TrackedCallback::Run, callback)));
345  callback->set_completion_task(
346      Bind(&FileIOResource::OnReadComplete, this, read_op, array_output));
347
348  return PP_OK_COMPLETIONPENDING;
349}
350
351void FileIOResource::CloseFileHandle() {
352  if (file_handle_ != base::kInvalidPlatformFileValue) {
353    // Close our local fd on the file thread.
354    base::TaskRunner* file_task_runner =
355        PpapiGlobals::Get()->GetFileTaskRunner(pp_instance());
356    file_task_runner->PostTask(FROM_HERE,
357                               base::Bind(&DoClose, file_handle_));
358
359    file_handle_ = base::kInvalidPlatformFileValue;
360  }
361}
362
363int32_t FileIOResource::OnQueryComplete(scoped_refptr<QueryOp> query_op,
364                                        PP_FileInfo* info,
365                                        int32_t result) {
366  DCHECK(state_manager_.get_pending_operation() ==
367         FileIOStateManager::OPERATION_EXCLUSIVE);
368
369  if (result == PP_OK) {
370    // This writes the file info into the plugin's PP_FileInfo struct.
371    ppapi::PlatformFileInfoToPepperFileInfo(query_op->file_info(),
372                                            file_system_type_,
373                                            info);
374  }
375  state_manager_.SetOperationFinished();
376  return result;
377}
378
379int32_t FileIOResource::OnReadComplete(scoped_refptr<ReadOp> read_op,
380                                       PP_ArrayOutput array_output,
381                                       int32_t result) {
382  DCHECK(state_manager_.get_pending_operation() ==
383         FileIOStateManager::OPERATION_READ);
384  if (result >= 0) {
385    ArrayWriter output;
386    output.set_pp_array_output(array_output);
387    if (output.is_valid())
388      output.StoreArray(read_op->buffer(), result);
389    else
390      result = PP_ERROR_FAILED;
391  } else {
392    // The read operation failed.
393    result = PP_ERROR_FAILED;
394  }
395  state_manager_.SetOperationFinished();
396  return result;
397}
398
399void FileIOResource::OnPluginMsgGeneralComplete(
400    scoped_refptr<TrackedCallback> callback,
401    const ResourceMessageReplyParams& params) {
402  DCHECK(state_manager_.get_pending_operation() ==
403         FileIOStateManager::OPERATION_EXCLUSIVE ||
404         state_manager_.get_pending_operation() ==
405         FileIOStateManager::OPERATION_WRITE);
406  // End this operation now, so the user's callback can execute another FileIO
407  // operation, assuming there are no other pending operations.
408  state_manager_.SetOperationFinished();
409  callback->Run(params.result());
410}
411
412void FileIOResource::OnPluginMsgOpenFileComplete(
413    scoped_refptr<TrackedCallback> callback,
414    const ResourceMessageReplyParams& params) {
415  DCHECK(state_manager_.get_pending_operation() ==
416         FileIOStateManager::OPERATION_EXCLUSIVE);
417
418  // Release the FileRef resource.
419  file_ref_ = NULL;
420  if (params.result() == PP_OK)
421    state_manager_.SetOpenSucceed();
422
423  int32_t result = params.result();
424  IPC::PlatformFileForTransit transit_file;
425  if ((result == PP_OK) && params.TakeFileHandleAtIndex(0, &transit_file))
426    file_handle_ = IPC::PlatformFileForTransitToPlatformFile(transit_file);
427  // End this operation now, so the user's callback can execute another FileIO
428  // operation, assuming there are no other pending operations.
429  state_manager_.SetOperationFinished();
430  callback->Run(result);
431}
432
433void FileIOResource::OnPluginMsgRequestOSFileHandleComplete(
434    scoped_refptr<TrackedCallback> callback,
435    PP_FileHandle* output_handle,
436    const ResourceMessageReplyParams& params) {
437  DCHECK(state_manager_.get_pending_operation() ==
438         FileIOStateManager::OPERATION_EXCLUSIVE);
439
440  if (!TrackedCallback::IsPending(callback)) {
441    state_manager_.SetOperationFinished();
442    return;
443  }
444
445  int32_t result = params.result();
446  IPC::PlatformFileForTransit transit_file;
447  if (!params.TakeFileHandleAtIndex(0, &transit_file))
448    result = PP_ERROR_FAILED;
449  *output_handle = IPC::PlatformFileForTransitToPlatformFile(transit_file);
450
451  // End this operation now, so the user's callback can execute another FileIO
452  // operation, assuming there are no other pending operations.
453  state_manager_.SetOperationFinished();
454  callback->Run(result);
455}
456
457}  // namespace proxy
458}  // namespace ppapi
459