1 // Copyright 2013 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 "media/cdm/ppapi/cdm_file_io_impl.h" 6 7 #include <algorithm> 8 #include <sstream> 9 10 #include "media/cdm/ppapi/cdm_logging.h" 11 #include "ppapi/c/pp_errors.h" 12 #include "ppapi/cpp/dev/url_util_dev.h" 13 14 namespace media { 15 16 // Arbitrary choice based on the following heuristic ideas: 17 // - not too big to avoid unnecessarily large memory allocation; 18 // - not too small to avoid breaking most reads into multiple read operations. 19 const int kReadSize = 8 * 1024; 20 21 // Call func_call and check the result. If the result is not 22 // PP_OK_COMPLETIONPENDING, print out logs, call OnError() and return. 23 #define CHECK_PP_OK_COMPLETIONPENDING(func_call, error_type) \ 24 do { \ 25 int32_t result = func_call; \ 26 PP_DCHECK(result != PP_OK); \ 27 if (result != PP_OK_COMPLETIONPENDING) { \ 28 CDM_DLOG() << #func_call << " failed with result: " << result; \ 29 state_ = STATE_ERROR; \ 30 OnError(error_type); \ 31 return; \ 32 } \ 33 } while (0) 34 35 #if !defined(NDEBUG) 36 // PPAPI calls should only be made on the main thread. In this file, main thread 37 // checking is only performed in public APIs and the completion callbacks. This 38 // ensures all functions are running on the main thread since internal methods 39 // are called either by the public APIs or by the completion callbacks. 40 static bool IsMainThread() { 41 return pp::Module::Get()->core()->IsMainThread(); 42 } 43 #endif // !defined(NDEBUG) 44 45 // Posts a task to run |cb| on the main thread. The task is posted even if the 46 // current thread is the main thread. 47 static void PostOnMain(pp::CompletionCallback cb) { 48 pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK); 49 } 50 51 CdmFileIOImpl::FileLockMap* CdmFileIOImpl::file_lock_map_ = NULL; 52 53 CdmFileIOImpl::ResourceTracker::ResourceTracker() { 54 // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_ 55 // in CdmFileIOImpl::AcquireFileLock(). 56 } 57 58 CdmFileIOImpl::ResourceTracker::~ResourceTracker() { 59 delete CdmFileIOImpl::file_lock_map_; 60 } 61 62 CdmFileIOImpl::CdmFileIOImpl( 63 cdm::FileIOClient* client, 64 PP_Instance pp_instance, 65 const pp::CompletionCallback& first_file_read_cb) 66 : state_(STATE_UNOPENED), 67 client_(client), 68 pp_instance_handle_(pp_instance), 69 io_offset_(0), 70 first_file_read_reported_(false), 71 first_file_read_cb_(first_file_read_cb), 72 callback_factory_(this) { 73 PP_DCHECK(IsMainThread()); 74 PP_DCHECK(pp_instance); // 0 indicates a "NULL handle". 75 } 76 77 CdmFileIOImpl::~CdmFileIOImpl() { 78 // The destructor is private. |this| can only be destructed through Close(). 79 PP_DCHECK(state_ == STATE_CLOSED); 80 } 81 82 // Call sequence: Open() -> OpenFileSystem() -> STATE_FILE_SYSTEM_OPENED. 83 // Note: This only stores file name and opens the file system. The real file 84 // open is deferred to when Read() or Write() is called. 85 void CdmFileIOImpl::Open(const char* file_name, uint32_t file_name_size) { 86 CDM_DLOG() << __FUNCTION__; 87 PP_DCHECK(IsMainThread()); 88 89 if (state_ != STATE_UNOPENED) { 90 CDM_DLOG() << "Open() called in an invalid state."; 91 OnError(OPEN_ERROR); 92 return; 93 } 94 95 // File name should not (1) be empty, (2) start with '_', or (3) contain any 96 // path separators. 97 std::string file_name_str(file_name, file_name_size); 98 if (file_name_str.empty() || 99 file_name_str[0] == '_' || 100 file_name_str.find('/') != std::string::npos || 101 file_name_str.find('\\') != std::string::npos) { 102 CDM_DLOG() << "Invalid file name."; 103 state_ = STATE_ERROR; 104 OnError(OPEN_ERROR); 105 return; 106 } 107 108 // pp::FileRef only accepts path that begins with a '/' character. 109 file_name_ = '/' + file_name_str; 110 111 if (!AcquireFileLock()) { 112 CDM_DLOG() << "File is in use by other cdm::FileIO objects."; 113 OnError(OPEN_WHILE_IN_USE); 114 return; 115 } 116 117 state_ = STATE_OPENING_FILE_SYSTEM; 118 OpenFileSystem(); 119 } 120 121 // Call sequence: 122 // Read() -> OpenFileForRead() -> ReadFile() -> Done. 123 void CdmFileIOImpl::Read() { 124 CDM_DLOG() << __FUNCTION__; 125 PP_DCHECK(IsMainThread()); 126 127 if (state_ == STATE_READING || state_ == STATE_WRITING) { 128 CDM_DLOG() << "Read() called during pending read/write."; 129 OnError(READ_WHILE_IN_USE); 130 return; 131 } 132 133 if (state_ != STATE_FILE_SYSTEM_OPENED) { 134 CDM_DLOG() << "Read() called in an invalid state."; 135 OnError(READ_ERROR); 136 return; 137 } 138 139 PP_DCHECK(io_offset_ == 0); 140 PP_DCHECK(io_buffer_.empty()); 141 PP_DCHECK(cumulative_read_buffer_.empty()); 142 io_buffer_.resize(kReadSize); 143 io_offset_ = 0; 144 145 state_ = STATE_READING; 146 OpenFileForRead(); 147 } 148 149 // Call sequence: 150 // Write() -> OpenTempFileForWrite() -> WriteTempFile() -> RenameTempFile(). 151 // The file name of the temporary file is /_<requested_file_name>. 152 void CdmFileIOImpl::Write(const uint8_t* data, uint32_t data_size) { 153 CDM_DLOG() << __FUNCTION__; 154 PP_DCHECK(IsMainThread()); 155 156 if (state_ == STATE_READING || state_ == STATE_WRITING) { 157 CDM_DLOG() << "Write() called during pending read/write."; 158 OnError(WRITE_WHILE_IN_USE); 159 return; 160 } 161 162 if (state_ != STATE_FILE_SYSTEM_OPENED) { 163 CDM_DLOG() << "Write() called in an invalid state."; 164 OnError(WRITE_ERROR); 165 return; 166 } 167 168 PP_DCHECK(io_offset_ == 0); 169 PP_DCHECK(io_buffer_.empty()); 170 if (data_size > 0) 171 io_buffer_.assign(data, data + data_size); 172 else 173 PP_DCHECK(!data); 174 175 state_ = STATE_WRITING; 176 OpenTempFileForWrite(); 177 } 178 179 void CdmFileIOImpl::Close() { 180 CDM_DLOG() << __FUNCTION__; 181 PP_DCHECK(IsMainThread()); 182 PP_DCHECK(state_ != STATE_CLOSED); 183 Reset(); 184 state_ = STATE_CLOSED; 185 ReleaseFileLock(); 186 // All pending callbacks are canceled since |callback_factory_| is destroyed. 187 delete this; 188 } 189 190 bool CdmFileIOImpl::SetFileID() { 191 PP_DCHECK(file_id_.empty()); 192 PP_DCHECK(!file_name_.empty() && file_name_[0] == '/'); 193 194 // Not taking ownership of |url_util_dev| (which is a singleton). 195 const pp::URLUtil_Dev* url_util_dev = pp::URLUtil_Dev::Get(); 196 PP_URLComponents_Dev components; 197 pp::Var url_var = 198 url_util_dev->GetDocumentURL(pp_instance_handle_, &components); 199 if (!url_var.is_string()) 200 return false; 201 std::string url = url_var.AsString(); 202 203 file_id_.append(url, components.scheme.begin, components.scheme.len); 204 file_id_ += ':'; 205 file_id_.append(url, components.host.begin, components.host.len); 206 file_id_ += ':'; 207 file_id_.append(url, components.port.begin, components.port.len); 208 file_id_ += file_name_; 209 210 return true; 211 } 212 213 bool CdmFileIOImpl::AcquireFileLock() { 214 PP_DCHECK(IsMainThread()); 215 216 if (file_id_.empty() && !SetFileID()) 217 return false; 218 219 if (!file_lock_map_) { 220 file_lock_map_ = new FileLockMap(); 221 } else { 222 FileLockMap::iterator found = file_lock_map_->find(file_id_); 223 if (found != file_lock_map_->end() && found->second) 224 return false; 225 } 226 227 (*file_lock_map_)[file_id_] = true; 228 return true; 229 } 230 231 void CdmFileIOImpl::ReleaseFileLock() { 232 PP_DCHECK(IsMainThread()); 233 234 if (!file_lock_map_) 235 return; 236 237 FileLockMap::iterator found = file_lock_map_->find(file_id_); 238 if (found != file_lock_map_->end() && found->second) 239 found->second = false; 240 } 241 242 void CdmFileIOImpl::OpenFileSystem() { 243 PP_DCHECK(state_ == STATE_OPENING_FILE_SYSTEM); 244 245 pp::CompletionCallbackWithOutput<pp::FileSystem> cb = 246 callback_factory_.NewCallbackWithOutput( 247 &CdmFileIOImpl::OnFileSystemOpened); 248 isolated_file_system_ = pp::IsolatedFileSystemPrivate( 249 pp_instance_handle_, PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE); 250 251 CHECK_PP_OK_COMPLETIONPENDING(isolated_file_system_.Open(cb), OPEN_ERROR); 252 } 253 254 void CdmFileIOImpl::OnFileSystemOpened(int32_t result, 255 pp::FileSystem file_system) { 256 PP_DCHECK(IsMainThread()); 257 PP_DCHECK(state_ == STATE_OPENING_FILE_SYSTEM); 258 259 if (result != PP_OK) { 260 CDM_DLOG() << "File system open failed asynchronously."; 261 ReleaseFileLock(); 262 state_ = STATE_ERROR; 263 OnError(OPEN_ERROR); 264 return; 265 } 266 267 file_system_ = file_system; 268 269 state_ = STATE_FILE_SYSTEM_OPENED; 270 client_->OnOpenComplete(cdm::FileIOClient::kSuccess); 271 } 272 273 void CdmFileIOImpl::OpenFileForRead() { 274 PP_DCHECK(state_ == STATE_READING); 275 276 PP_DCHECK(file_io_.is_null()); 277 PP_DCHECK(file_ref_.is_null()); 278 file_io_ = pp::FileIO(pp_instance_handle_); 279 file_ref_ = pp::FileRef(file_system_, file_name_.c_str()); 280 281 // Open file for read. If file doesn't exist, PP_ERROR_FILENOTFOUND will be 282 // returned. 283 int32_t file_open_flag = PP_FILEOPENFLAG_READ; 284 285 pp::CompletionCallback cb = 286 callback_factory_.NewCallback(&CdmFileIOImpl::OnFileOpenedForRead); 287 CHECK_PP_OK_COMPLETIONPENDING(file_io_.Open(file_ref_, file_open_flag, cb), 288 READ_ERROR); 289 } 290 291 void CdmFileIOImpl::OnFileOpenedForRead(int32_t result) { 292 CDM_DLOG() << __FUNCTION__ << ": " << result; 293 PP_DCHECK(IsMainThread()); 294 PP_DCHECK(state_ == STATE_READING); 295 296 if (result != PP_OK && result != PP_ERROR_FILENOTFOUND) { 297 CDM_DLOG() << "File open failed."; 298 state_ = STATE_ERROR; 299 OnError(OPEN_ERROR); 300 return; 301 } 302 303 // File doesn't exist. 304 if (result == PP_ERROR_FILENOTFOUND) { 305 Reset(); 306 state_ = STATE_FILE_SYSTEM_OPENED; 307 client_->OnReadComplete(cdm::FileIOClient::kSuccess, NULL, 0); 308 return; 309 } 310 311 ReadFile(); 312 } 313 314 // Call sequence: 315 // fully read 316 // ReadFile() ---> OnFileRead() ------------> Done. 317 // ^ | 318 // | partially read | 319 // |----------------| 320 void CdmFileIOImpl::ReadFile() { 321 PP_DCHECK(state_ == STATE_READING); 322 PP_DCHECK(!io_buffer_.empty()); 323 324 pp::CompletionCallback cb = 325 callback_factory_.NewCallback(&CdmFileIOImpl::OnFileRead); 326 CHECK_PP_OK_COMPLETIONPENDING( 327 file_io_.Read(io_offset_, &io_buffer_[0], io_buffer_.size(), cb), 328 READ_ERROR); 329 } 330 331 void CdmFileIOImpl::OnFileRead(int32_t bytes_read) { 332 CDM_DLOG() << __FUNCTION__ << ": " << bytes_read; 333 PP_DCHECK(IsMainThread()); 334 PP_DCHECK(state_ == STATE_READING); 335 336 // 0 |bytes_read| indicates end-of-file reached. 337 if (bytes_read < PP_OK) { 338 CDM_DLOG() << "Read file failed."; 339 state_ = STATE_ERROR; 340 OnError(READ_ERROR); 341 return; 342 } 343 344 PP_DCHECK(static_cast<size_t>(bytes_read) <= io_buffer_.size()); 345 // Append |bytes_read| in |io_buffer_| to |cumulative_read_buffer_|. 346 cumulative_read_buffer_.insert(cumulative_read_buffer_.end(), 347 io_buffer_.begin(), 348 io_buffer_.begin() + bytes_read); 349 io_offset_ += bytes_read; 350 351 // Not received end-of-file yet. Keep reading. 352 if (bytes_read > 0) { 353 ReadFile(); 354 return; 355 } 356 357 // We hit end-of-file. Return read data to the client. 358 359 // Clear |cumulative_read_buffer_| in case OnReadComplete() calls Read() or 360 // Write(). 361 std::vector<char> local_buffer; 362 std::swap(cumulative_read_buffer_, local_buffer); 363 364 const uint8_t* data = local_buffer.empty() ? 365 NULL : reinterpret_cast<const uint8_t*>(&local_buffer[0]); 366 367 // Call this before OnReadComplete() so that we always have the latest file 368 // size before CDM fires errors. 369 if (!first_file_read_reported_) { 370 first_file_read_cb_.Run(local_buffer.size()); 371 first_file_read_reported_ = true; 372 } 373 374 Reset(); 375 376 state_ = STATE_FILE_SYSTEM_OPENED; 377 client_->OnReadComplete( 378 cdm::FileIOClient::kSuccess, data, local_buffer.size()); 379 } 380 381 void CdmFileIOImpl::OpenTempFileForWrite() { 382 PP_DCHECK(state_ == STATE_WRITING); 383 384 PP_DCHECK(file_name_.size() > 1 && file_name_[0] == '/'); 385 // Temporary file name format: /_<requested_file_name> 386 std::string temp_file_name = "/_" + file_name_.substr(1); 387 388 PP_DCHECK(file_io_.is_null()); 389 PP_DCHECK(file_ref_.is_null()); 390 file_io_ = pp::FileIO(pp_instance_handle_); 391 file_ref_ = pp::FileRef(file_system_, temp_file_name.c_str()); 392 393 // Create the file if it doesn't exist. Truncate the file to length 0 if it 394 // exists. 395 // TODO(xhwang): Find a good way to report to UMA cases where the temporary 396 // file already exists (due to previous interruption or failure). 397 int32_t file_open_flag = PP_FILEOPENFLAG_WRITE | 398 PP_FILEOPENFLAG_TRUNCATE | 399 PP_FILEOPENFLAG_CREATE; 400 401 pp::CompletionCallback cb = 402 callback_factory_.NewCallback(&CdmFileIOImpl::OnTempFileOpenedForWrite); 403 CHECK_PP_OK_COMPLETIONPENDING( 404 file_io_.Open(file_ref_, file_open_flag, cb), WRITE_ERROR); 405 } 406 407 void CdmFileIOImpl::OnTempFileOpenedForWrite(int32_t result) { 408 CDM_DLOG() << __FUNCTION__ << ": " << result; 409 PP_DCHECK(IsMainThread()); 410 PP_DCHECK(state_ == STATE_WRITING); 411 412 if (result != PP_OK) { 413 CDM_DLOG() << "Open temporary file failed."; 414 state_ = STATE_ERROR; 415 OnError(WRITE_ERROR); 416 return; 417 } 418 419 // We were told to write 0 bytes (to clear the file). In this case, there's 420 // no need to write anything. 421 if (io_buffer_.empty()) { 422 RenameTempFile(); 423 return; 424 } 425 426 PP_DCHECK(io_offset_ == 0); 427 io_offset_ = 0; 428 WriteTempFile(); 429 } 430 431 // Call sequence: 432 // fully written 433 // WriteTempFile() -> OnTempFileWritten() ---------------> RenameTempFile(). 434 // ^ | 435 // | partially written | 436 // |---------------------| 437 void CdmFileIOImpl::WriteTempFile() { 438 PP_DCHECK(state_ == STATE_WRITING); 439 PP_DCHECK(io_offset_ < io_buffer_.size()); 440 441 pp::CompletionCallback cb = 442 callback_factory_.NewCallback(&CdmFileIOImpl::OnTempFileWritten); 443 CHECK_PP_OK_COMPLETIONPENDING(file_io_.Write(io_offset_, 444 &io_buffer_[io_offset_], 445 io_buffer_.size() - io_offset_, 446 cb), 447 WRITE_ERROR); 448 } 449 450 void CdmFileIOImpl::OnTempFileWritten(int32_t bytes_written) { 451 CDM_DLOG() << __FUNCTION__ << ": " << bytes_written; 452 PP_DCHECK(IsMainThread()); 453 PP_DCHECK(state_ == STATE_WRITING); 454 455 if (bytes_written <= PP_OK) { 456 CDM_DLOG() << "Write temporary file failed."; 457 state_ = STATE_ERROR; 458 OnError(WRITE_ERROR); 459 return; 460 } 461 462 io_offset_ += bytes_written; 463 PP_DCHECK(io_offset_ <= io_buffer_.size()); 464 465 if (io_offset_ < io_buffer_.size()) { 466 WriteTempFile(); 467 return; 468 } 469 470 // All data written. Now rename the temporary file to the real file. 471 RenameTempFile(); 472 } 473 474 void CdmFileIOImpl::RenameTempFile() { 475 PP_DCHECK(state_ == STATE_WRITING); 476 477 pp::CompletionCallback cb = 478 callback_factory_.NewCallback(&CdmFileIOImpl::OnTempFileRenamed); 479 CHECK_PP_OK_COMPLETIONPENDING( 480 file_ref_.Rename(pp::FileRef(file_system_, file_name_.c_str()), cb), 481 WRITE_ERROR); 482 } 483 484 void CdmFileIOImpl::OnTempFileRenamed(int32_t result) { 485 CDM_DLOG() << __FUNCTION__ << ": " << result; 486 PP_DCHECK(IsMainThread()); 487 PP_DCHECK(state_ == STATE_WRITING); 488 489 if (result != PP_OK) { 490 CDM_DLOG() << "Rename temporary file failed."; 491 state_ = STATE_ERROR; 492 OnError(WRITE_ERROR); 493 return; 494 } 495 496 Reset(); 497 498 state_ = STATE_FILE_SYSTEM_OPENED; 499 client_->OnWriteComplete(cdm::FileIOClient::kSuccess); 500 } 501 502 void CdmFileIOImpl::Reset() { 503 PP_DCHECK(IsMainThread()); 504 io_buffer_.clear(); 505 io_offset_ = 0; 506 cumulative_read_buffer_.clear(); 507 file_io_.Close(); 508 file_io_ = pp::FileIO(); 509 file_ref_ = pp::FileRef(); 510 } 511 512 void CdmFileIOImpl::OnError(ErrorType error_type) { 513 // For *_WHILE_IN_USE errors, do not reset these values. Otherwise, the 514 // existing read/write operation will fail. 515 if (error_type == READ_ERROR || error_type == WRITE_ERROR) 516 Reset(); 517 518 PostOnMain(callback_factory_.NewCallback(&CdmFileIOImpl::NotifyClientOfError, 519 error_type)); 520 } 521 522 void CdmFileIOImpl::NotifyClientOfError(int32_t result, 523 ErrorType error_type) { 524 PP_DCHECK(result == PP_OK); 525 switch (error_type) { 526 case OPEN_ERROR: 527 client_->OnOpenComplete(cdm::FileIOClient::kError); 528 break; 529 case READ_ERROR: 530 client_->OnReadComplete(cdm::FileIOClient::kError, NULL, 0); 531 break; 532 case WRITE_ERROR: 533 client_->OnWriteComplete(cdm::FileIOClient::kError); 534 break; 535 case OPEN_WHILE_IN_USE: 536 client_->OnOpenComplete(cdm::FileIOClient::kInUse); 537 break; 538 case READ_WHILE_IN_USE: 539 client_->OnReadComplete(cdm::FileIOClient::kInUse, NULL, 0); 540 break; 541 case WRITE_WHILE_IN_USE: 542 client_->OnWriteComplete(cdm::FileIOClient::kInUse); 543 break; 544 } 545 } 546 547 } // namespace media 548