| // | 
 | // Copyright (C) 2016 The Android Open Source Project | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //      http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 | // | 
 |  | 
 | #include "update_engine/common/file_fetcher.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <string> | 
 |  | 
 | #include <base/bind.h> | 
 | #include <base/format_macros.h> | 
 | #include <base/location.h> | 
 | #include <base/logging.h> | 
 | #include <base/strings/string_util.h> | 
 | #include <base/strings/stringprintf.h> | 
 | #include <brillo/streams/file_stream.h> | 
 |  | 
 | #include "update_engine/common/hardware_interface.h" | 
 | #include "update_engine/common/platform_constants.h" | 
 |  | 
 | using std::string; | 
 |  | 
 | namespace { | 
 |  | 
 | size_t kReadBufferSize = 16 * 1024; | 
 |  | 
 | }  // namespace | 
 |  | 
 | namespace chromeos_update_engine { | 
 |  | 
 | // static | 
 | bool FileFetcher::SupportedUrl(const string& url) { | 
 |   // Note that we require the file path to start with a "/". | 
 |   return ( | 
 |       base::StartsWith(url, "file:///", base::CompareCase::INSENSITIVE_ASCII) || | 
 |       base::StartsWith(url, "fd://", base::CompareCase::INSENSITIVE_ASCII)); | 
 | } | 
 |  | 
 | FileFetcher::~FileFetcher() { | 
 |   LOG_IF(ERROR, transfer_in_progress_) | 
 |       << "Destroying the fetcher while a transfer is in progress."; | 
 |   CleanUp(); | 
 | } | 
 |  | 
 | // Begins the transfer, which must not have already been started. | 
 | void FileFetcher::BeginTransfer(const string& url) { | 
 |   CHECK(!transfer_in_progress_); | 
 |  | 
 |   if (!SupportedUrl(url)) { | 
 |     LOG(ERROR) << "Unsupported file URL: " << url; | 
 |     // No HTTP error code when the URL is not supported. | 
 |     http_response_code_ = 0; | 
 |     CleanUp(); | 
 |     if (delegate_) | 
 |       delegate_->TransferComplete(this, false); | 
 |     return; | 
 |   } | 
 |  | 
 |   string file_path; | 
 |  | 
 |   if (base::StartsWith(url, "fd://", base::CompareCase::INSENSITIVE_ASCII)) { | 
 |     int fd = std::stoi(url.substr(strlen("fd://"))); | 
 |     file_path = url; | 
 |     stream_ = brillo::FileStream::FromFileDescriptor(fd, false, nullptr); | 
 |   } else { | 
 |     file_path = url.substr(strlen("file://")); | 
 |     stream_ = | 
 |         brillo::FileStream::Open(base::FilePath(file_path), | 
 |                                  brillo::Stream::AccessMode::READ, | 
 |                                  brillo::FileStream::Disposition::OPEN_EXISTING, | 
 |                                  nullptr); | 
 |   } | 
 |  | 
 |   if (!stream_) { | 
 |     LOG(ERROR) << "Couldn't open " << file_path; | 
 |     http_response_code_ = kHttpResponseNotFound; | 
 |     CleanUp(); | 
 |     if (delegate_) | 
 |       delegate_->TransferComplete(this, false); | 
 |     return; | 
 |   } | 
 |   http_response_code_ = kHttpResponseOk; | 
 |  | 
 |   if (offset_) | 
 |     stream_->SetPosition(offset_, nullptr); | 
 |   bytes_copied_ = 0; | 
 |   transfer_in_progress_ = true; | 
 |   ScheduleRead(); | 
 | } | 
 |  | 
 | void FileFetcher::TerminateTransfer() { | 
 |   CleanUp(); | 
 |   if (delegate_) { | 
 |     // Note that after the callback returns this object may be destroyed. | 
 |     delegate_->TransferTerminated(this); | 
 |   } | 
 | } | 
 |  | 
 | void FileFetcher::ScheduleRead() { | 
 |   if (transfer_paused_ || ongoing_read_ || !transfer_in_progress_) | 
 |     return; | 
 |  | 
 |   buffer_.resize(kReadBufferSize); | 
 |   size_t bytes_to_read = buffer_.size(); | 
 |   if (data_length_ >= 0) { | 
 |     bytes_to_read = std::min(static_cast<uint64_t>(bytes_to_read), | 
 |                              data_length_ - bytes_copied_); | 
 |   } | 
 |  | 
 |   if (!bytes_to_read) { | 
 |     OnReadDoneCallback(0); | 
 |     return; | 
 |   } | 
 |  | 
 |   ongoing_read_ = stream_->ReadAsync( | 
 |       buffer_.data(), | 
 |       bytes_to_read, | 
 |       base::Bind(&FileFetcher::OnReadDoneCallback, base::Unretained(this)), | 
 |       base::Bind(&FileFetcher::OnReadErrorCallback, base::Unretained(this)), | 
 |       nullptr); | 
 |  | 
 |   if (!ongoing_read_) { | 
 |     LOG(ERROR) << "Unable to schedule an asynchronous read from the stream."; | 
 |     CleanUp(); | 
 |     if (delegate_) | 
 |       delegate_->TransferComplete(this, false); | 
 |   } | 
 | } | 
 |  | 
 | void FileFetcher::OnReadDoneCallback(size_t bytes_read) { | 
 |   ongoing_read_ = false; | 
 |   if (bytes_read == 0) { | 
 |     CleanUp(); | 
 |     if (delegate_) | 
 |       delegate_->TransferComplete(this, true); | 
 |   } else { | 
 |     bytes_copied_ += bytes_read; | 
 |     if (delegate_ && | 
 |         !delegate_->ReceivedBytes(this, buffer_.data(), bytes_read)) | 
 |       return; | 
 |     ScheduleRead(); | 
 |   } | 
 | } | 
 |  | 
 | void FileFetcher::OnReadErrorCallback(const brillo::Error* error) { | 
 |   LOG(ERROR) << "Asynchronous read failed: " << error->GetMessage(); | 
 |   CleanUp(); | 
 |   if (delegate_) | 
 |     delegate_->TransferComplete(this, false); | 
 | } | 
 |  | 
 | void FileFetcher::Pause() { | 
 |   if (transfer_paused_) { | 
 |     LOG(ERROR) << "Fetcher already paused."; | 
 |     return; | 
 |   } | 
 |   transfer_paused_ = true; | 
 | } | 
 |  | 
 | void FileFetcher::Unpause() { | 
 |   if (!transfer_paused_) { | 
 |     LOG(ERROR) << "Resume attempted when fetcher not paused."; | 
 |     return; | 
 |   } | 
 |   transfer_paused_ = false; | 
 |   ScheduleRead(); | 
 | } | 
 |  | 
 | void FileFetcher::CleanUp() { | 
 |   if (stream_) { | 
 |     stream_->CancelPendingAsyncOperations(); | 
 |     stream_->CloseBlocking(nullptr); | 
 |     stream_.reset(); | 
 |   } | 
 |   // Destroying the |stream_| releases the callback, so we don't have any | 
 |   // ongoing read at this point. | 
 |   ongoing_read_ = false; | 
 |   buffer_ = brillo::Blob(); | 
 |  | 
 |   transfer_in_progress_ = false; | 
 |   transfer_paused_ = false; | 
 | } | 
 | }  // namespace chromeos_update_engine |