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(). 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. 42 void DoClose(base::PlatformFile file) { 43 base::ClosePlatformFile(file); 44 } 45 46 } // namespace 47 48 namespace ppapi { 49 namespace proxy { 50 51 FileIOResource::QueryOp::QueryOp(scoped_refptr<FileHandleHolder> file_handle) 52 : file_handle_(file_handle) { 53 DCHECK(file_handle_); 54 } 55 56 FileIOResource::QueryOp::~QueryOp() { 57 } 58 59 int32_t FileIOResource::QueryOp::DoWork() { 60 return base::GetPlatformFileInfo(file_handle_->raw_handle(), &file_info_) ? 61 PP_OK : PP_ERROR_FAILED; 62 } 63 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 73 FileIOResource::ReadOp::~ReadOp() { 74 } 75 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 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 90 FileIOResource::~FileIOResource() { 91 Close(); 92 } 93 94 PPB_FileIO_API* FileIOResource::AsPPB_FileIO_API() { 95 return this; 96 } 97 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 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 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 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 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 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 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 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 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 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 319 FileIOResource::FileHandleHolder::FileHandleHolder(PP_FileHandle file_handle) 320 : raw_handle_(file_handle) { 321 } 322 323 // static 324 bool FileIOResource::FileHandleHolder::IsValid( 325 const scoped_refptr<FileIOResource::FileHandleHolder>& handle) { 326 return handle && (handle->raw_handle() != base::kInvalidPlatformFileValue); 327 } 328 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 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 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 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 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 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 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