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 14namespace media { 15 16const int kReadSize = 4 * 1024; // Arbitrary choice. 17 18// Call func_call and check the result. If the result is not 19// PP_OK_COMPLETIONPENDING, print out logs, call OnError() and return. 20#define CHECK_PP_OK_COMPLETIONPENDING(func_call, error_type) \ 21 do { \ 22 int32_t result = func_call; \ 23 PP_DCHECK(result != PP_OK); \ 24 if (result != PP_OK_COMPLETIONPENDING) { \ 25 CDM_DLOG() << #func_call << " failed with result: " << result; \ 26 OnError(error_type); \ 27 return; \ 28 } \ 29 } while (0) 30 31#if !defined(NDEBUG) 32// PPAPI calls should only be made on the main thread. In this file, main thread 33// checking is only performed in public APIs and the completion callbacks. This 34// ensures all functions are running on the main thread since internal methods 35// are called either by the public APIs or by the completion callbacks. 36static bool IsMainThread() { 37 return pp::Module::Get()->core()->IsMainThread(); 38} 39#endif // !defined(NDEBUG) 40 41// Posts a task to run |cb| on the main thread. The task is posted even if the 42// current thread is the main thread. 43static void PostOnMain(pp::CompletionCallback cb) { 44 pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK); 45} 46 47CdmFileIOImpl::FileLockMap* CdmFileIOImpl::file_lock_map_ = NULL; 48 49CdmFileIOImpl::ResourceTracker::ResourceTracker() { 50 // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_ 51 // in CdmFileIOImpl::AcquireFileLock(). 52} 53 54CdmFileIOImpl::ResourceTracker::~ResourceTracker() { 55 delete CdmFileIOImpl::file_lock_map_; 56} 57 58CdmFileIOImpl::CdmFileIOImpl(cdm::FileIOClient* client, PP_Instance pp_instance) 59 : state_(FILE_UNOPENED), 60 client_(client), 61 pp_instance_handle_(pp_instance), 62 callback_factory_(this), 63 io_offset_(0) { 64 PP_DCHECK(IsMainThread()); 65 PP_DCHECK(pp_instance); // 0 indicates a "NULL handle". 66} 67 68CdmFileIOImpl::~CdmFileIOImpl() { 69 PP_DCHECK(state_ == FILE_CLOSED); 70} 71 72// Call sequence: Open() -> OpenFileSystem() -> OpenFile() -> FILE_OPENED. 73void CdmFileIOImpl::Open(const char* file_name, uint32_t file_name_size) { 74 CDM_DLOG() << __FUNCTION__; 75 PP_DCHECK(IsMainThread()); 76 77 if (state_ != FILE_UNOPENED) { 78 CDM_DLOG() << "Open() called in an invalid state."; 79 OnError(OPEN_ERROR); 80 return; 81 } 82 83 // File name should not contain any path separators. 84 std::string file_name_str(file_name, file_name_size); 85 if (file_name_str.find('/') != std::string::npos || 86 file_name_str.find('\\') != std::string::npos) { 87 CDM_DLOG() << "Invalid file name."; 88 OnError(OPEN_ERROR); 89 return; 90 } 91 92 // pp::FileRef only accepts path that begins with a '/' character. 93 file_name_ = '/' + file_name_str; 94 95 if (!AcquireFileLock()) { 96 CDM_DLOG() << "File is in use by other cdm::FileIO objects."; 97 OnError(OPEN_WHILE_IN_USE); 98 return; 99 } 100 101 state_ = OPENING_FILE_SYSTEM; 102 OpenFileSystem(); 103} 104 105// Call sequence: 106// finished 107// Read() -> ReadFile() -> OnFileRead() ----------> Done. 108// ^ | 109// | not finished | 110// |--------------| 111void CdmFileIOImpl::Read() { 112 CDM_DLOG() << __FUNCTION__; 113 PP_DCHECK(IsMainThread()); 114 115 if (state_ == READING_FILE || state_ == WRITING_FILE) { 116 CDM_DLOG() << "Read() called during pending read/write."; 117 OnError(READ_WHILE_IN_USE); 118 return; 119 } 120 121 if (state_ != FILE_OPENED) { 122 CDM_DLOG() << "Read() called in an invalid state."; 123 OnError(READ_ERROR); 124 return; 125 } 126 127 PP_DCHECK(io_buffer_.empty()); 128 PP_DCHECK(cumulative_read_buffer_.empty()); 129 130 io_buffer_.resize(kReadSize); 131 io_offset_ = 0; 132 133 state_ = READING_FILE; 134 ReadFile(); 135} 136 137// Call sequence: 138// finished 139// Write() -> WriteFile() -> OnFileWritten() ----------> Done. 140// ^ | 141// | | not finished 142// |------------------| 143void CdmFileIOImpl::Write(const uint8_t* data, uint32_t data_size) { 144 CDM_DLOG() << __FUNCTION__; 145 PP_DCHECK(IsMainThread()); 146 147 if (state_ == READING_FILE || state_ == WRITING_FILE) { 148 CDM_DLOG() << "Write() called during pending read/write."; 149 OnError(WRITE_WHILE_IN_USE); 150 return; 151 } 152 153 if (state_ != FILE_OPENED) { 154 CDM_DLOG() << "Write() called in an invalid state."; 155 OnError(WRITE_ERROR); 156 return; 157 } 158 159 PP_DCHECK(io_offset_ == 0); 160 PP_DCHECK(io_buffer_.empty()); 161 if (data_size > 0) 162 io_buffer_.assign(data, data + data_size); 163 else 164 PP_DCHECK(!data); 165 166 state_ = WRITING_FILE; 167 168 // Always SetLength() in case |data_size| is less than the file size. 169 SetLength(data_size); 170} 171 172void CdmFileIOImpl::Close() { 173 CDM_DLOG() << __FUNCTION__; 174 PP_DCHECK(IsMainThread()); 175 PP_DCHECK(state_ != FILE_CLOSED); 176 CloseFile(); 177 ReleaseFileLock(); 178 // All pending callbacks are canceled since |callback_factory_| is destroyed. 179 delete this; 180} 181 182bool CdmFileIOImpl::SetFileID() { 183 PP_DCHECK(file_id_.empty()); 184 PP_DCHECK(!file_name_.empty() && file_name_[0] == '/'); 185 186 // Not taking ownership of |url_util_dev| (which is a singleton). 187 const pp::URLUtil_Dev* url_util_dev = pp::URLUtil_Dev::Get(); 188 PP_URLComponents_Dev components; 189 pp::Var url_var = 190 url_util_dev->GetDocumentURL(pp_instance_handle_, &components); 191 if (!url_var.is_string()) 192 return false; 193 std::string url = url_var.AsString(); 194 195 file_id_.append(url, components.scheme.begin, components.scheme.len); 196 file_id_ += ':'; 197 file_id_.append(url, components.host.begin, components.host.len); 198 file_id_ += ':'; 199 file_id_.append(url, components.port.begin, components.port.len); 200 file_id_ += file_name_; 201 202 return true; 203} 204 205bool CdmFileIOImpl::AcquireFileLock() { 206 PP_DCHECK(IsMainThread()); 207 208 if (file_id_.empty() && !SetFileID()) 209 return false; 210 211 if (!file_lock_map_) { 212 file_lock_map_ = new FileLockMap(); 213 } else { 214 FileLockMap::iterator found = file_lock_map_->find(file_id_); 215 if (found != file_lock_map_->end() && found->second) 216 return false; 217 } 218 219 (*file_lock_map_)[file_id_] = true; 220 return true; 221} 222 223void CdmFileIOImpl::ReleaseFileLock() { 224 PP_DCHECK(IsMainThread()); 225 226 if (!file_lock_map_) 227 return; 228 229 FileLockMap::iterator found = file_lock_map_->find(file_id_); 230 if (found != file_lock_map_->end() && found->second) 231 found->second = false; 232} 233 234void CdmFileIOImpl::OpenFileSystem() { 235 PP_DCHECK(state_ == OPENING_FILE_SYSTEM); 236 237 pp::CompletionCallbackWithOutput<pp::FileSystem> cb = 238 callback_factory_.NewCallbackWithOutput( 239 &CdmFileIOImpl::OnFileSystemOpened); 240 isolated_file_system_ = pp::IsolatedFileSystemPrivate( 241 pp_instance_handle_, PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE); 242 243 CHECK_PP_OK_COMPLETIONPENDING(isolated_file_system_.Open(cb), OPEN_ERROR); 244} 245 246void CdmFileIOImpl::OnFileSystemOpened(int32_t result, 247 pp::FileSystem file_system) { 248 PP_DCHECK(IsMainThread()); 249 PP_DCHECK(state_ == OPENING_FILE_SYSTEM); 250 251 if (result != PP_OK) { 252 CDM_DLOG() << "File system open failed asynchronously."; 253 ReleaseFileLock(); 254 OnError(OPEN_ERROR); 255 return; 256 } 257 258 file_system_ = file_system; 259 state_ = OPENING_FILE; 260 OpenFile(); 261} 262 263void CdmFileIOImpl::OpenFile() { 264 PP_DCHECK(state_ == OPENING_FILE); 265 266 file_io_ = pp::FileIO(pp_instance_handle_); 267 file_ref_ = pp::FileRef(file_system_, file_name_.c_str()); 268 int32_t file_open_flag = PP_FILEOPENFLAG_READ | 269 PP_FILEOPENFLAG_WRITE | 270 PP_FILEOPENFLAG_CREATE; 271 pp::CompletionCallback cb = 272 callback_factory_.NewCallback(&CdmFileIOImpl::OnFileOpened); 273 CHECK_PP_OK_COMPLETIONPENDING(file_io_.Open(file_ref_, file_open_flag, cb), 274 OPEN_ERROR); 275} 276 277void CdmFileIOImpl::OnFileOpened(int32_t result) { 278 PP_DCHECK(IsMainThread()); 279 PP_DCHECK(state_ == OPENING_FILE); 280 281 if (result != PP_OK) { 282 CDM_DLOG() << "File open failed."; 283 ReleaseFileLock(); 284 OnError(OPEN_ERROR); 285 return; 286 } 287 288 state_ = FILE_OPENED; 289 client_->OnOpenComplete(cdm::FileIOClient::kSuccess); 290} 291 292void CdmFileIOImpl::ReadFile() { 293 PP_DCHECK(state_ == READING_FILE); 294 PP_DCHECK(!io_buffer_.empty()); 295 296 pp::CompletionCallback cb = 297 callback_factory_.NewCallback(&CdmFileIOImpl::OnFileRead); 298 CHECK_PP_OK_COMPLETIONPENDING( 299 file_io_.Read(io_offset_, &io_buffer_[0], io_buffer_.size(), cb), 300 READ_ERROR); 301} 302 303void CdmFileIOImpl::OnFileRead(int32_t bytes_read) { 304 CDM_DLOG() << __FUNCTION__ << ": " << bytes_read; 305 PP_DCHECK(IsMainThread()); 306 PP_DCHECK(state_ == READING_FILE); 307 308 // 0 |bytes_read| indicates end-of-file reached. 309 if (bytes_read < PP_OK) { 310 CDM_DLOG() << "Read file failed."; 311 OnError(READ_ERROR); 312 return; 313 } 314 315 PP_DCHECK(static_cast<size_t>(bytes_read) <= io_buffer_.size()); 316 // Append |bytes_read| in |io_buffer_| to |cumulative_read_buffer_|. 317 cumulative_read_buffer_.insert(cumulative_read_buffer_.end(), 318 io_buffer_.begin(), 319 io_buffer_.begin() + bytes_read); 320 io_offset_ += bytes_read; 321 322 // Not received end-of-file yet. 323 if (bytes_read > 0) { 324 ReadFile(); 325 return; 326 } 327 328 // We hit end-of-file. Return read data to the client. 329 io_buffer_.clear(); 330 io_offset_ = 0; 331 // Clear |cumulative_read_buffer_| in case OnReadComplete() calls Read() or 332 // Write(). 333 std::vector<char> local_buffer; 334 std::swap(cumulative_read_buffer_, local_buffer); 335 336 state_ = FILE_OPENED; 337 const uint8_t* data = local_buffer.empty() ? 338 NULL : reinterpret_cast<const uint8_t*>(&local_buffer[0]); 339 client_->OnReadComplete( 340 cdm::FileIOClient::kSuccess, data, local_buffer.size()); 341} 342 343void CdmFileIOImpl::SetLength(uint32_t length) { 344 PP_DCHECK(state_ == WRITING_FILE); 345 346 pp::CompletionCallback cb = 347 callback_factory_.NewCallback(&CdmFileIOImpl::OnLengthSet); 348 CHECK_PP_OK_COMPLETIONPENDING(file_io_.SetLength(length, cb), WRITE_ERROR); 349} 350 351void CdmFileIOImpl::OnLengthSet(int32_t result) { 352 CDM_DLOG() << __FUNCTION__ << ": " << result; 353 PP_DCHECK(IsMainThread()); 354 PP_DCHECK(state_ == WRITING_FILE); 355 356 if (result != PP_OK) { 357 CDM_DLOG() << "File SetLength failed."; 358 OnError(WRITE_ERROR); 359 return; 360 } 361 362 if (io_buffer_.empty()) { 363 state_ = FILE_OPENED; 364 client_->OnWriteComplete(cdm::FileIOClient::kSuccess); 365 return; 366 } 367 368 WriteFile(); 369} 370 371void CdmFileIOImpl::WriteFile() { 372 PP_DCHECK(state_ == WRITING_FILE); 373 PP_DCHECK(io_offset_ < io_buffer_.size()); 374 375 pp::CompletionCallback cb = 376 callback_factory_.NewCallback(&CdmFileIOImpl::OnFileWritten); 377 CHECK_PP_OK_COMPLETIONPENDING(file_io_.Write(io_offset_, 378 &io_buffer_[io_offset_], 379 io_buffer_.size() - io_offset_, 380 cb), 381 WRITE_ERROR); 382} 383 384void CdmFileIOImpl::OnFileWritten(int32_t bytes_written) { 385 CDM_DLOG() << __FUNCTION__ << ": " << bytes_written; 386 PP_DCHECK(IsMainThread()); 387 PP_DCHECK(state_ == WRITING_FILE); 388 389 if (bytes_written <= PP_OK) { 390 CDM_DLOG() << "Write file failed."; 391 OnError(READ_ERROR); 392 return; 393 } 394 395 io_offset_ += bytes_written; 396 PP_DCHECK(io_offset_ <= io_buffer_.size()); 397 398 if (io_offset_ < io_buffer_.size()) { 399 WriteFile(); 400 return; 401 } 402 403 io_buffer_.clear(); 404 io_offset_ = 0; 405 state_ = FILE_OPENED; 406 client_->OnWriteComplete(cdm::FileIOClient::kSuccess); 407} 408 409void CdmFileIOImpl::CloseFile() { 410 PP_DCHECK(IsMainThread()); 411 412 state_ = FILE_CLOSED; 413 414 file_io_.Close(); 415 io_buffer_.clear(); 416 io_offset_ = 0; 417 cumulative_read_buffer_.clear(); 418} 419 420void CdmFileIOImpl::OnError(ErrorType error_type) { 421 // For *_WHILE_IN_USE errors, do not reset these values. Otherwise, the 422 // existing read/write operation will fail. 423 if (error_type == READ_ERROR || error_type == WRITE_ERROR) { 424 io_buffer_.clear(); 425 io_offset_ = 0; 426 cumulative_read_buffer_.clear(); 427 } 428 PostOnMain(callback_factory_.NewCallback(&CdmFileIOImpl::NotifyClientOfError, 429 error_type)); 430} 431 432void CdmFileIOImpl::NotifyClientOfError(int32_t result, 433 ErrorType error_type) { 434 PP_DCHECK(result == PP_OK); 435 switch (error_type) { 436 case OPEN_ERROR: 437 client_->OnOpenComplete(cdm::FileIOClient::kError); 438 break; 439 case READ_ERROR: 440 client_->OnReadComplete(cdm::FileIOClient::kError, NULL, 0); 441 break; 442 case WRITE_ERROR: 443 client_->OnWriteComplete(cdm::FileIOClient::kError); 444 break; 445 case OPEN_WHILE_IN_USE: 446 client_->OnOpenComplete(cdm::FileIOClient::kInUse); 447 break; 448 case READ_WHILE_IN_USE: 449 client_->OnReadComplete(cdm::FileIOClient::kInUse, NULL, 0); 450 break; 451 case WRITE_WHILE_IN_USE: 452 client_->OnWriteComplete(cdm::FileIOClient::kInUse); 453 break; 454 } 455} 456 457} // namespace media 458