blob: 190509d301bcf915cff6d721b51b1f15e8c2f727 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/utility/image_writer/image_writer.h"
#include <string.h>
#include "base/location.h"
#include "base/memory/aligned_memory.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/utility/image_writer/error_message_strings.h"
#include "chrome/utility/image_writer/image_writer_handler.h"
#include "content/public/utility/utility_thread.h"
#if BUILDFLAG(IS_MAC)
#include "chrome/utility/image_writer/disk_unmounter_mac.h"
#endif
namespace image_writer {
// Since block devices like large sequential access and IPC is expensive we're
// doing work in 1MB chunks.
const int kBurningBlockSize = 1 << 20; // 1 MB
const int kMemoryAlignment = 4096;
ImageWriter::ImageWriter(ImageWriterHandler* handler,
const base::FilePath& image_path,
const base::FilePath& device_path)
: image_path_(image_path),
device_path_(device_path),
bytes_processed_(0),
running_(false),
handler_(handler) {}
ImageWriter::~ImageWriter() {
#if BUILDFLAG(IS_WIN)
for (std::vector<HANDLE>::const_iterator it = volume_handles_.begin();
it != volume_handles_.end();
++it) {
CloseHandle(*it);
}
#endif
}
void ImageWriter::Write() {
if (!InitializeFiles()) {
return;
}
PostProgress(0);
PostTask(base::BindOnce(&ImageWriter::WriteChunk, AsWeakPtr()));
}
void ImageWriter::Verify() {
if (!InitializeFiles()) {
return;
}
PostProgress(0);
PostTask(base::BindOnce(&ImageWriter::VerifyChunk, AsWeakPtr()));
}
void ImageWriter::Cancel() {
running_ = false;
handler_->SendCancelled();
}
bool ImageWriter::IsRunning() const { return running_; }
const base::FilePath& ImageWriter::GetImagePath() { return image_path_; }
const base::FilePath& ImageWriter::GetDevicePath() { return device_path_; }
void ImageWriter::PostTask(base::OnceClosure task) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(task));
}
void ImageWriter::PostProgress(int64_t progress) {
handler_->SendProgress(progress);
}
void ImageWriter::Error(const std::string& message) {
running_ = false;
handler_->SendFailed(message);
}
bool ImageWriter::InitializeFiles() {
if (!image_file_.IsValid()) {
image_file_.Initialize(image_path_,
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WIN_EXCLUSIVE_READ);
if (!image_file_.IsValid()) {
DLOG(ERROR) << "Unable to open file for read: " << image_path_.value();
Error(error::kOpenImage);
return false;
}
}
if (!device_file_.IsValid()) {
if (!OpenDevice()) {
Error(error::kOpenDevice);
return false;
}
}
bytes_processed_ = 0;
running_ = true;
return true;
}
void ImageWriter::WriteChunk() {
if (!IsRunning()) {
return;
}
// DASD buffers require memory alignment on some systems.
std::unique_ptr<char, base::AlignedFreeDeleter> buffer(static_cast<char*>(
base::AlignedAlloc(kBurningBlockSize, kMemoryAlignment)));
memset(buffer.get(), 0, kBurningBlockSize);
int bytes_read = image_file_.Read(bytes_processed_, buffer.get(),
kBurningBlockSize);
if (bytes_read > 0) {
// Always attempt to write a whole block, as writing DASD requires sector-
// aligned writes to devices.
int bytes_to_write = bytes_read + (kMemoryAlignment - 1) -
(bytes_read - 1) % kMemoryAlignment;
DCHECK_EQ(0, bytes_to_write % kMemoryAlignment);
int bytes_written =
device_file_.Write(bytes_processed_, buffer.get(), bytes_to_write);
if (bytes_written < bytes_read) {
Error(error::kWriteImage);
return;
}
bytes_processed_ += bytes_read;
PostProgress(bytes_processed_);
PostTask(base::BindOnce(&ImageWriter::WriteChunk, AsWeakPtr()));
} else if (bytes_read == 0) {
// End of file.
device_file_.Flush();
running_ = false;
handler_->SendSucceeded();
} else {
// Unable to read entire file.
Error(error::kReadImage);
}
}
void ImageWriter::VerifyChunk() {
if (!IsRunning()) {
return;
}
std::unique_ptr<char[]> image_buffer(new char[kBurningBlockSize]);
// DASD buffers require memory alignment on some systems.
std::unique_ptr<char, base::AlignedFreeDeleter> device_buffer(
static_cast<char*>(
base::AlignedAlloc(kBurningBlockSize, kMemoryAlignment)));
int bytes_read = image_file_.Read(bytes_processed_, image_buffer.get(),
kBurningBlockSize);
if (bytes_read > 0) {
if (device_file_.Read(bytes_processed_,
device_buffer.get(),
kBurningBlockSize) < bytes_read) {
LOG(ERROR) << "Failed to read " << bytes_read << " bytes of "
<< "device at offset " << bytes_processed_;
Error(error::kReadDevice);
return;
}
if (memcmp(image_buffer.get(), device_buffer.get(), bytes_read) != 0) {
LOG(ERROR) << "Write verification failed when comparing " << bytes_read
<< " bytes at " << bytes_processed_;
Error(error::kVerificationFailed);
return;
}
bytes_processed_ += bytes_read;
PostProgress(bytes_processed_);
PostTask(base::BindOnce(&ImageWriter::VerifyChunk, AsWeakPtr()));
} else if (bytes_read == 0) {
// End of file.
handler_->SendSucceeded();
running_ = false;
} else {
// Unable to read entire file.
LOG(ERROR) << "Failed to read " << kBurningBlockSize << " bytes of image "
<< "at offset " << bytes_processed_;
Error(error::kReadImage);
}
}
} // namespace image_writer