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
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.
19const 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.
40static 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.
47static void PostOnMain(pp::CompletionCallback cb) {
48  pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK);
49}
50
51CdmFileIOImpl::FileLockMap* CdmFileIOImpl::file_lock_map_ = NULL;
52
53CdmFileIOImpl::ResourceTracker::ResourceTracker() {
54  // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_
55  // in CdmFileIOImpl::AcquireFileLock().
56}
57
58CdmFileIOImpl::ResourceTracker::~ResourceTracker() {
59  delete CdmFileIOImpl::file_lock_map_;
60}
61
62CdmFileIOImpl::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
77CdmFileIOImpl::~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.
85void 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.
123void 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>.
152void 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
179void 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
190bool 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
213bool 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
231void 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
242void 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
254void 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
273void 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
291void 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//     |----------------|
320void 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
331void 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
381void 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
407void 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//      |---------------------|
437void 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
450void 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
474void 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
484void 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
502void 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
512void 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
522void 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