Chromium Code Reviews| Index: content/common/media/cdm_host_files.cc |
| diff --git a/content/common/media/cdm_host_files.cc b/content/common/media/cdm_host_files.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2ae9d7454cb501e7ffaccc1998ae9b7b73294083 |
| --- /dev/null |
| +++ b/content/common/media/cdm_host_files.cc |
| @@ -0,0 +1,333 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/common/media/cdm_host_files.h" |
| + |
| +#include <map> |
| +#include <memory> |
| +#include <vector> |
| + |
| +#include "base/command_line.h" |
| +#include "base/files/file.h" |
| +#include "base/files/file_path.h" |
| +#include "base/lazy_instance.h" |
| +#include "base/logging.h" |
| +#include "base/memory/ptr_util.h" |
| +#include "base/native_library.h" |
| +#include "base/path_service.h" |
| +#include "base/scoped_native_library.h" |
| +#include "build/build_config.h" |
| +#include "content/common/media/cdm_host_file.h" |
| +#include "content/public/common/cdm_info.h" |
| +#include "content/public/common/content_client.h" |
| +#include "media/base/media_switches.h" |
| +#include "media/cdm/api/content_decryption_module_ext.h" |
| +#include "media/cdm/cdm_paths.h" |
| + |
| +#if defined(POSIX_WITH_ZYGOTE) |
| +#include "content/common/pepper_plugin_list.h" |
| +#include "content/public/common/pepper_plugin_info.h" |
| +#endif |
| + |
| +#include "widevine_cdm_version.h" // In SHARED_INTERMEDIATE_DIR. |
| + |
| +namespace content { |
| + |
| +namespace { |
| + |
| +bool IgnoreMissingCdmHostFile() { |
| + return base::CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kIgnoreMissingCdmHostFileForTesting); |
| +} |
| + |
| +// TODO(xhwang): Move this to a common place if needed. |
| +const base::FilePath::CharType kSignatureFileExtension[] = |
| + FILE_PATH_LITERAL(".sig"); |
| + |
| +// Returns the signature file path given the |file_path|. This function should |
| +// only be used when the signature file and the file are located in the same |
| +// directory, which is the case for the CDM and CDM adapter. |
| +base::FilePath GetSigFilePath(const base::FilePath& file_path) { |
|
Greg K
2017/01/19 04:13:07
Isn't this already defined elsewhere?
xhwang
2017/01/19 08:30:51
Right, it's also defined in chrome/common/media/cd
|
| + return file_path.AddExtension(kSignatureFileExtension); |
| +} |
| + |
| +// Returns the CDM library file name given the |cdm_adapter_file_name|. Returns |
| +// nullptr if |cdm_adapter_file_name| does not correspond to a known CDM. |
| +const char* GetCdmFileName(const base::FilePath& cdm_adapter_file_name) { |
| +#if defined(WIDEVINE_CDM_AVAILABLE) |
| + if (cdm_adapter_file_name == |
| + base::FilePath::FromUTF8Unsafe(kWidevineCdmAdapterFileName)) |
| + return kWidevineCdmLibraryName; |
| +#endif |
| + |
| + // Clear Key CDM. For test only. |
| + if (cdm_adapter_file_name == |
| + base::FilePath::FromUTF8Unsafe(media::kClearKeyCdmAdapterFileName)) |
| + return media::kClearKeyCdmLibraryName; |
| + |
| + return nullptr; |
| +} |
| + |
| +// Returns the path to the CDM binary given the |cdm_adapter_path|. Returns an |
| +// empty path if |cdm_adapter_path| does not correspond to a known CDM. |
| +base::FilePath GetCdmPath(const base::FilePath& cdm_adapter_path) { |
| + const char* cdm_file_name = GetCdmFileName(cdm_adapter_path.BaseName()); |
| + if (!cdm_file_name) |
| + return base::FilePath(); |
| + |
| + return cdm_adapter_path.DirName().AppendASCII( |
| + base::GetNativeLibraryName(cdm_file_name)); |
| +} |
| + |
| +#if defined(POSIX_WITH_ZYGOTE) |
| +// From the list of registered plugins, finds all registered CDMs and fills |
| +// |cdm_adapter_paths| with found CDM adapters paths. |
| +void GetRegisteredCdms(std::vector<base::FilePath>* cdm_adapter_paths) { |
| + std::vector<PepperPluginInfo> plugins; |
| + ComputePepperPluginList(&plugins); |
| + for (const auto& plugin : plugins) { |
| + // CDM is not an internal plugin. |
| + if (plugin.is_internal) |
| + continue; |
| + |
| + if (IsCdm(plugin.path)) |
| + cdm_adapter_paths->push_back(plugin.path); |
| + } |
| +} |
| + |
| +// A global instance used on platforms where we have to open the files in the |
| +// Zygote process. |
| +base::LazyInstance<std::unique_ptr<CdmHostFiles>> g_cdm_host_files = |
| + LAZY_INSTANCE_INITIALIZER; |
| +#endif |
| + |
| +} // namespace |
| + |
| +CdmHostFiles::CdmHostFiles() { |
| + DVLOG(1) << __func__; |
| +} |
| + |
| +CdmHostFiles::~CdmHostFiles() { |
| + DVLOG(1) << __func__; |
| +} |
| + |
| +#if defined(POSIX_WITH_ZYGOTE) |
| +// static |
| +void CdmHostFiles::CreateGlobalInstance() { |
| + DVLOG(1) << __func__; |
| + DCHECK(!g_cdm_host_files.Get().get()); |
| + |
| + std::unique_ptr<CdmHostFiles> cdm_host_files = |
| + base::MakeUnique<CdmHostFiles>(); |
| + if (!cdm_host_files->OpenFilesForAllRegisteredCdms()) { |
| + DVLOG(1) << __func__ << " failed."; |
| + cdm_host_files.reset(); |
| + return; |
| + } |
| + |
| + g_cdm_host_files.Get().reset(cdm_host_files.release()); |
| +} |
| + |
| +// static |
| +std::unique_ptr<CdmHostFiles> CdmHostFiles::TakeGlobalInstance() { |
| + return std::move(g_cdm_host_files.Get()); |
| +} |
| +#endif |
| + |
| +// static |
| +std::unique_ptr<CdmHostFiles> CdmHostFiles::Create( |
| + const base::FilePath& cdm_adapter_path) { |
| + DVLOG(1) << __func__; |
| + std::unique_ptr<CdmHostFiles> cdm_host_files = |
| + base::MakeUnique<CdmHostFiles>(); |
| + if (!cdm_host_files->OpenFiles(cdm_adapter_path)) { |
| + cdm_host_files.reset(); |
| + return nullptr; |
| + } |
| + |
| + return cdm_host_files; |
| +} |
| + |
| +void CdmHostFiles::VerifyFiles(base::NativeLibrary cdm_adapter_library, |
| + const base::FilePath& cdm_adapter_path) { |
| + DVLOG(1) << __func__; |
| + DCHECK(cdm_adapter_library); |
| + |
| + // Get VerifyHostFiles() function pointer exported by the CDM. |
| + // See media/cdm/api/content_decryption_module_ext.h. |
| + using VerifyHostFilesFunc = |
| + bool (*)(const cdm::HostFile* cdm_host_files, uint32_t num_files); |
| + static const char kVerifyHostFilesFuncName[] = "VerifyHostFiles"; |
| + |
| + base::NativeLibrary cdm_library; |
| +#if defined(OS_LINUX) || defined(OS_MACOSX) |
| + // On POSIX, "the dlsym() function shall search for the named symbol in all |
| + // objects loaded automatically as a result of loading the object referenced |
| + // by handle". Since the CDM is loaded automatically as a result of loading |
| + // the CDM adapter, we can just use the adapter to look for CDM symbols. |
| + cdm_library = cdm_adapter_library; |
| +#elif defined(OS_WIN) |
| + // On Windows, we have manually load the CDM. |
| + base::ScopedNativeLibrary scoped_cdm_library; |
| + base::NativeLibraryLoadError error; |
| + scoped_cdm_library.Reset( |
| + base::LoadNativeLibrary(GetCdmPath(cdm_adapter_path), &error)); |
| + if (!scoped_cdm_library.is_valid()) { |
| + LOG(ERROR) << "Failed to load CDM (error: " << error.ToString() << ")"; |
| + CloseAllFiles(); |
| + return; |
| + } |
| + cdm_library = scoped_cdm_library.get(); |
| +#endif |
| + |
| + // TODO(xhwang): This may not work on Windows. If so, the caller should load |
| + // the CDM explicitly and pass the CDM |library| in this call. |
|
jrummell
2017/01/18 21:46:45
Does this comment still apply?
xhwang
2017/01/18 22:29:08
Good catch. Removed.
|
| + VerifyHostFilesFunc verify_host_files_func = |
| + reinterpret_cast<VerifyHostFilesFunc>( |
| + base::GetFunctionPointerFromNativeLibrary(cdm_library, |
| + kVerifyHostFilesFuncName)); |
| + if (!verify_host_files_func) { |
| + LOG(ERROR) << "Function " << kVerifyHostFilesFuncName << " not found."; |
| + CloseAllFiles(); |
| + return; |
| + } |
| + |
| + // Fills |cdm_host_files| with common and CDM specific files for |
| + // |cdm_adapter_path|. |
| + std::vector<cdm::HostFile> cdm_host_files; |
| + if (!TakePlatformFiles(cdm_adapter_path, &cdm_host_files)) { |
| + DVLOG(1) << "Failed to take platform files."; |
| + CloseAllFiles(); |
| + return; |
| + } |
| + |
| + // Call VerifyHostFiles() on the CDM with |cdm_host_files|. Note that the |
| + // ownership of these files are transferred to the CDM, which will close the |
| + // files immediately after use. |
| + DVLOG(1) << __func__ << ": Calling " << kVerifyHostFilesFuncName << "()."; |
| + verify_host_files_func(cdm_host_files.data(), cdm_host_files.size()); |
|
Greg K
2017/01/19 04:13:07
The service is sandboxed when this is called, righ
xhwang
2017/01/19 08:30:51
Yes. See the call site in ppapi_thread.cc.
|
| + |
| + // Close all files not passed to the CDM. |
| + CloseAllFiles(); |
| +} |
| + |
| +#if defined(POSIX_WITH_ZYGOTE) |
| +bool CdmHostFiles::OpenFilesForAllRegisteredCdms() { |
| + std::vector<base::FilePath> cdm_adapter_paths; |
| + GetRegisteredCdms(&cdm_adapter_paths); |
| + if (cdm_adapter_paths.empty()) { |
| + DVLOG(1) << "No CDM registered."; |
| + return false; |
| + } |
| + |
| + // Ignore |
| + for (auto& cdm_adapter_path : cdm_adapter_paths) { |
| + bool result = OpenCdmFiles(cdm_adapter_path); |
| + if (!result) |
| + DVLOG(1) << "CDM files cannot be opened for " << cdm_adapter_path.value(); |
| + // Ignore the failure and try other registered CDM. |
| + } |
| + |
| + if (cdm_specific_files_map_.empty()) { |
| + DVLOG(1) << "CDM specific files cannot be opened for any registered CDM."; |
| + return false; |
| + } |
| + |
| + return OpenCommonFiles(); |
| +} |
| +#endif |
| + |
| +bool CdmHostFiles::OpenFiles(const base::FilePath& cdm_adapter_path) { |
| + if (!OpenCdmFiles(cdm_adapter_path)) |
| + return false; |
| + |
| + return OpenCommonFiles(); |
| +} |
| + |
| +bool CdmHostFiles::OpenCommonFiles() { |
| + DCHECK(common_files_.empty()); |
| + |
| + std::vector<CdmHostFilePath> cdm_host_file_paths; |
| + GetContentClient()->AddContentDecryptionModuleHostFilePaths( |
| + &cdm_host_file_paths); |
| + |
| + for (const CdmHostFilePath& value : cdm_host_file_paths) { |
| + std::unique_ptr<CdmHostFile> cdm_host_file = |
| + CdmHostFile::Create(value.file_path, value.sig_file_path); |
| + if (cdm_host_file) { |
| + common_files_.push_back(std::move(cdm_host_file)); |
| + continue; |
| + } |
| + |
| + if (!IgnoreMissingCdmHostFile()) |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +bool CdmHostFiles::OpenCdmFiles(const base::FilePath& cdm_adapter_path) { |
| + DCHECK(!cdm_adapter_path.empty()); |
| + DCHECK(!cdm_specific_files_map_.count(cdm_adapter_path)); |
| + |
| + std::unique_ptr<CdmHostFile> cdm_adapter_file = |
| + CdmHostFile::Create(cdm_adapter_path, GetSigFilePath(cdm_adapter_path)); |
| + if (!cdm_adapter_file) |
| + return false; |
| + |
| + base::FilePath cdm_path = GetCdmPath(cdm_adapter_path); |
| + std::unique_ptr<CdmHostFile> cdm_file = |
| + CdmHostFile::Create(cdm_path, GetSigFilePath(cdm_path)); |
| + if (!cdm_file) |
| + return false; |
| + |
| + ScopedFileVector cdm_specific_files; |
| + cdm_specific_files.push_back(std::move(cdm_adapter_file)); |
| + cdm_specific_files.push_back(std::move(cdm_file)); |
| + |
| + cdm_specific_files_map_[cdm_adapter_path] = std::move(cdm_specific_files); |
| + return true; |
| +} |
| + |
| +bool CdmHostFiles::TakePlatformFiles( |
| + const base::FilePath& cdm_adapter_path, |
| + std::vector<cdm::HostFile>* cdm_host_files) { |
| + DCHECK(cdm_host_files->empty()); |
| + |
| + if (!IgnoreMissingCdmHostFile()) |
| + DCHECK(!common_files_.empty()); |
| + |
| + // Check whether CDM specific files exist. |
| + const auto& iter = cdm_specific_files_map_.find(cdm_adapter_path); |
| + if (iter == cdm_specific_files_map_.end()) { |
| + // This could happen on Linux where CDM files fail to open for Foo CDM, but |
| + // now we hit Bar CDM. |
| + DVLOG(1) << "No CDM specific files for " << cdm_adapter_path.value(); |
| + return false; |
| + } |
| + |
| + const ScopedFileVector& cdm_specific_files = iter->second; |
| + |
| + // Populate an array of cdm::HostFile. |
| + for (const auto& file : common_files_) |
| + cdm_host_files->push_back(file->TakePlatformFile()); |
|
jrummell
2017/01/18 21:46:45
Should reserve() be called on |cdm_host_files| fir
xhwang
2017/01/18 22:29:08
Done.
|
| + |
| + for (const auto& file : cdm_specific_files) |
| + cdm_host_files->push_back(file->TakePlatformFile()); |
| + |
| + return true; |
| +} |
| + |
| +void CdmHostFiles::CloseAllFiles() { |
| + common_files_.clear(); |
| + cdm_specific_files_map_.clear(); |
| +} |
| + |
| +// Question(xhwang): Any better way to check whether a plugin is a CDM? Maybe |
| +// when we register the plugin we can set some flag explicitly? |
| +bool IsCdm(const base::FilePath& cdm_adapter_path) { |
| + return !GetCdmPath(cdm_adapter_path).empty(); |
| +} |
| + |
| +} // namespace content |