| // Copyright 2017 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/browser/media/cdm_file_impl.h" |
| |
| #include <set> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "storage/browser/file_system/file_stream_reader.h" |
| #include "storage/browser/file_system/file_stream_writer.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "storage/browser/file_system/file_system_operation_context.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| #include "storage/browser/quota/quota_manager.h" |
| #include "storage/common/file_system/file_system_types.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // The CDM interface has a restriction that file names can not begin with _, |
| // so use it to prefix temporary files. |
| const char kTemporaryFilePrefix = '_'; |
| |
| // File size limit is 512KB. Licenses saved by the CDM are typically several |
| // hundreds of bytes. |
| const int64_t kMaxFileSizeBytes = 512 * 1024; |
| |
| // Maximum length of a file name. |
| const size_t kFileNameMaxLength = 256; |
| |
| const char kReadTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.ReadFile"; |
| const char kWriteTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.WriteFile"; |
| const char kDeleteTimeUmaName[] = "Media.EME.CdmFileIO.TimeTo.DeleteFile"; |
| |
| std::string GetTempFileName(const std::string& file_name) { |
| DCHECK(!base::StartsWith(file_name, std::string(1, kTemporaryFilePrefix), |
| base::CompareCase::SENSITIVE)); |
| return kTemporaryFilePrefix + file_name; |
| } |
| |
| // The file system is different for each CDM and each origin. So track files |
| // in use based on (file system ID, origin, file name). |
| struct FileLockKey { |
| FileLockKey(const std::string& file_system_id, |
| const url::Origin& origin, |
| const std::string& file_name) |
| : file_system_id(file_system_id), origin(origin), file_name(file_name) {} |
| ~FileLockKey() = default; |
| |
| // Allow use as a key in std::set. |
| bool operator<(const FileLockKey& other) const { |
| return std::tie(file_system_id, origin, file_name) < |
| std::tie(other.file_system_id, other.origin, other.file_name); |
| } |
| |
| std::string file_system_id; |
| url::Origin origin; |
| std::string file_name; |
| }; |
| |
| // File map shared by all CdmFileImpl objects to prevent read/write race. |
| // A lock must be acquired before opening a file to ensure that the file is not |
| // currently in use. The lock must be held until the file is closed. |
| class FileLockMap { |
| public: |
| FileLockMap() = default; |
| ~FileLockMap() = default; |
| |
| // Acquire a lock on the file represented by |key|. Returns true if |key| |
| // is not currently in use, false otherwise. |
| bool AcquireFileLock(const FileLockKey& key) { |
| DVLOG(3) << __func__ << " file: " << key.file_name; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // Add a new entry. If |key| already has an entry, insert() tells so |
| // with the second piece of the returned value and does not modify |
| // the original. |
| return file_lock_map_.insert(key).second; |
| } |
| |
| // Release the lock held on the file represented by |key|. |
| void ReleaseFileLock(const FileLockKey& key) { |
| DVLOG(3) << __func__ << " file: " << key.file_name; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| auto entry = file_lock_map_.find(key); |
| if (entry == file_lock_map_.end()) { |
| NOTREACHED() << "Unable to release lock on file " << key.file_name; |
| return; |
| } |
| |
| file_lock_map_.erase(entry); |
| } |
| |
| private: |
| // Note that this map is never deleted. As entries are removed when a file |
| // is closed, it should never get too large. |
| std::set<FileLockKey> file_lock_map_; |
| |
| THREAD_CHECKER(thread_checker_); |
| DISALLOW_COPY_AND_ASSIGN(FileLockMap); |
| }; |
| |
| // The FileLockMap is a global lock map shared by all CdmFileImpl instances. |
| FileLockMap* GetFileLockMap() { |
| static auto* file_lock_map = new FileLockMap(); |
| return file_lock_map; |
| } |
| |
| // File stream operations need an IOBuffer to hold the data. This class stores |
| // the data in a std::vector<uint8_t> to match what is used in the |
| // mojom::CdmFile API. |
| class CdmFileIOBuffer : public net::IOBuffer { |
| public: |
| // Create an empty buffer of size |size|. |
| explicit CdmFileIOBuffer(size_t size) : buffer_(size) { |
| data_ = reinterpret_cast<char*>(buffer_.data()); |
| } |
| |
| // Create a buffer that contains |data|. |
| explicit CdmFileIOBuffer(const std::vector<uint8_t>& data) : buffer_(data) { |
| data_ = reinterpret_cast<char*>(buffer_.data()); |
| } |
| |
| // Returns ownership of |buffer_| to the caller. |
| std::vector<uint8_t>&& TakeData() { return std::move(buffer_); } |
| |
| protected: |
| ~CdmFileIOBuffer() override { data_ = nullptr; } |
| |
| private: |
| std::vector<uint8_t> buffer_; |
| }; |
| |
| } // namespace |
| |
| // Read a file using FileStreamReader. Implemented as a separate class so that |
| // it can be run on the IO thread. |
| class CdmFileImpl::FileReader { |
| public: |
| // Returns whether the read operation succeeded or not. If |result| = true, |
| // then |data| is the contents of the file. |
| using ReadDoneCB = |
| base::OnceCallback<void(bool result, std::vector<uint8_t> data)>; |
| |
| FileReader() = default; |
| |
| // Reads the contents of |file_url| and calls |callback| with the result |
| // (file contents on success, empty data on error). |
| void Read(scoped_refptr<storage::FileSystemContext> file_system_context, |
| const storage::FileSystemURL& file_url, |
| ReadDoneCB callback) { |
| DVLOG(3) << __func__ << " url: " << file_url.DebugString(); |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!callback_); |
| DCHECK(!file_stream_reader_); |
| |
| callback_ = std::move(callback); |
| |
| file_stream_reader_ = file_system_context->CreateFileStreamReader( |
| file_url, 0, kMaxFileSizeBytes, base::Time()); |
| auto result = file_stream_reader_->GetLength( |
| base::BindOnce(&FileReader::OnGetLength, weak_factory_.GetWeakPtr())); |
| DVLOG(3) << __func__ << " GetLength(): " << result; |
| |
| // If GetLength() is running asynchronously, simply return. |
| if (result == net::ERR_IO_PENDING) |
| return; |
| |
| // GetLength() was synchronous, so pass the result on. |
| OnGetLength(result); |
| } |
| |
| private: |
| // Called when the size of the file to be read is known. Allocates a buffer |
| // large enough to hold the contents, then attempts to read the contents into |
| // the buffer. |result| will be the length of the file (if >= 0) or a net:: |
| // error on failure (if < 0). |
| void OnGetLength(int64_t result) { |
| DVLOG(3) << __func__ << " result: " << result; |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(callback_); |
| DCHECK(file_stream_reader_); |
| |
| // If the file doesn't exist, then pretend it is empty. |
| if (result == net::ERR_FILE_NOT_FOUND) { |
| std::move(callback_).Run(true, {}); |
| return; |
| } |
| |
| // Any other failure is an error. |
| if (result < 0) { |
| DLOG(WARNING) << __func__ |
| << " Unable to get file length. result = " << result; |
| std::move(callback_).Run(false, {}); |
| return; |
| } |
| |
| // Files are limited in size, so fail if file too big. |
| if (result > kMaxFileSizeBytes) { |
| DLOG(WARNING) << __func__ |
| << " Too much data to read. #bytes = " << result; |
| std::move(callback_).Run(false, {}); |
| return; |
| } |
| |
| // Read() sizes (provided and returned) are type int, so cast appropriately. |
| int bytes_to_read = base::checked_cast<int>(result); |
| auto buffer = base::MakeRefCounted<CdmFileIOBuffer>( |
| base::checked_cast<size_t>(bytes_to_read)); |
| |
| // Read the contents of the file into |buffer|. |
| result = file_stream_reader_->Read( |
| buffer.get(), bytes_to_read, |
| base::BindOnce(&FileReader::OnRead, weak_factory_.GetWeakPtr(), buffer, |
| bytes_to_read)); |
| DVLOG(3) << __func__ << " Read(): " << result; |
| |
| // If Read() is running asynchronously, simply return. |
| if (result == net::ERR_IO_PENDING) |
| return; |
| |
| // Read() was synchronous, so pass the result on. |
| OnRead(std::move(buffer), bytes_to_read, result); |
| } |
| |
| // Called when the file has been read and returns the result to the callback |
| // provided to Read(). |result| will be the number of bytes read (if >= 0) or |
| // a net:: error on failure (if < 0). |
| void OnRead(scoped_refptr<CdmFileIOBuffer> buffer, |
| int bytes_to_read, |
| int result) { |
| DVLOG(3) << __func__ << " Requested " << bytes_to_read << " bytes, got " |
| << result; |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(callback_); |
| DCHECK(file_stream_reader_); |
| |
| if (result != bytes_to_read) { |
| // Unable to read the contents of the file completely. |
| DLOG(WARNING) << "Failed to read file. Requested " << bytes_to_read |
| << " bytes, got " << result; |
| std::move(callback_).Run(false, {}); |
| return; |
| } |
| |
| // Successful read. Return the bytes read. |
| std::move(callback_).Run(true, std::move(buffer->TakeData())); |
| } |
| |
| // Called when the read operation is done. |
| ReadDoneCB callback_; |
| |
| // Used to read the stream. |
| std::unique_ptr<storage::FileStreamReader> file_stream_reader_; |
| |
| base::WeakPtrFactory<FileReader> weak_factory_{this}; |
| DISALLOW_COPY_AND_ASSIGN(FileReader); |
| }; |
| |
| class CdmFileImpl::FileWriter { |
| public: |
| // Returns whether the write operation succeeded or not. |
| using WriteDoneCB = base::OnceCallback<void(bool)>; |
| |
| FileWriter() {} |
| |
| // Writes |buffer| as the contents of |file_url| and calls |callback| with |
| // whether the write succeeded or not. |
| void Write(scoped_refptr<storage::FileSystemContext> file_system_context, |
| const storage::FileSystemURL& file_url, |
| scoped_refptr<net::IOBuffer> buffer, |
| int bytes_to_write, |
| WriteDoneCB callback) { |
| DVLOG(3) << __func__ << " url: " << file_url.DebugString(); |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| callback_ = std::move(callback); |
| |
| // Create a writer on |temp_file_name_|. This temp file will be renamed |
| // after a successful write. |
| file_stream_writer_ = |
| file_system_context->CreateFileStreamWriter(file_url, 0); |
| auto result = file_stream_writer_->Write( |
| buffer.get(), bytes_to_write, |
| base::BindOnce(&FileWriter::OnWrite, weak_factory_.GetWeakPtr(), buffer, |
| bytes_to_write)); |
| DVLOG(3) << __func__ << " Write(): " << result; |
| |
| // If Write() is running asynchronously, simply return. |
| if (result == net::ERR_IO_PENDING) |
| return; |
| |
| // Write() was synchronous, so pass the result on. |
| OnWrite(std::move(buffer), bytes_to_write, result); |
| } |
| |
| private: |
| // Called when the file has been written. |result| will be the number of bytes |
| // written (if >= 0) or a net:: error on failure (if < 0). |
| void OnWrite(scoped_refptr<net::IOBuffer> buffer, |
| int bytes_to_write, |
| int result) { |
| DVLOG(3) << __func__ << " Expected to write " << bytes_to_write |
| << " bytes, got " << result; |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| if (result != bytes_to_write) { |
| // Unable to write the file. |
| DLOG(WARNING) << "Failed to write file. Sent " << bytes_to_write |
| << " bytes, wrote " << result; |
| std::move(callback_).Run(false); |
| return; |
| } |
| |
| result = file_stream_writer_->Flush( |
| base::BindOnce(&FileWriter::OnFlush, weak_factory_.GetWeakPtr())); |
| DVLOG(3) << __func__ << " Flush(): " << result; |
| |
| // If Flush() is running asynchronously, simply return. |
| if (result == net::ERR_IO_PENDING) |
| return; |
| |
| // Flush() was synchronous, so pass the result on. |
| OnFlush(result); |
| } |
| |
| // Called when the file has been flushed. |result| is the net:: error code. |
| void OnFlush(int result) { |
| DVLOG(3) << __func__ << " Result: " << result; |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // We are done with |file_stream_writer_|. |
| file_stream_writer_.reset(); |
| |
| DLOG_IF(WARNING, result != net::OK) |
| << "Failed to flush file, result: " << result; |
| std::move(callback_).Run(result == net::OK); |
| } |
| |
| // Called when the write operation is done. |
| WriteDoneCB callback_; |
| |
| // Used to write the stream. |
| std::unique_ptr<storage::FileStreamWriter> file_stream_writer_; |
| |
| base::WeakPtrFactory<FileWriter> weak_factory_{this}; |
| DISALLOW_COPY_AND_ASSIGN(FileWriter); |
| }; |
| |
| // static |
| bool CdmFileImpl::IsValidName(const std::string& name) { |
| // File names must only contain letters (A-Za-z), digits(0-9), or "._-", |
| // and not start with "_". It must contain at least 1 character, and not |
| // more then |kFileNameMaxLength| characters. |
| if (name.empty() || name.length() > kFileNameMaxLength || |
| name[0] == kTemporaryFilePrefix) { |
| return false; |
| } |
| |
| for (const auto ch : name) { |
| if (!base::IsAsciiAlpha(ch) && !base::IsAsciiDigit(ch) && ch != '.' && |
| ch != '_' && ch != '-') { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| CdmFileImpl::CdmFileImpl( |
| const std::string& file_name, |
| const url::Origin& origin, |
| const std::string& file_system_id, |
| const std::string& file_system_root_uri, |
| scoped_refptr<storage::FileSystemContext> file_system_context) |
| : file_name_(file_name), |
| temp_file_name_(GetTempFileName(file_name_)), |
| origin_(origin), |
| file_system_id_(file_system_id), |
| file_system_root_uri_(file_system_root_uri), |
| file_system_context_(file_system_context) { |
| DVLOG(3) << __func__ << " " << file_name_; |
| DCHECK(IsValidName(file_name_)); |
| } |
| |
| CdmFileImpl::~CdmFileImpl() { |
| DVLOG(3) << __func__ << " " << file_name_; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (read_callback_) |
| std::move(read_callback_).Run(Status::kFailure, {}); |
| |
| if (write_callback_) |
| std::move(write_callback_).Run(Status::kFailure); |
| |
| if (file_locked_) |
| ReleaseFileLock(file_name_); |
| } |
| |
| bool CdmFileImpl::Initialize() { |
| DVLOG(3) << __func__ << " file: " << file_name_; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(!file_locked_); |
| |
| // Grab the lock on |file_name_|. The lock will be held until this object is |
| // destructed. |
| if (!AcquireFileLock(file_name_)) { |
| DVLOG(2) << "File " << file_name_ << " is already in use."; |
| return false; |
| } |
| |
| // We have the lock on |file_name_|. |file_locked_| is set to simplify |
| // validation, and to help destruction not have to check. |
| file_locked_ = true; |
| return true; |
| } |
| |
| void CdmFileImpl::Read(ReadCallback callback) { |
| DVLOG(3) << __func__ << " file: " << file_name_; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| |
| // Only 1 Read() or Write() is allowed at any time. |
| if (read_callback_ || write_callback_) { |
| std::move(callback).Run(Status::kFailure, {}); |
| return; |
| } |
| |
| // Save |callback| for later use. |
| read_callback_ = std::move(callback); |
| start_time_ = base::TimeTicks::Now(); |
| |
| // As reading is done on the IO thread, when it's done ReadDone() needs to be |
| // called back on this thread. |
| auto read_done_cb = media::BindToCurrentLoop( |
| base::BindOnce(&CdmFileImpl::ReadDone, weak_factory_.GetWeakPtr())); |
| |
| // Create the file reader that runs on the IO thread, and then call Read() on |
| // the IO thread. Use of base::Unretained() is OK as the reader is owned by |
| // |this|, and if |this| is destructed it will destroy the file reader on the |
| // IO thread. |
| file_reader_ = base::SequenceBound<FileReader>(GetIOThreadTaskRunner({})); |
| // TODO(dcheng): Migrate this to use Then()? |
| file_reader_.AsyncCall(&FileReader::Read) |
| .WithArgs(file_system_context_, CreateFileSystemURL(file_name_), |
| std::move(read_done_cb)); |
| } |
| |
| void CdmFileImpl::ReadDone(bool success, std::vector<uint8_t> data) { |
| DVLOG(3) << __func__ << " file: " << file_name_ |
| << ", success: " << (success ? "yes" : "no"); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| DCHECK(file_reader_); |
| DCHECK(read_callback_); |
| |
| // We are done with the reader, so destroy it. |
| file_reader_.Reset(); |
| |
| if (!success) { |
| // Unable to read the contents of the file. |
| std::move(read_callback_).Run(Status::kFailure, {}); |
| return; |
| } |
| |
| // Only report reading time for successful reads. |
| ReportFileOperationTimeUMA(kReadTimeUmaName); |
| std::move(read_callback_).Run(Status::kSuccess, std::move(data)); |
| } |
| |
| void CdmFileImpl::Write(const std::vector<uint8_t>& data, |
| WriteCallback callback) { |
| DVLOG(3) << __func__ << " file: " << file_name_ << ", size: " << data.size(); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| |
| // Only 1 Read() or Write() is allowed at any time. |
| if (read_callback_ || write_callback_) { |
| std::move(callback).Run(Status::kFailure); |
| return; |
| } |
| |
| // Files are limited in size, so fail if file too big. This should have been |
| // checked by the caller, but we don't fully trust IPC. |
| if (data.size() > kMaxFileSizeBytes) { |
| DLOG(WARNING) << __func__ |
| << " Too much data to write. #bytes = " << data.size(); |
| std::move(callback).Run(Status::kFailure); |
| return; |
| } |
| |
| // Save |callback| for later use. |
| write_callback_ = std::move(callback); |
| start_time_ = base::TimeTicks::Now(); |
| |
| // If there is no data to write, delete the file to save space. |
| // |write_callback_| will be called after the file is deleted. |
| if (data.empty()) { |
| DeleteFile(); |
| return; |
| } |
| |
| // Copy |data| into a net::IOBuffer. |
| int bytes_to_write = base::checked_cast<int>(data.size()); |
| auto buffer = base::MakeRefCounted<CdmFileIOBuffer>(data); |
| |
| // FileStreamWriter only works on existing files. |temp_file_name_| should not |
| // exist, so create an empty one if necessary. |
| // We can not use AsyncFileUtil::CreateOrOpen() as it does not work with the |
| // incognito filesystem (http://crbug.com/958294). |
| auto url = CreateFileSystemURL(temp_file_name_); |
| auto* file_util = file_system_context_->GetAsyncFileUtil( |
| storage::kFileSystemTypePluginPrivate); |
| auto operation_context = |
| std::make_unique<storage::FileSystemOperationContext>( |
| file_system_context_.get()); |
| operation_context->set_allowed_bytes_growth(storage::QuotaManager::kNoLimit); |
| file_util->EnsureFileExists( |
| std::move(operation_context), url, |
| base::BindOnce(&CdmFileImpl::OnEnsureTempFileExists, |
| weak_factory_.GetWeakPtr(), std::move(buffer), |
| bytes_to_write)); |
| } |
| |
| void CdmFileImpl::OnEnsureTempFileExists(scoped_refptr<net::IOBuffer> buffer, |
| int bytes_to_write, |
| base::File::Error result, |
| bool created) { |
| DVLOG(3) << __func__ << " file: " << temp_file_name_ |
| << ", result: " << base::File::ErrorToString(result); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| DCHECK(write_callback_); |
| DCHECK(!file_writer_); |
| |
| if (result != base::File::FILE_OK) { |
| // Unable to create the file. |
| DLOG(WARNING) << "Failed to create temporary file, result: " |
| << base::File::ErrorToString(result); |
| std::move(write_callback_).Run(Status::kFailure); |
| return; |
| } |
| |
| // If the temp file has just been created, we know it is empty and can simply |
| // proceed with writing to it. However, if the file exists, truncate it in |
| // case it is longer than the number of bytes we want to write. |
| if (created) { |
| OnTempFileIsEmpty(std::move(buffer), bytes_to_write, result); |
| return; |
| } |
| |
| auto url = CreateFileSystemURL(temp_file_name_); |
| auto* file_util = file_system_context_->GetAsyncFileUtil( |
| storage::kFileSystemTypePluginPrivate); |
| auto operation_context = |
| std::make_unique<storage::FileSystemOperationContext>( |
| file_system_context_.get()); |
| operation_context->set_allowed_bytes_growth(storage::QuotaManager::kNoLimit); |
| file_util->Truncate(std::move(operation_context), url, 0, |
| base::BindOnce(&CdmFileImpl::OnTempFileIsEmpty, |
| weak_factory_.GetWeakPtr(), |
| std::move(buffer), bytes_to_write)); |
| } |
| |
| void CdmFileImpl::OnTempFileIsEmpty(scoped_refptr<net::IOBuffer> buffer, |
| int bytes_to_write, |
| base::File::Error result) { |
| DVLOG(3) << __func__ << " file: " << temp_file_name_ |
| << ", result: " << base::File::ErrorToString(result); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| DCHECK(write_callback_); |
| DCHECK(!file_writer_); |
| |
| if (result != base::File::FILE_OK) { |
| DLOG(WARNING) << "Failed to truncate temporary file, result: " |
| << base::File::ErrorToString(result); |
| std::move(write_callback_).Run(Status::kFailure); |
| return; |
| } |
| |
| // As writing is done on the IO thread, when it's done WriteDone() needs to be |
| // called on this thread. |
| auto write_done_cb = media::BindToCurrentLoop( |
| base::BindOnce(&CdmFileImpl::WriteDone, weak_factory_.GetWeakPtr())); |
| |
| // Create the file writer that runs on the IO thread, and then call Write() |
| // on the IO thread to write |buffer| into the temporary file. Use of |
| // base::Unretained() is OK as |file_writer_| is owned by |this|, and if |
| // |this| is destructed it will destroy |file_writer_| on the IO thread. |
| file_writer_ = base::SequenceBound<FileWriter>(GetIOThreadTaskRunner({})); |
| // TODO(dcheng): Migrate this to use Then()? |
| file_writer_.AsyncCall(&FileWriter::Write) |
| .WithArgs(file_system_context_, CreateFileSystemURL(temp_file_name_), |
| std::move(buffer), bytes_to_write, std::move(write_done_cb)); |
| } |
| |
| void CdmFileImpl::WriteDone(bool success) { |
| DVLOG(3) << __func__ << " file: " << file_name_ |
| << ", success: " << (success ? "yes" : "no"); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| DCHECK(file_writer_); |
| DCHECK(write_callback_); |
| |
| // We are done with |file_writer_|. |
| file_writer_.Reset(); |
| |
| if (!success) { |
| std::move(write_callback_).Run(Status::kFailure); |
| return; |
| } |
| |
| // Now rename |temp_file_name_| to |file_name_|. |
| storage::FileSystemURL src_file_url = CreateFileSystemURL(temp_file_name_); |
| storage::FileSystemURL dest_file_url = CreateFileSystemURL(file_name_); |
| storage::AsyncFileUtil* file_util = file_system_context_->GetAsyncFileUtil( |
| storage::kFileSystemTypePluginPrivate); |
| auto operation_context = |
| std::make_unique<storage::FileSystemOperationContext>( |
| file_system_context_.get()); |
| DVLOG(3) << "Renaming " << src_file_url.DebugString() << " to " |
| << dest_file_url.DebugString(); |
| file_util->MoveFileLocal( |
| std::move(operation_context), src_file_url, dest_file_url, |
| storage::FileSystemOperation::OPTION_NONE, |
| base::BindOnce(&CdmFileImpl::OnFileRenamed, weak_factory_.GetWeakPtr())); |
| } |
| |
| void CdmFileImpl::OnFileRenamed(base::File::Error move_result) { |
| DVLOG(3) << __func__ << " file: " << file_name_ |
| << ", result: " << base::File::ErrorToString(move_result); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| DCHECK(!file_writer_); |
| DCHECK(write_callback_); |
| |
| // Was the rename successful? |
| if (move_result != base::File::FILE_OK) { |
| DLOG(WARNING) << "Unable to rename file " << temp_file_name_ << " to " |
| << file_name_ |
| << ", error: " << base::File::ErrorToString(move_result); |
| std::move(write_callback_).Run(Status::kFailure); |
| return; |
| } |
| |
| // Only report writing time for successful writes. |
| ReportFileOperationTimeUMA(kWriteTimeUmaName); |
| std::move(write_callback_).Run(Status::kSuccess); |
| } |
| |
| void CdmFileImpl::DeleteFile() { |
| DVLOG(3) << __func__; |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| DCHECK(!file_writer_); |
| DCHECK(write_callback_); |
| |
| storage::FileSystemURL file_url = CreateFileSystemURL(file_name_); |
| storage::AsyncFileUtil* file_util = file_system_context_->GetAsyncFileUtil( |
| storage::kFileSystemTypePluginPrivate); |
| auto operation_context = |
| std::make_unique<storage::FileSystemOperationContext>( |
| file_system_context_.get()); |
| |
| DVLOG(3) << "Deleting " << file_url.DebugString(); |
| file_util->DeleteFile( |
| std::move(operation_context), file_url, |
| base::BindOnce(&CdmFileImpl::OnFileDeleted, weak_factory_.GetWeakPtr())); |
| } |
| |
| void CdmFileImpl::OnFileDeleted(base::File::Error result) { |
| DVLOG(3) << __func__ << " file: " << file_name_ |
| << ", result: " << base::File::ErrorToString(result); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(file_locked_); |
| DCHECK(!file_writer_); |
| DCHECK(write_callback_); |
| |
| if (result != base::File::FILE_OK && |
| result != base::File::FILE_ERROR_NOT_FOUND) { |
| DLOG(WARNING) << "Unable to delete file " << file_name_ |
| << ", error: " << base::File::ErrorToString(result); |
| std::move(write_callback_).Run(Status::kFailure); |
| return; |
| } |
| |
| // Only report time for successful deletes. |
| ReportFileOperationTimeUMA(kDeleteTimeUmaName); |
| std::move(write_callback_).Run(Status::kSuccess); |
| } |
| |
| storage::FileSystemURL CdmFileImpl::CreateFileSystemURL( |
| const std::string& file_name) { |
| return file_system_context_->CrackURL( |
| GURL(file_system_root_uri_ + file_name)); |
| } |
| |
| bool CdmFileImpl::AcquireFileLock(const std::string& file_name) { |
| FileLockKey file_lock_key(file_system_id_, origin_, file_name); |
| return GetFileLockMap()->AcquireFileLock(file_lock_key); |
| } |
| |
| void CdmFileImpl::ReleaseFileLock(const std::string& file_name) { |
| FileLockKey file_lock_key(file_system_id_, origin_, file_name); |
| GetFileLockMap()->ReleaseFileLock(file_lock_key); |
| } |
| |
| void CdmFileImpl::ReportFileOperationTimeUMA(const std::string& uma_name) { |
| static const char kIncognito[] = ".Incognito"; |
| static const char kNormal[] = ".Normal"; |
| |
| // This records the time taken to the base histogram as well as splitting it |
| // out by incognito or normal mode. |
| auto time_taken = base::TimeTicks::Now() - start_time_; |
| base::UmaHistogramTimes(uma_name, time_taken); |
| base::UmaHistogramTimes( |
| base::StrCat({uma_name, file_system_context_->is_incognito() ? kIncognito |
| : kNormal}), |
| time_taken); |
| } |
| |
| } // namespace content |