• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
IsMainThread()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.
PostOnMain(pp::CompletionCallback cb)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 
ResourceTracker()53 CdmFileIOImpl::ResourceTracker::ResourceTracker() {
54   // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_
55   // in CdmFileIOImpl::AcquireFileLock().
56 }
57 
~ResourceTracker()58 CdmFileIOImpl::ResourceTracker::~ResourceTracker() {
59   delete CdmFileIOImpl::file_lock_map_;
60 }
61 
CdmFileIOImpl(cdm::FileIOClient * client,PP_Instance pp_instance,const pp::CompletionCallback & first_file_read_cb)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 
~CdmFileIOImpl()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.
Open(const char * file_name,uint32_t file_name_size)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.
Read()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>.
Write(const uint8_t * data,uint32_t data_size)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 
Close()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 
SetFileID()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 
AcquireFileLock()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 
ReleaseFileLock()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 
OpenFileSystem()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 
OnFileSystemOpened(int32_t result,pp::FileSystem file_system)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 
OpenFileForRead()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 
OnFileOpenedForRead(int32_t result)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 //     |----------------|
ReadFile()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 
OnFileRead(int32_t bytes_read)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 
OpenTempFileForWrite()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 
OnTempFileOpenedForWrite(int32_t result)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 //      |---------------------|
WriteTempFile()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 
OnTempFileWritten(int32_t bytes_written)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 
RenameTempFile()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 
OnTempFileRenamed(int32_t result)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 
Reset()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 
OnError(ErrorType error_type)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 
NotifyClientOfError(int32_t result,ErrorType error_type)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