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