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.
5 #include "media/cdm/ppapi/cdm_file_io_impl.h"
9 #include "media/cdm/ppapi/cdm_logging.h"
10 #include "ppapi/c/pp_errors.h"
11 #include "ppapi/cpp/dev/url_util_dev.h"
15 // Arbitrary choice based on the following heuristic ideas:
16 // - not too big to avoid unnecessarily large memory allocation;
17 // - not too small to avoid breaking most reads into multiple read operations.
18 const int kReadSize
= 8 * 1024;
20 // Call func_call and check the result. If the result is not
21 // PP_OK_COMPLETIONPENDING, print out logs, call OnError() and return.
22 #define CHECK_PP_OK_COMPLETIONPENDING(func_call, error_type) \
24 int32_t result = func_call; \
25 PP_DCHECK(result != PP_OK); \
26 if (result != PP_OK_COMPLETIONPENDING) { \
27 CDM_DLOG() << #func_call << " failed with result: " << result; \
28 state_ = STATE_ERROR; \
29 OnError(error_type); \
35 // PPAPI calls should only be made on the main thread. In this file, main thread
36 // checking is only performed in public APIs and the completion callbacks. This
37 // ensures all functions are running on the main thread since internal methods
38 // are called either by the public APIs or by the completion callbacks.
39 static bool IsMainThread() {
40 return pp::Module::Get()->core()->IsMainThread();
42 #endif // !defined(NDEBUG)
44 // Posts a task to run |cb| on the main thread. The task is posted even if the
45 // current thread is the main thread.
46 static void PostOnMain(pp::CompletionCallback cb
) {
47 pp::Module::Get()->core()->CallOnMainThread(0, cb
, PP_OK
);
50 CdmFileIOImpl::FileLockMap
* CdmFileIOImpl::file_lock_map_
= NULL
;
52 CdmFileIOImpl::ResourceTracker::ResourceTracker() {
53 // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_
54 // in CdmFileIOImpl::AcquireFileLock().
57 CdmFileIOImpl::ResourceTracker::~ResourceTracker() {
58 delete CdmFileIOImpl::file_lock_map_
;
61 CdmFileIOImpl::CdmFileIOImpl(
62 cdm::FileIOClient
* client
,
63 PP_Instance pp_instance
,
64 const pp::CompletionCallback
& first_file_read_cb
)
65 : state_(STATE_UNOPENED
),
67 pp_instance_handle_(pp_instance
),
69 first_file_read_reported_(false),
70 first_file_read_cb_(first_file_read_cb
),
71 callback_factory_(this) {
72 PP_DCHECK(IsMainThread());
73 PP_DCHECK(pp_instance
); // 0 indicates a "NULL handle".
76 CdmFileIOImpl::~CdmFileIOImpl() {
77 // The destructor is private. |this| can only be destructed through Close().
78 PP_DCHECK(state_
== STATE_CLOSED
);
81 // Call sequence: Open() -> OpenFileSystem() -> STATE_FILE_SYSTEM_OPENED.
82 // Note: This only stores file name and opens the file system. The real file
83 // open is deferred to when Read() or Write() is called.
84 void CdmFileIOImpl::Open(const char* file_name
, uint32_t file_name_size
) {
85 CDM_DLOG() << __FUNCTION__
;
86 PP_DCHECK(IsMainThread());
88 if (state_
!= STATE_UNOPENED
) {
89 CDM_DLOG() << "Open() called in an invalid state.";
94 // File name should not (1) be empty, (2) start with '_', or (3) contain any
96 std::string
file_name_str(file_name
, file_name_size
);
97 if (file_name_str
.empty() ||
98 file_name_str
[0] == '_' ||
99 file_name_str
.find('/') != std::string::npos
||
100 file_name_str
.find('\\') != std::string::npos
) {
101 CDM_DLOG() << "Invalid file name.";
102 state_
= STATE_ERROR
;
107 // pp::FileRef only accepts path that begins with a '/' character.
108 file_name_
= '/' + file_name_str
;
110 if (!AcquireFileLock()) {
111 CDM_DLOG() << "File is in use by other cdm::FileIO objects.";
112 OnError(OPEN_WHILE_IN_USE
);
116 state_
= STATE_OPENING_FILE_SYSTEM
;
121 // Read() -> OpenFileForRead() -> ReadFile() -> Done.
122 void CdmFileIOImpl::Read() {
123 CDM_DLOG() << __FUNCTION__
;
124 PP_DCHECK(IsMainThread());
126 if (state_
== STATE_READING
|| state_
== STATE_WRITING
) {
127 CDM_DLOG() << "Read() called during pending read/write.";
128 OnError(READ_WHILE_IN_USE
);
132 if (state_
!= STATE_FILE_SYSTEM_OPENED
) {
133 CDM_DLOG() << "Read() called in an invalid state.";
138 PP_DCHECK(io_offset_
== 0);
139 PP_DCHECK(io_buffer_
.empty());
140 PP_DCHECK(cumulative_read_buffer_
.empty());
141 io_buffer_
.resize(kReadSize
);
144 state_
= STATE_READING
;
149 // Write() -> OpenTempFileForWrite() -> WriteTempFile() -> RenameTempFile().
150 // The file name of the temporary file is /_<requested_file_name>.
151 void CdmFileIOImpl::Write(const uint8_t* data
, uint32_t data_size
) {
152 CDM_DLOG() << __FUNCTION__
;
153 PP_DCHECK(IsMainThread());
155 if (state_
== STATE_READING
|| state_
== STATE_WRITING
) {
156 CDM_DLOG() << "Write() called during pending read/write.";
157 OnError(WRITE_WHILE_IN_USE
);
161 if (state_
!= STATE_FILE_SYSTEM_OPENED
) {
162 CDM_DLOG() << "Write() called in an invalid state.";
163 OnError(WRITE_ERROR
);
167 PP_DCHECK(io_offset_
== 0);
168 PP_DCHECK(io_buffer_
.empty());
170 io_buffer_
.assign(data
, data
+ data_size
);
174 state_
= STATE_WRITING
;
175 OpenTempFileForWrite();
178 void CdmFileIOImpl::Close() {
179 CDM_DLOG() << __FUNCTION__
;
180 PP_DCHECK(IsMainThread());
181 PP_DCHECK(state_
!= STATE_CLOSED
);
183 state_
= STATE_CLOSED
;
185 // All pending callbacks are canceled since |callback_factory_| is destroyed.
189 bool CdmFileIOImpl::SetFileID() {
190 PP_DCHECK(file_id_
.empty());
191 PP_DCHECK(!file_name_
.empty() && file_name_
[0] == '/');
193 // Not taking ownership of |url_util_dev| (which is a singleton).
194 const pp::URLUtil_Dev
* url_util_dev
= pp::URLUtil_Dev::Get();
195 PP_URLComponents_Dev components
;
197 url_util_dev
->GetDocumentURL(pp_instance_handle_
, &components
);
198 if (!url_var
.is_string())
200 std::string url
= url_var
.AsString();
202 file_id_
.append(url
, components
.scheme
.begin
, components
.scheme
.len
);
204 file_id_
.append(url
, components
.host
.begin
, components
.host
.len
);
206 file_id_
.append(url
, components
.port
.begin
, components
.port
.len
);
207 file_id_
+= file_name_
;
212 bool CdmFileIOImpl::AcquireFileLock() {
213 PP_DCHECK(IsMainThread());
215 if (file_id_
.empty() && !SetFileID())
218 if (!file_lock_map_
) {
219 file_lock_map_
= new FileLockMap();
221 FileLockMap::iterator found
= file_lock_map_
->find(file_id_
);
222 if (found
!= file_lock_map_
->end() && found
->second
)
226 (*file_lock_map_
)[file_id_
] = true;
230 void CdmFileIOImpl::ReleaseFileLock() {
231 PP_DCHECK(IsMainThread());
236 FileLockMap::iterator found
= file_lock_map_
->find(file_id_
);
237 if (found
!= file_lock_map_
->end() && found
->second
)
238 found
->second
= false;
241 void CdmFileIOImpl::OpenFileSystem() {
242 PP_DCHECK(state_
== STATE_OPENING_FILE_SYSTEM
);
244 pp::CompletionCallbackWithOutput
<pp::FileSystem
> cb
=
245 callback_factory_
.NewCallbackWithOutput(
246 &CdmFileIOImpl::OnFileSystemOpened
);
247 isolated_file_system_
= pp::IsolatedFileSystemPrivate(
248 pp_instance_handle_
, PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE
);
250 CHECK_PP_OK_COMPLETIONPENDING(isolated_file_system_
.Open(cb
), OPEN_ERROR
);
253 void CdmFileIOImpl::OnFileSystemOpened(int32_t result
,
254 pp::FileSystem file_system
) {
255 PP_DCHECK(IsMainThread());
256 PP_DCHECK(state_
== STATE_OPENING_FILE_SYSTEM
);
258 if (result
!= PP_OK
) {
259 CDM_DLOG() << "File system open failed asynchronously.";
261 state_
= STATE_ERROR
;
266 file_system_
= file_system
;
268 state_
= STATE_FILE_SYSTEM_OPENED
;
269 client_
->OnOpenComplete(cdm::FileIOClient::kSuccess
);
272 void CdmFileIOImpl::OpenFileForRead() {
273 PP_DCHECK(state_
== STATE_READING
);
275 PP_DCHECK(file_io_
.is_null());
276 PP_DCHECK(file_ref_
.is_null());
277 file_io_
= pp::FileIO(pp_instance_handle_
);
278 file_ref_
= pp::FileRef(file_system_
, file_name_
.c_str());
280 // Open file for read. If file doesn't exist, PP_ERROR_FILENOTFOUND will be
282 int32_t file_open_flag
= PP_FILEOPENFLAG_READ
;
284 pp::CompletionCallback cb
=
285 callback_factory_
.NewCallback(&CdmFileIOImpl::OnFileOpenedForRead
);
286 CHECK_PP_OK_COMPLETIONPENDING(file_io_
.Open(file_ref_
, file_open_flag
, cb
),
290 void CdmFileIOImpl::OnFileOpenedForRead(int32_t result
) {
291 CDM_DLOG() << __FUNCTION__
<< ": " << result
;
292 PP_DCHECK(IsMainThread());
293 PP_DCHECK(state_
== STATE_READING
);
295 if (result
!= PP_OK
&& result
!= PP_ERROR_FILENOTFOUND
) {
296 CDM_DLOG() << "File open failed.";
297 state_
= STATE_ERROR
;
302 // File doesn't exist.
303 if (result
== PP_ERROR_FILENOTFOUND
) {
305 state_
= STATE_FILE_SYSTEM_OPENED
;
306 client_
->OnReadComplete(cdm::FileIOClient::kSuccess
, NULL
, 0);
315 // ReadFile() ---> OnFileRead() ------------> Done.
317 // | partially read |
318 // |----------------|
319 void CdmFileIOImpl::ReadFile() {
320 PP_DCHECK(state_
== STATE_READING
);
321 PP_DCHECK(!io_buffer_
.empty());
323 pp::CompletionCallback cb
=
324 callback_factory_
.NewCallback(&CdmFileIOImpl::OnFileRead
);
325 CHECK_PP_OK_COMPLETIONPENDING(
326 file_io_
.Read(io_offset_
, &io_buffer_
[0], io_buffer_
.size(), cb
),
330 void CdmFileIOImpl::OnFileRead(int32_t bytes_read
) {
331 CDM_DLOG() << __FUNCTION__
<< ": " << bytes_read
;
332 PP_DCHECK(IsMainThread());
333 PP_DCHECK(state_
== STATE_READING
);
335 // 0 |bytes_read| indicates end-of-file reached.
336 if (bytes_read
< PP_OK
) {
337 CDM_DLOG() << "Read file failed.";
338 state_
= STATE_ERROR
;
343 PP_DCHECK(static_cast<size_t>(bytes_read
) <= io_buffer_
.size());
344 // Append |bytes_read| in |io_buffer_| to |cumulative_read_buffer_|.
345 cumulative_read_buffer_
.insert(cumulative_read_buffer_
.end(),
347 io_buffer_
.begin() + bytes_read
);
348 io_offset_
+= bytes_read
;
350 // Not received end-of-file yet. Keep reading.
351 if (bytes_read
> 0) {
356 // We hit end-of-file. Return read data to the client.
358 // Clear |cumulative_read_buffer_| in case OnReadComplete() calls Read() or
360 std::vector
<char> local_buffer
;
361 std::swap(cumulative_read_buffer_
, local_buffer
);
363 const uint8_t* data
= local_buffer
.empty() ?
364 NULL
: reinterpret_cast<const uint8_t*>(&local_buffer
[0]);
366 // Call this before OnReadComplete() so that we always have the latest file
367 // size before CDM fires errors.
368 if (!first_file_read_reported_
) {
369 first_file_read_cb_
.Run(local_buffer
.size());
370 first_file_read_reported_
= true;
375 state_
= STATE_FILE_SYSTEM_OPENED
;
376 client_
->OnReadComplete(
377 cdm::FileIOClient::kSuccess
, data
, local_buffer
.size());
380 void CdmFileIOImpl::OpenTempFileForWrite() {
381 PP_DCHECK(state_
== STATE_WRITING
);
383 PP_DCHECK(file_name_
.size() > 1 && file_name_
[0] == '/');
384 // Temporary file name format: /_<requested_file_name>
385 std::string temp_file_name
= "/_" + file_name_
.substr(1);
387 PP_DCHECK(file_io_
.is_null());
388 PP_DCHECK(file_ref_
.is_null());
389 file_io_
= pp::FileIO(pp_instance_handle_
);
390 file_ref_
= pp::FileRef(file_system_
, temp_file_name
.c_str());
392 // Create the file if it doesn't exist. Truncate the file to length 0 if it
394 // TODO(xhwang): Find a good way to report to UMA cases where the temporary
395 // file already exists (due to previous interruption or failure).
396 int32_t file_open_flag
= PP_FILEOPENFLAG_WRITE
|
397 PP_FILEOPENFLAG_TRUNCATE
|
398 PP_FILEOPENFLAG_CREATE
;
400 pp::CompletionCallback cb
=
401 callback_factory_
.NewCallback(&CdmFileIOImpl::OnTempFileOpenedForWrite
);
402 CHECK_PP_OK_COMPLETIONPENDING(
403 file_io_
.Open(file_ref_
, file_open_flag
, cb
), WRITE_ERROR
);
406 void CdmFileIOImpl::OnTempFileOpenedForWrite(int32_t result
) {
407 CDM_DLOG() << __FUNCTION__
<< ": " << result
;
408 PP_DCHECK(IsMainThread());
409 PP_DCHECK(state_
== STATE_WRITING
);
411 if (result
!= PP_OK
) {
412 CDM_DLOG() << "Open temporary file failed.";
413 state_
= STATE_ERROR
;
414 OnError(WRITE_ERROR
);
418 // We were told to write 0 bytes (to clear the file). In this case, there's
419 // no need to write anything.
420 if (io_buffer_
.empty()) {
425 PP_DCHECK(io_offset_
== 0);
432 // WriteTempFile() -> OnTempFileWritten() ---------------> RenameTempFile().
434 // | partially written |
435 // |---------------------|
436 void CdmFileIOImpl::WriteTempFile() {
437 PP_DCHECK(state_
== STATE_WRITING
);
438 PP_DCHECK(io_offset_
< io_buffer_
.size());
440 pp::CompletionCallback cb
=
441 callback_factory_
.NewCallback(&CdmFileIOImpl::OnTempFileWritten
);
442 CHECK_PP_OK_COMPLETIONPENDING(file_io_
.Write(io_offset_
,
443 &io_buffer_
[io_offset_
],
444 io_buffer_
.size() - io_offset_
,
449 void CdmFileIOImpl::OnTempFileWritten(int32_t bytes_written
) {
450 CDM_DLOG() << __FUNCTION__
<< ": " << bytes_written
;
451 PP_DCHECK(IsMainThread());
452 PP_DCHECK(state_
== STATE_WRITING
);
454 if (bytes_written
<= PP_OK
) {
455 CDM_DLOG() << "Write temporary file failed.";
456 state_
= STATE_ERROR
;
457 OnError(WRITE_ERROR
);
461 io_offset_
+= bytes_written
;
462 PP_DCHECK(io_offset_
<= io_buffer_
.size());
464 if (io_offset_
< io_buffer_
.size()) {
469 // All data written. Now rename the temporary file to the real file.
473 void CdmFileIOImpl::RenameTempFile() {
474 PP_DCHECK(state_
== STATE_WRITING
);
476 pp::CompletionCallback cb
=
477 callback_factory_
.NewCallback(&CdmFileIOImpl::OnTempFileRenamed
);
478 CHECK_PP_OK_COMPLETIONPENDING(
479 file_ref_
.Rename(pp::FileRef(file_system_
, file_name_
.c_str()), cb
),
483 void CdmFileIOImpl::OnTempFileRenamed(int32_t result
) {
484 CDM_DLOG() << __FUNCTION__
<< ": " << result
;
485 PP_DCHECK(IsMainThread());
486 PP_DCHECK(state_
== STATE_WRITING
);
488 if (result
!= PP_OK
) {
489 CDM_DLOG() << "Rename temporary file failed.";
490 state_
= STATE_ERROR
;
491 OnError(WRITE_ERROR
);
497 state_
= STATE_FILE_SYSTEM_OPENED
;
498 client_
->OnWriteComplete(cdm::FileIOClient::kSuccess
);
501 void CdmFileIOImpl::Reset() {
502 PP_DCHECK(IsMainThread());
505 cumulative_read_buffer_
.clear();
507 file_io_
= pp::FileIO();
508 file_ref_
= pp::FileRef();
511 void CdmFileIOImpl::OnError(ErrorType error_type
) {
512 // For *_WHILE_IN_USE errors, do not reset these values. Otherwise, the
513 // existing read/write operation will fail.
514 if (error_type
== READ_ERROR
|| error_type
== WRITE_ERROR
)
517 PostOnMain(callback_factory_
.NewCallback(&CdmFileIOImpl::NotifyClientOfError
,
521 void CdmFileIOImpl::NotifyClientOfError(int32_t result
,
522 ErrorType error_type
) {
523 PP_DCHECK(result
== PP_OK
);
524 switch (error_type
) {
526 client_
->OnOpenComplete(cdm::FileIOClient::kError
);
529 client_
->OnReadComplete(cdm::FileIOClient::kError
, NULL
, 0);
532 client_
->OnWriteComplete(cdm::FileIOClient::kError
);
534 case OPEN_WHILE_IN_USE
:
535 client_
->OnOpenComplete(cdm::FileIOClient::kInUse
);
537 case READ_WHILE_IN_USE
:
538 client_
->OnReadComplete(cdm::FileIOClient::kInUse
, NULL
, 0);
540 case WRITE_WHILE_IN_USE
:
541 client_
->OnWriteComplete(cdm::FileIOClient::kInUse
);