Home | History | Annotate | Download | only in ppapi
      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