summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation.cc8
-rw-r--r--chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc46
-rw-r--r--chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc158
-rw-r--r--chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h87
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation.cc73
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation.h36
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation_linux.cc195
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation_mac.cc20
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation_manager_unittest.cc28
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation_nonchromeos.cc76
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation_unittest.cc118
-rw-r--r--chrome/browser/extensions/api/image_writer_private/operation_win.cc20
-rw-r--r--chrome/browser/extensions/api/image_writer_private/test_utils.cc74
-rw-r--r--chrome/browser/extensions/api/image_writer_private/test_utils.h46
-rw-r--r--chrome/browser/extensions/api/image_writer_private/write_from_file_operation.cc18
-rw-r--r--chrome/browser/extensions/api/image_writer_private/write_from_file_operation_unittest.cc26
-rw-r--r--chrome/chrome.gyp6
-rw-r--r--chrome/chrome_browser_extensions.gypi8
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/common/chrome_utility_messages.h26
-rw-r--r--chrome/utility/chrome_content_utility_client.cc3
-rw-r--r--chrome/utility/chrome_content_utility_ipc_whitelist.cc5
-rw-r--r--chrome/utility/image_writer/OWNERS2
-rw-r--r--chrome/utility/image_writer/error_messages.cc24
-rw-r--r--chrome/utility/image_writer/error_messages.h27
-rw-r--r--chrome/utility/image_writer/image_writer.cc244
-rw-r--r--chrome/utility/image_writer/image_writer.h63
-rw-r--r--chrome/utility/image_writer/image_writer_handler.cc151
-rw-r--r--chrome/utility/image_writer/image_writer_handler.h51
-rw-r--r--chrome/utility/image_writer/image_writer_unittest.cc182
30 files changed, 1453 insertions, 369 deletions
diff --git a/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation.cc b/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation.cc
index 94b26757..9c1871b 100644
--- a/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation.cc
+++ b/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation.cc
@@ -5,6 +5,7 @@
#include "base/file_util.h"
#include "chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation.h"
#include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
+#include "content/public/browser/browser_thread.h"
namespace extensions {
namespace image_writer {
@@ -37,7 +38,12 @@ void DestroyPartitionsOperation::StartImpl() {
return;
}
- Write(base::Bind(&DestroyPartitionsOperation::Finish, this));
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&DestroyPartitionsOperation::Write,
+ this,
+ base::Bind(&DestroyPartitionsOperation::Finish, this)));
}
} // namespace image_writer
diff --git a/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc b/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc
index 68a669a..4621285 100644
--- a/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc
+++ b/chrome/browser/extensions/api/image_writer_private/destroy_partitions_operation_unittest.cc
@@ -9,29 +9,32 @@
namespace extensions {
namespace image_writer {
+namespace {
using testing::_;
using testing::AnyNumber;
using testing::AtLeast;
-namespace {
-
class ImageWriterDestroyPartitionsOperationTest
- : public ImageWriterUnitTestBase {
-};
+ : public ImageWriterUnitTestBase {};
-// Tests that the DestroyPartitionsOperation can successfully zero the first
-// kPartitionTableSize bytes of an image.
-TEST_F(ImageWriterDestroyPartitionsOperationTest, DestroyPartitionsEndToEnd) {
+TEST_F(ImageWriterDestroyPartitionsOperationTest, EndToEnd) {
MockOperationManager manager;
- base::RunLoop loop;
+ scoped_refptr<FakeImageWriterClient> client = FakeImageWriterClient::Create();
scoped_refptr<DestroyPartitionsOperation> operation(
new DestroyPartitionsOperation(manager.AsWeakPtr(),
kDummyExtensionId,
test_device_path_.AsUTF8Unsafe()));
-#if defined(OS_LINUX) || defined(OS_CHROMEOS)
+#if !defined(OS_CHROMEOS)
+ operation->SetUtilityClientForTesting(client);
+#endif
+
+ EXPECT_CALL(
+ manager,
+ OnProgress(kDummyExtensionId, image_writer_api::STAGE_VERIFYWRITE, _))
+ .Times(AnyNumber());
EXPECT_CALL(manager, OnProgress(kDummyExtensionId,
image_writer_api::STAGE_WRITE,
_)).Times(AnyNumber());
@@ -43,27 +46,18 @@ TEST_F(ImageWriterDestroyPartitionsOperationTest, DestroyPartitionsEndToEnd) {
.Times(AtLeast(1));
EXPECT_CALL(manager, OnComplete(kDummyExtensionId)).Times(1);
EXPECT_CALL(manager, OnError(kDummyExtensionId, _, _, _)).Times(0);
-#else
- EXPECT_CALL(manager, OnProgress(kDummyExtensionId, _, _)).Times(0);
- EXPECT_CALL(manager, OnComplete(kDummyExtensionId)).Times(0);
- EXPECT_CALL(manager, OnError(kDummyExtensionId,
- _,
- _,
- error::kUnsupportedOperation)).Times(1);
-#endif
operation->Start();
- loop.RunUntilIdle();
+ base::RunLoop().RunUntilIdle();
+
+#if !defined(OS_CHROMEOS)
+ client->Progress(0);
+ client->Progress(50);
+ client->Progress(100);
+ client->Success();
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
- scoped_ptr<char[]> image_data(new char[kPartitionTableSize]);
- scoped_ptr<char[]> zeroes(new char[kPartitionTableSize]);
- memset(zeroes.get(), 0, kPartitionTableSize);
- ASSERT_EQ(kPartitionTableSize, base::ReadFile(test_device_path_,
- image_data.get(),
- kPartitionTableSize));
- EXPECT_EQ(0, memcmp(image_data.get(), zeroes.get(), kPartitionTableSize));
+ base::RunLoop().RunUntilIdle();
#endif
}
diff --git a/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc b/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc
new file mode 100644
index 0000000..deff3ab
--- /dev/null
+++ b/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.cc
@@ -0,0 +1,158 @@
+// Copyright 2014 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 "base/bind.h"
+#include "base/message_loop/message_loop.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h"
+#include "chrome/common/chrome_utility_messages.h"
+#include "content/public/browser/browser_thread.h"
+
+using content::BrowserThread;
+
+ImageWriterUtilityClient::ImageWriterUtilityClient()
+ : message_loop_proxy_(base::MessageLoopProxy::current()) {}
+ImageWriterUtilityClient::~ImageWriterUtilityClient() {}
+
+void ImageWriterUtilityClient::Write(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ StartHost();
+
+ progress_callback_ = progress_callback;
+ success_callback_ = success_callback;
+ error_callback_ = error_callback;
+
+ if (!Send(new ChromeUtilityMsg_ImageWriter_Write(source, target))) {
+ DLOG(ERROR) << "Unable to send Write message to Utility Process.";
+ message_loop_proxy_->PostTask(
+ FROM_HERE, base::Bind(error_callback_, "IPC communication failed"));
+ }
+}
+
+void ImageWriterUtilityClient::Verify(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ StartHost();
+
+ progress_callback_ = progress_callback;
+ success_callback_ = success_callback;
+ error_callback_ = error_callback;
+
+ if (!Send(new ChromeUtilityMsg_ImageWriter_Verify(source, target))) {
+ DLOG(ERROR) << "Unable to send Verify message to Utility Process.";
+ message_loop_proxy_->PostTask(
+ FROM_HERE, base::Bind(error_callback_, "IPC communication failed"));
+ }
+}
+
+void ImageWriterUtilityClient::Cancel(const CancelCallback& cancel_callback) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+
+ if (!utility_process_host_) {
+ // If we haven't connected, there is nothing to cancel.
+ message_loop_proxy_->PostTask(FROM_HERE, cancel_callback);
+ return;
+ }
+
+ cancel_callback_ = cancel_callback;
+
+ if (!Send(new ChromeUtilityMsg_ImageWriter_Cancel())) {
+ DLOG(ERROR) << "Unable to send Cancel message to Utility Process.";
+ }
+}
+
+void ImageWriterUtilityClient::Shutdown() {
+ if (utility_process_host_ &&
+ Send(new ChromeUtilityMsg_ImageWriter_Cancel())) {
+ utility_process_host_->EndBatchMode();
+ }
+
+ // Clear handlers to not hold any reference to the caller.
+ success_callback_ = base::Closure();
+ progress_callback_ = base::Callback<void(int64)>();
+ error_callback_ = base::Callback<void(const std::string&)>();
+ cancel_callback_ = base::Closure();
+}
+
+void ImageWriterUtilityClient::StartHost() {
+ if (!utility_process_host_) {
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ base::MessageLoop::current()->message_loop_proxy();
+ utility_process_host_ = content::UtilityProcessHost::Create(
+ this, task_runner.get())->AsWeakPtr();
+
+#if defined(OS_WIN)
+ utility_process_host_->ElevatePrivileges();
+#else
+ utility_process_host_->DisableSandbox();
+#endif
+ utility_process_host_->StartBatchMode();
+ utility_process_host_->DisableSandbox();
+ }
+}
+
+void ImageWriterUtilityClient::OnProcessCrashed(int exit_code) {
+ message_loop_proxy_->PostTask(
+ FROM_HERE, base::Bind(error_callback_, "Utility process crashed."));
+}
+
+void ImageWriterUtilityClient::OnProcessLaunchFailed() {
+ message_loop_proxy_->PostTask(
+ FROM_HERE, base::Bind(error_callback_, "Process launch failed."));
+}
+
+bool ImageWriterUtilityClient::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ImageWriterUtilityClient, message)
+ IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ImageWriter_Succeeded,
+ OnWriteImageSucceeded)
+ IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ImageWriter_Cancelled,
+ OnWriteImageCancelled)
+ IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ImageWriter_Failed,
+ OnWriteImageFailed)
+ IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ImageWriter_Progress,
+ OnWriteImageProgress)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+bool ImageWriterUtilityClient::Send(IPC::Message* msg) {
+ return utility_process_host_ && utility_process_host_->Send(msg);
+}
+
+void ImageWriterUtilityClient::OnWriteImageSucceeded() {
+ if (!success_callback_.is_null()) {
+ message_loop_proxy_->PostTask(FROM_HERE, success_callback_);
+ }
+}
+
+void ImageWriterUtilityClient::OnWriteImageCancelled() {
+ if (!cancel_callback_.is_null()) {
+ message_loop_proxy_->PostTask(FROM_HERE, cancel_callback_);
+ }
+}
+
+void ImageWriterUtilityClient::OnWriteImageFailed(const std::string& message) {
+ if (!error_callback_.is_null()) {
+ message_loop_proxy_->PostTask(FROM_HERE,
+ base::Bind(error_callback_, message));
+ }
+}
+
+void ImageWriterUtilityClient::OnWriteImageProgress(int64 progress) {
+ if (!progress_callback_.is_null()) {
+ message_loop_proxy_->PostTask(FROM_HERE,
+ base::Bind(progress_callback_, progress));
+ }
+}
diff --git a/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h b/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h
new file mode 100644
index 0000000..81408ff
--- /dev/null
+++ b/chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h
@@ -0,0 +1,87 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_IMAGE_WRITER_PRIVATE_IMAGE_WRITER_UTILITY_CLIENT_H_
+#define CHROME_BROWSER_EXTENSIONS_API_IMAGE_WRITER_PRIVATE_IMAGE_WRITER_UTILITY_CLIENT_H_
+
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "content/public/browser/utility_process_host.h"
+#include "content/public/browser/utility_process_host_client.h"
+
+// Writes a disk image to a device inside the utility process.
+class ImageWriterUtilityClient : public content::UtilityProcessHostClient {
+ public:
+ typedef base::Callback<void()> CancelCallback;
+ typedef base::Callback<void()> SuccessCallback;
+ typedef base::Callback<void(int64)> ProgressCallback;
+ typedef base::Callback<void(const std::string&)> ErrorCallback;
+
+ ImageWriterUtilityClient();
+
+ // Starts the write.
+ // |progress_callback|: Called periodically with the count of bytes processed.
+ // |success_callback|: Called at successful completion.
+ // |error_callback|: Called with an error message on failure.
+ // |source|: The path to the source file to read data from.
+ // |target|: The path to the target device to write the image file to.
+ virtual void Write(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target);
+
+ // Starts a verification.
+ // |progress_callback|: Called periodically with the count of bytes processed.
+ // |success_callback|: Called at successful completion.
+ // |error_callback|: Called with an error message on failure.
+ // |source|: The path to the source file to read data from.
+ // |target|: The path to the target device to write the image file to.
+ virtual void Verify(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target);
+
+ // Cancels any pending write or verification.
+ // |cancel_callback|: Called when the cancel has actually occurred.
+ virtual void Cancel(const CancelCallback& cancel_callback);
+
+ // Shuts down the Utility thread that may have been created.
+ virtual void Shutdown();
+
+ protected:
+ // It's a reference-counted object, so destructor is not public.
+ virtual ~ImageWriterUtilityClient();
+
+ private:
+ // Ensures the UtilityProcessHost has been created.
+ void StartHost();
+
+ // UtilityProcessHostClient implementation.
+ virtual void OnProcessCrashed(int exit_code) OVERRIDE;
+ virtual void OnProcessLaunchFailed() OVERRIDE;
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+ virtual bool Send(IPC::Message* msg);
+
+ // IPC message handlers.
+ void OnWriteImageSucceeded();
+ void OnWriteImageCancelled();
+ void OnWriteImageFailed(const std::string& message);
+ void OnWriteImageProgress(int64 progress);
+
+ CancelCallback cancel_callback_;
+ ProgressCallback progress_callback_;
+ SuccessCallback success_callback_;
+ ErrorCallback error_callback_;
+
+ base::WeakPtr<content::UtilityProcessHost> utility_process_host_;
+
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ImageWriterUtilityClient);
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_IMAGE_WRITER_PRIVATE_IMAGE_WRITER_UTILITY_CLIENT_H_
diff --git a/chrome/browser/extensions/api/image_writer_private/operation.cc b/chrome/browser/extensions/api/image_writer_private/operation.cc
index e58ca43..944febc 100644
--- a/chrome/browser/extensions/api/image_writer_private/operation.cc
+++ b/chrome/browser/extensions/api/image_writer_private/operation.cc
@@ -33,10 +33,6 @@ Operation::Operation(base::WeakPtr<OperationManager> manager,
#else
device_path_(device_path),
#endif
-#if defined(OS_LINUX) && !defined(CHROMEOS)
- image_file_(base::kInvalidPlatformFileValue),
- device_file_(base::kInvalidPlatformFileValue),
-#endif
stage_(image_writer_api::STAGE_UNKNOWN),
progress_(0) {
}
@@ -63,6 +59,13 @@ image_writer_api::Stage Operation::GetStage() {
return stage_;
}
+#if !defined(OS_CHROMEOS)
+void Operation::SetUtilityClientForTesting(
+ scoped_refptr<ImageWriterUtilityClient> client) {
+ image_writer_client_ = client;
+}
+#endif
+
void Operation::Start() {
#if defined(OS_CHROMEOS)
if (!temp_dir_.CreateUniqueTempDirUnderPath(
@@ -117,7 +120,7 @@ void Operation::Unzip(const base::Closure& continuation) {
zip_reader_.ExtractCurrentEntryToFilePathAsync(
image_path_,
- base::Bind(&Operation::OnUnzipSuccess, this, continuation),
+ base::Bind(&Operation::CompleteAndContinue, this, continuation),
base::Bind(&Operation::OnUnzipFailure, this),
base::Bind(&Operation::OnUnzipProgress,
this,
@@ -181,14 +184,13 @@ void Operation::SetProgress(int progress) {
progress_ = progress;
- BrowserThread::PostTask(
- BrowserThread::UI,
- FROM_HERE,
- base::Bind(&OperationManager::OnProgress,
- manager_,
- extension_id_,
- stage_,
- progress_));
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ base::Bind(&OperationManager::OnProgress,
+ manager_,
+ extension_id_,
+ stage_,
+ progress_));
}
void Operation::SetStage(image_writer_api::Stage stage) {
@@ -230,6 +232,43 @@ void Operation::AddCleanUpFunction(const base::Closure& callback) {
cleanup_functions_.push_back(callback);
}
+void Operation::CompleteAndContinue(const base::Closure& continuation) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ SetProgress(kProgressComplete);
+ BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation);
+}
+
+#if !defined(OS_CHROMEOS)
+void Operation::StartUtilityClient() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (!image_writer_client_) {
+ image_writer_client_ = new ImageWriterUtilityClient();
+ AddCleanUpFunction(base::Bind(&Operation::StopUtilityClient, this));
+ }
+}
+
+void Operation::StopUtilityClient() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&ImageWriterUtilityClient::Shutdown, image_writer_client_));
+}
+
+void Operation::WriteImageProgress(int64 total_bytes, int64 curr_bytes) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (IsCancelled()) {
+ return;
+ }
+
+ int progress = kProgressComplete * curr_bytes / total_bytes;
+
+ if (progress > GetProgress()) {
+ SetProgress(progress);
+ }
+}
+#endif
+
void Operation::GetMD5SumOfFile(
const base::FilePath& file_path,
int64 file_size,
@@ -326,12 +365,6 @@ void Operation::MD5Chunk(
base::ClosePlatformFile(file);
}
-void Operation::OnUnzipSuccess(const base::Closure& continuation) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
- SetProgress(kProgressComplete);
- BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation);
-}
-
void Operation::OnUnzipFailure() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
Error(error::kUnzipGenericError);
@@ -340,7 +373,7 @@ void Operation::OnUnzipFailure() {
void Operation::OnUnzipProgress(int64 total_bytes, int64 progress_bytes) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
- int progress_percent = 100 * progress_bytes / total_bytes;
+ int progress_percent = kProgressComplete * progress_bytes / total_bytes;
SetProgress(progress_percent);
}
diff --git a/chrome/browser/extensions/api/image_writer_private/operation.h b/chrome/browser/extensions/api/image_writer_private/operation.h
index 2a37d41..1c9933f 100644
--- a/chrome/browser/extensions/api/image_writer_private/operation.h
+++ b/chrome/browser/extensions/api/image_writer_private/operation.h
@@ -12,6 +12,7 @@
#include "base/memory/weak_ptr.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/timer/timer.h"
+#include "chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h"
#include "chrome/common/extensions/api/image_writer_private.h"
#include "third_party/zlib/google/zip_reader.h"
@@ -70,6 +71,12 @@ class Operation : public base::RefCountedThreadSafe<Operation> {
int GetProgress();
image_writer_api::Stage GetStage();
+#if !defined(OS_CHROMEOS)
+ // Set an ImageWriterClient to use. Should be called only when testing.
+ void SetUtilityClientForTesting(
+ scoped_refptr<ImageWriterUtilityClient> client);
+#endif
+
protected:
virtual ~Operation();
@@ -110,6 +117,10 @@ class Operation : public base::RefCountedThreadSafe<Operation> {
// functions will be run on the FILE thread.
void AddCleanUpFunction(const base::Closure& callback);
+ // Completes the current operation (progress set to 100) and runs the
+ // continuation.
+ void CompleteAndContinue(const base::Closure& continuation);
+
// If |file_size| is non-zero, only |file_size| bytes will be read from file,
// otherwise the entire file will be read.
// |progress_scale| is a percentage to which the progress will be scale, e.g.
@@ -135,20 +146,20 @@ class Operation : public base::RefCountedThreadSafe<Operation> {
private:
friend class base::RefCountedThreadSafe<Operation>;
- // TODO(haven): Clean up these switches. http://crbug.com/292956
-#if defined(OS_LINUX) && !defined(CHROMEOS)
- void WriteChunk(const int64& bytes_written,
- const int64& total_size,
- const base::Closure& continuation);
- void WriteComplete(const base::Closure& continuation);
+#if !defined(OS_CHROMEOS)
+ // Ensures the client is started. This may be called many times but will only
+ // instantiate one client which should exist for the lifetime of the
+ // Operation.
+ void StartUtilityClient();
+
+ // Stops the client. This must be called to ensure the utility process can
+ // shutdown.
+ void StopUtilityClient();
- void VerifyWriteChunk(const int64& bytes_written,
- const int64& total_size,
- const base::Closure& continuation);
- void VerifyWriteComplete(const base::Closure& continuation);
+ // Reports progress from the client, transforming from bytes to percentage.
+ virtual void WriteImageProgress(int64 total_bytes, int64 curr_bytes);
- base::PlatformFile image_file_;
- base::PlatformFile device_file_;
+ scoped_refptr<ImageWriterUtilityClient> image_writer_client_;
#endif
#if defined(OS_CHROMEOS)
@@ -173,7 +184,6 @@ class Operation : public base::RefCountedThreadSafe<Operation> {
const base::Callback<void(const std::string&)>& callback);
// Callbacks for zip::ZipReader.
- void OnUnzipSuccess(const base::Closure& continuation);
void OnUnzipFailure();
void OnUnzipProgress(int64 total_bytes, int64 progress_bytes);
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_linux.cc b/chrome/browser/extensions/api/image_writer_private/operation_linux.cc
deleted file mode 100644
index efba631..0000000
--- a/chrome/browser/extensions/api/image_writer_private/operation_linux.cc
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2013 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 "base/file_util.h"
-#include "base/files/file_enumerator.h"
-#include "base/threading/worker_pool.h"
-#include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
-#include "chrome/browser/extensions/api/image_writer_private/operation.h"
-#include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
-#include "content/public/browser/browser_thread.h"
-#include "third_party/zlib/google/zip.h"
-
-namespace extensions {
-namespace image_writer {
-
-using content::BrowserThread;
-
-const int kBurningBlockSize = 8 * 1024; // 8 KiB
-
-void Operation::Write(const base::Closure& continuation) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
- if (IsCancelled()) {
- return;
- }
-
- SetStage(image_writer_api::STAGE_WRITE);
-
- // TODO (haven): Unmount partitions before writing. http://crbug.com/284834
-
- base::PlatformFileError result;
- image_file_ = base::CreatePlatformFile(
- image_path_,
- base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
- NULL,
- &result);
- if (result != base::PLATFORM_FILE_OK) {
- Error(error::kImageOpenError);
- return;
- }
-
- device_file_ = base::CreatePlatformFile(
- device_path_,
- base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE,
- NULL,
- &result);
- if (result != base::PLATFORM_FILE_OK) {
- Error(error::kDeviceOpenError);
- base::ClosePlatformFile(image_file_);
- return;
- }
-
- int64 total_size;
- base::GetFileSize(image_path_, &total_size);
-
- BrowserThread::PostTask(
- BrowserThread::FILE,
- FROM_HERE,
- base::Bind(&Operation::WriteChunk, this, 0, total_size, continuation));
-}
-
-void Operation::WriteChunk(const int64& bytes_written,
- const int64& total_size,
- const base::Closure& continuation) {
- if (!IsCancelled()) {
- scoped_ptr<char[]> buffer(new char[kBurningBlockSize]);
- int64 len = base::ReadPlatformFile(
- image_file_, bytes_written, buffer.get(), kBurningBlockSize);
-
- if (len > 0) {
- if (base::WritePlatformFile(
- device_file_, bytes_written, buffer.get(), len) == len) {
- int percent_curr =
- kProgressComplete * (bytes_written + len) / total_size;
-
- SetProgress(percent_curr);
-
- BrowserThread::PostTask(BrowserThread::FILE,
- FROM_HERE,
- base::Bind(&Operation::WriteChunk,
- this,
- bytes_written + len,
- total_size,
- continuation));
- return;
- } else {
- Error(error::kDeviceWriteError);
- }
- } else if (len == 0) {
- WriteComplete(continuation);
- } else { // len < 0
- Error(error::kImageReadError);
- }
- }
-
- base::ClosePlatformFile(image_file_);
- base::ClosePlatformFile(device_file_);
-}
-
-void Operation::WriteComplete(const base::Closure& continuation) {
- SetProgress(kProgressComplete);
- BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation);
-}
-
-void Operation::VerifyWrite(const base::Closure& continuation) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
- if (IsCancelled()) {
- return;
- }
-
- SetStage(image_writer_api::STAGE_VERIFYWRITE);
-
- base::PlatformFileError result;
-
- image_file_ = base::CreatePlatformFile(
- image_path_,
- base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
- NULL,
- &result);
- if (result != base::PLATFORM_FILE_OK) {
- Error(error::kImageOpenError);
- return;
- }
-
- device_file_ = base::CreatePlatformFile(
- device_path_,
- base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
- NULL,
- &result);
- if (result != base::PLATFORM_FILE_OK) {
- Error(error::kDeviceOpenError);
- base::ClosePlatformFile(image_file_);
- return;
- }
-
- int64 total_size;
- base::GetFileSize(image_path_, &total_size);
-
- BrowserThread::PostTask(
- BrowserThread::FILE,
- FROM_HERE,
- base::Bind(
- &Operation::VerifyWriteChunk, this, 0, total_size, continuation));
-}
-
-void Operation::VerifyWriteChunk(const int64& bytes_processed,
- const int64& total_size,
- const base::Closure& continuation) {
- if (!IsCancelled()) {
- scoped_ptr<char[]> source_buffer(new char[kBurningBlockSize]);
- scoped_ptr<char[]> target_buffer(new char[kBurningBlockSize]);
-
- int64 image_bytes_read = base::ReadPlatformFile(
- image_file_, bytes_processed, source_buffer.get(), kBurningBlockSize);
-
- if (image_bytes_read > 0) {
- int64 device_bytes_read = base::ReadPlatformFile(
- device_file_, bytes_processed, target_buffer.get(), image_bytes_read);
- if (image_bytes_read == device_bytes_read &&
- memcmp(source_buffer.get(), target_buffer.get(), image_bytes_read) ==
- 0) {
- int percent_curr = kProgressComplete *
- (bytes_processed + image_bytes_read) / total_size;
-
- SetProgress(percent_curr);
-
- BrowserThread::PostTask(BrowserThread::FILE,
- FROM_HERE,
- base::Bind(&Operation::VerifyWriteChunk,
- this,
- bytes_processed + image_bytes_read,
- total_size,
- continuation));
- return;
- } else {
- Error(error::kVerificationFailed);
- }
- } else if (image_bytes_read == 0) {
- VerifyWriteComplete(continuation);
- } else { // len < 0
- Error(error::kImageReadError);
- }
- }
-
- base::ClosePlatformFile(image_file_);
- base::ClosePlatformFile(device_file_);
-}
-
-void Operation::VerifyWriteComplete(const base::Closure& continuation) {
- SetProgress(kProgressComplete);
- BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, continuation);
-}
-
-} // namespace image_writer
-} // namespace extensions
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_mac.cc b/chrome/browser/extensions/api/image_writer_private/operation_mac.cc
deleted file mode 100644
index 2280f49..0000000
--- a/chrome/browser/extensions/api/image_writer_private/operation_mac.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2013 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 "chrome/browser/extensions/api/image_writer_private/error_messages.h"
-#include "chrome/browser/extensions/api/image_writer_private/operation.h"
-
-namespace extensions {
-namespace image_writer {
-
-void Operation::Write(const base::Closure& continuation) {
- Error(error::kUnsupportedOperation);
-}
-
-void Operation::VerifyWrite(const base::Closure& continuation) {
- Error(error::kUnsupportedOperation);
-}
-
-} // namespace image_writer
-} // namespace extensions
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_manager_unittest.cc b/chrome/browser/extensions/api/image_writer_private/operation_manager_unittest.cc
index ca4c59b..4a5ccf7 100644
--- a/chrome/browser/extensions/api/image_writer_private/operation_manager_unittest.cc
+++ b/chrome/browser/extensions/api/image_writer_private/operation_manager_unittest.cc
@@ -68,6 +68,12 @@ class ImageWriterOperationManagerTest
start_error_ = error;
}
+ void CancelCallback(bool success, const std::string& error) {
+ cancelled_ = true;
+ cancel_success_ = true;
+ cancel_error_ = error;
+ }
+
protected:
ImageWriterOperationManagerTest()
: started_(false),
@@ -87,6 +93,10 @@ class ImageWriterOperationManagerTest
bool start_success_;
std::string start_error_;
+ bool cancelled_;
+ bool cancel_success_;
+ std::string cancel_error_;
+
TestingProfile test_profile_;
FakeExtensionSystem* extension_system_;
FakeEventRouter* event_router_;
@@ -112,6 +122,15 @@ TEST_F(ImageWriterOperationManagerTest, WriteFromFile) {
EXPECT_TRUE(start_success_);
EXPECT_EQ("", start_error_);
+ manager.CancelWrite(
+ kDummyExtensionId,
+ base::Bind(&ImageWriterOperationManagerTest::CancelCallback,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(cancelled_);
+ EXPECT_TRUE(cancel_success_);
+ EXPECT_EQ("", cancel_error_);
+
base::RunLoop().RunUntilIdle();
}
@@ -128,6 +147,15 @@ TEST_F(ImageWriterOperationManagerTest, DestroyPartitions) {
EXPECT_TRUE(start_success_);
EXPECT_EQ("", start_error_);
+ manager.CancelWrite(
+ kDummyExtensionId,
+ base::Bind(&ImageWriterOperationManagerTest::CancelCallback,
+ base::Unretained(this)));
+
+ EXPECT_TRUE(cancelled_);
+ EXPECT_TRUE(cancel_success_);
+ EXPECT_EQ("", cancel_error_);
+
base::RunLoop().RunUntilIdle();
}
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_nonchromeos.cc b/chrome/browser/extensions/api/image_writer_private/operation_nonchromeos.cc
new file mode 100644
index 0000000..b5a7e1c
--- /dev/null
+++ b/chrome/browser/extensions/api/image_writer_private/operation_nonchromeos.cc
@@ -0,0 +1,76 @@
+// Copyright 2014 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 "base/file_util.h"
+#include "base/files/file_enumerator.h"
+#include "base/threading/worker_pool.h"
+#include "chrome/browser/extensions/api/image_writer_private/error_messages.h"
+#include "chrome/browser/extensions/api/image_writer_private/operation.h"
+#include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
+#include "content/public/browser/browser_thread.h"
+
+namespace extensions {
+namespace image_writer {
+
+using content::BrowserThread;
+
+void Operation::Write(const base::Closure& continuation) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ if (IsCancelled()) {
+ return;
+ }
+
+ SetStage(image_writer_api::STAGE_WRITE);
+ StartUtilityClient();
+
+ int64 file_size;
+ if (!base::GetFileSize(image_path_, &file_size)) {
+ Error(error::kImageReadError);
+ return;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ImageWriterUtilityClient::Write,
+ image_writer_client_,
+ base::Bind(&Operation::WriteImageProgress, this, file_size),
+ base::Bind(&Operation::CompleteAndContinue, this, continuation),
+ base::Bind(&Operation::Error, this),
+ image_path_,
+ device_path_));
+}
+
+void Operation::VerifyWrite(const base::Closure& continuation) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+
+ if (IsCancelled()) {
+ return;
+ }
+
+ SetStage(image_writer_api::STAGE_VERIFYWRITE);
+ StartUtilityClient();
+
+ int64 file_size;
+ if (!base::GetFileSize(image_path_, &file_size)) {
+ Error(error::kImageReadError);
+ return;
+ }
+
+ BrowserThread::PostTask(
+ BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(
+ &ImageWriterUtilityClient::Verify,
+ image_writer_client_,
+ base::Bind(&Operation::WriteImageProgress, this, file_size),
+ base::Bind(&Operation::CompleteAndContinue, this, continuation),
+ base::Bind(&Operation::Error, this),
+ image_path_,
+ device_path_));
+}
+
+} // namespace image_writer
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc b/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc
index 1c65efb..8012209 100644
--- a/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc
+++ b/chrome/browser/extensions/api/image_writer_private/operation_unittest.cc
@@ -77,9 +77,20 @@ class ImageWriterOperationTest : public ImageWriterUnitTestBase {
zip_file_ = temp_dir_.path().AppendASCII("test_image.zip");
ASSERT_TRUE(zip::Zip(image_dir, zip_file_, true));
+
+ // Operation setup.
+ operation_ = new OperationForTest(manager_.AsWeakPtr(),
+ kDummyExtensionId,
+ test_device_path_.AsUTF8Unsafe());
+ client_ = FakeImageWriterClient::Create();
+ operation_->SetImagePath(test_image_path_);
}
virtual void TearDown() OVERRIDE {
+ // Ensure all callbacks have been destroyed and cleanup occurs.
+ client_->Shutdown();
+ operation_->Cancel();
+
ImageWriterUnitTestBase::TearDown();
}
@@ -87,36 +98,31 @@ class ImageWriterOperationTest : public ImageWriterUnitTestBase {
base::FilePath zip_file_;
MockOperationManager manager_;
+ scoped_refptr<FakeImageWriterClient> client_;
+ scoped_refptr<OperationForTest> operation_;
};
} // namespace
+// Unizpping a non-zip should do nothing.
TEST_F(ImageWriterOperationTest, UnzipNonZipFile) {
- scoped_refptr<OperationForTest> operation(
- new OperationForTest(manager_.AsWeakPtr(),
- kDummyExtensionId,
- test_device_path_.AsUTF8Unsafe()));
-
EXPECT_CALL(manager_, OnProgress(kDummyExtensionId, _, _)).Times(0);
- operation->SetImagePath(test_image_path_);
+ EXPECT_CALL(manager_, OnError(kDummyExtensionId, _, _, _)).Times(0);
+ EXPECT_CALL(manager_, OnProgress(kDummyExtensionId, _, _)).Times(0);
+ EXPECT_CALL(manager_, OnComplete(kDummyExtensionId)).Times(0);
- operation->Start();
+ operation_->Start();
content::BrowserThread::PostTask(
content::BrowserThread::FILE,
FROM_HERE,
base::Bind(
- &OperationForTest::Unzip, operation, base::Bind(&base::DoNothing)));
+ &OperationForTest::Unzip, operation_, base::Bind(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
}
TEST_F(ImageWriterOperationTest, UnzipZipFile) {
- scoped_refptr<OperationForTest> operation(
- new OperationForTest(manager_.AsWeakPtr(),
- kDummyExtensionId,
- test_device_path_.AsUTF8Unsafe()));
-
EXPECT_CALL(manager_, OnError(kDummyExtensionId, _, _, _)).Times(0);
EXPECT_CALL(manager_,
OnProgress(kDummyExtensionId, image_writer_api::STAGE_UNZIP, _))
@@ -128,27 +134,25 @@ TEST_F(ImageWriterOperationTest, UnzipZipFile) {
OnProgress(kDummyExtensionId, image_writer_api::STAGE_UNZIP, 100))
.Times(AtLeast(1));
- operation->SetImagePath(zip_file_);
+ operation_->SetImagePath(zip_file_);
- operation->Start();
+ operation_->Start();
content::BrowserThread::PostTask(
content::BrowserThread::FILE,
FROM_HERE,
base::Bind(
- &OperationForTest::Unzip, operation, base::Bind(&base::DoNothing)));
+ &OperationForTest::Unzip, operation_, base::Bind(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
- EXPECT_TRUE(base::ContentsEqual(image_path_, operation->GetImagePath()));
+ EXPECT_TRUE(base::ContentsEqual(image_path_, operation_->GetImagePath()));
}
#if defined(OS_LINUX)
TEST_F(ImageWriterOperationTest, WriteImageToDevice) {
-
- scoped_refptr<OperationForTest> operation(
- new OperationForTest(manager_.AsWeakPtr(),
- kDummyExtensionId,
- test_device_path_.AsUTF8Unsafe()));
+#if !defined(OS_CHROMEOS)
+ operation_->SetUtilityClientForTesting(client_);
+#endif
EXPECT_CALL(manager_, OnError(kDummyExtensionId, _, _, _)).Times(0);
EXPECT_CALL(manager_,
@@ -161,34 +165,32 @@ TEST_F(ImageWriterOperationTest, WriteImageToDevice) {
OnProgress(kDummyExtensionId, image_writer_api::STAGE_WRITE, 100))
.Times(AtLeast(1));
- operation->SetImagePath(test_image_path_);
-
- operation->Start();
+ operation_->Start();
content::BrowserThread::PostTask(
content::BrowserThread::FILE,
FROM_HERE,
base::Bind(
- &OperationForTest::Write, operation, base::Bind(&base::DoNothing)));
+ &OperationForTest::Write, operation_, base::Bind(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
#if !defined(OS_CHROMEOS)
- // Chrome OS tests don't actually write to the disk because that's handled by
- // the DBUS process.
- EXPECT_TRUE(base::ContentsEqual(test_image_path_, test_device_path_));
+ client_->Progress(0);
+ client_->Progress(kTestFileSize / 2);
+ client_->Progress(kTestFileSize);
+ client_->Success();
+
+ base::RunLoop().RunUntilIdle();
#endif
}
#endif
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#if !defined(OS_CHROMEOS)
// Chrome OS doesn't support verification in the ImageBurner, so these two tests
// are skipped.
TEST_F(ImageWriterOperationTest, VerifyFileSuccess) {
- scoped_refptr<OperationForTest> operation(
- new OperationForTest(manager_.AsWeakPtr(),
- kDummyExtensionId,
- test_device_path_.AsUTF8Unsafe()));
+ operation_->SetUtilityClientForTesting(client_);
EXPECT_CALL(manager_, OnError(kDummyExtensionId, _, _, _)).Times(0);
EXPECT_CALL(
@@ -205,60 +207,64 @@ TEST_F(ImageWriterOperationTest, VerifyFileSuccess) {
.Times(AtLeast(1));
FillFile(test_device_path_, kImagePattern, kTestFileSize);
- operation->SetImagePath(test_image_path_);
- operation->Start();
+ operation_->Start();
content::BrowserThread::PostTask(content::BrowserThread::FILE,
FROM_HERE,
base::Bind(&OperationForTest::VerifyWrite,
- operation,
+ operation_,
base::Bind(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
+
+ client_->Progress(0);
+ client_->Progress(kTestFileSize / 2);
+ client_->Progress(kTestFileSize);
+ client_->Success();
+
+ base::RunLoop().RunUntilIdle();
}
TEST_F(ImageWriterOperationTest, VerifyFileFailure) {
- scoped_refptr<OperationForTest> operation(
- new OperationForTest(manager_.AsWeakPtr(),
- kDummyExtensionId,
- test_device_path_.AsUTF8Unsafe()));
+ operation_->SetUtilityClientForTesting(client_);
EXPECT_CALL(
manager_,
- OnError(kDummyExtensionId, image_writer_api::STAGE_VERIFYWRITE, _, _))
- .Times(1);
- EXPECT_CALL(
- manager_,
OnProgress(kDummyExtensionId, image_writer_api::STAGE_VERIFYWRITE, _))
.Times(AnyNumber());
EXPECT_CALL(
manager_,
OnProgress(kDummyExtensionId, image_writer_api::STAGE_VERIFYWRITE, 100))
.Times(0);
+ EXPECT_CALL(manager_, OnComplete(kDummyExtensionId)).Times(0);
+ EXPECT_CALL(
+ manager_,
+ OnError(kDummyExtensionId, image_writer_api::STAGE_VERIFYWRITE, _, _))
+ .Times(1);
FillFile(test_device_path_, kDevicePattern, kTestFileSize);
- operation->SetImagePath(test_image_path_);
- operation->Start();
+ operation_->Start();
content::BrowserThread::PostTask(content::BrowserThread::FILE,
FROM_HERE,
base::Bind(&OperationForTest::VerifyWrite,
- operation,
+ operation_,
base::Bind(&base::DoNothing)));
base::RunLoop().RunUntilIdle();
+
+ client_->Progress(0);
+ client_->Progress(kTestFileSize / 2);
+ client_->Error(error::kVerificationFailed);
+
+ base::RunLoop().RunUntilIdle();
}
#endif
-// Tests that on creation the operation has the expected state.
+// Tests that on creation the operation_ has the expected state.
TEST_F(ImageWriterOperationTest, Creation) {
- scoped_refptr<Operation> op(
- new OperationForTest(manager_.AsWeakPtr(),
- kDummyExtensionId,
- test_device_path_.AsUTF8Unsafe()));
-
- EXPECT_EQ(0, op->GetProgress());
- EXPECT_EQ(image_writer_api::STAGE_UNKNOWN, op->GetStage());
+ EXPECT_EQ(0, operation_->GetProgress());
+ EXPECT_EQ(image_writer_api::STAGE_UNKNOWN, operation_->GetStage());
}
} // namespace image_writer
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_win.cc b/chrome/browser/extensions/api/image_writer_private/operation_win.cc
deleted file mode 100644
index 2280f49..0000000
--- a/chrome/browser/extensions/api/image_writer_private/operation_win.cc
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2013 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 "chrome/browser/extensions/api/image_writer_private/error_messages.h"
-#include "chrome/browser/extensions/api/image_writer_private/operation.h"
-
-namespace extensions {
-namespace image_writer {
-
-void Operation::Write(const base::Closure& continuation) {
- Error(error::kUnsupportedOperation);
-}
-
-void Operation::VerifyWrite(const base::Closure& continuation) {
- Error(error::kUnsupportedOperation);
-}
-
-} // namespace image_writer
-} // namespace extensions
diff --git a/chrome/browser/extensions/api/image_writer_private/test_utils.cc b/chrome/browser/extensions/api/image_writer_private/test_utils.cc
index 29c5b5c..0bbb196 100644
--- a/chrome/browser/extensions/api/image_writer_private/test_utils.cc
+++ b/chrome/browser/extensions/api/image_writer_private/test_utils.cc
@@ -55,14 +55,65 @@ MockOperationManager::MockOperationManager(Profile* profile)
: OperationManager(profile) {}
MockOperationManager::~MockOperationManager() {}
+FakeImageWriterClient::FakeImageWriterClient() {}
+FakeImageWriterClient::~FakeImageWriterClient() {}
+
+void FakeImageWriterClient::Write(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target) {
+ progress_callback_ = progress_callback;
+ success_callback_ = success_callback;
+ error_callback_ = error_callback;
+}
+
+void FakeImageWriterClient::Verify(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target) {
+ progress_callback_ = progress_callback;
+ success_callback_ = success_callback;
+ error_callback_ = error_callback;
+}
+
+void FakeImageWriterClient::Cancel(const CancelCallback& cancel_callback) {
+ cancel_callback_ = cancel_callback;
+}
+
+void FakeImageWriterClient::Shutdown() {
+ // Clear handlers to not hold any reference to the caller.
+ success_callback_ = base::Closure();
+ progress_callback_ = base::Callback<void(int64)>();
+ error_callback_ = base::Callback<void(const std::string&)>();
+ cancel_callback_ = base::Closure();
+}
+
+void FakeImageWriterClient::Progress(int64 progress) {
+ progress_callback_.Run(progress);
+}
+
+void FakeImageWriterClient::Success() { success_callback_.Run(); }
+
+void FakeImageWriterClient::Error(const std::string& message) {
+ error_callback_.Run(message);
+}
+
+void FakeImageWriterClient::Cancel() { cancel_callback_.Run(); }
+
+scoped_refptr<FakeImageWriterClient> FakeImageWriterClient::Create() {
+ return scoped_refptr<FakeImageWriterClient>(new FakeImageWriterClient());
+}
+
ImageWriterUnitTestBase::ImageWriterUnitTestBase()
: thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {}
ImageWriterUnitTestBase::~ImageWriterUnitTestBase() {}
void ImageWriterUnitTestBase::SetUp() {
testing::Test::SetUp();
- ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(),
&test_image_path_));
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(),
@@ -89,6 +140,27 @@ void ImageWriterUnitTestBase::TearDown() {
#endif
}
+bool ImageWriterUnitTestBase::ImageWrittenToDevice(
+ const base::FilePath& image_path,
+ const base::FilePath& device_path) {
+ scoped_ptr<char[]> image_buffer(new char[kTestFileSize]);
+ scoped_ptr<char[]> device_buffer(new char[kTestFileSize]);
+
+ int image_bytes_read =
+ ReadFile(image_path, image_buffer.get(), kTestFileSize);
+
+ if (image_bytes_read < 0)
+ return false;
+
+ int device_bytes_read =
+ ReadFile(device_path, device_buffer.get(), kTestFileSize);
+
+ if (image_bytes_read != device_bytes_read)
+ return false;
+
+ return memcmp(image_buffer.get(), device_buffer.get(), image_bytes_read) == 0;
+}
+
bool ImageWriterUnitTestBase::FillFile(const base::FilePath& file,
const int pattern,
const int length) {
diff --git a/chrome/browser/extensions/api/image_writer_private/test_utils.h b/chrome/browser/extensions/api/image_writer_private/test_utils.h
index a19a48c..9542bbb 100644
--- a/chrome/browser/extensions/api/image_writer_private/test_utils.h
+++ b/chrome/browser/extensions/api/image_writer_private/test_utils.h
@@ -9,8 +9,10 @@
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
+#include "chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h"
#include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
#include "content/public/test/test_browser_thread_bundle.h"
+#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -47,17 +49,57 @@ class MockOperationManager : public OperationManager {
const std::string& error_message));
};
+class FakeImageWriterClient : public ImageWriterUtilityClient {
+ public:
+ FakeImageWriterClient();
+
+ virtual void Write(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target) OVERRIDE;
+
+ virtual void Verify(const ProgressCallback& progress_callback,
+ const SuccessCallback& success_callback,
+ const ErrorCallback& error_callback,
+ const base::FilePath& source,
+ const base::FilePath& target) OVERRIDE;
+
+ virtual void Cancel(const CancelCallback& cancel_callback) OVERRIDE;
+
+ virtual void Shutdown() OVERRIDE;
+
+ void Progress(int64 progress);
+ void Success();
+ void Error(const std::string& message);
+ void Cancel();
+ static scoped_refptr<FakeImageWriterClient> Create();
+
+ private:
+ virtual ~FakeImageWriterClient();
+
+ ProgressCallback progress_callback_;
+ SuccessCallback success_callback_;
+ ErrorCallback error_callback_;
+ CancelCallback cancel_callback_;
+};
+
// Base class for unit tests that manages creating image and device files.
class ImageWriterUnitTestBase : public testing::Test {
- public:
+ protected:
ImageWriterUnitTestBase();
virtual ~ImageWriterUnitTestBase();
- protected:
virtual void SetUp() OVERRIDE;
virtual void TearDown() OVERRIDE;
+ // Verifies that the data in image_path was written to the file at
+ // device_path. This is different from base::ContentsEqual because the device
+ // may be larger than the image.
+ bool ImageWrittenToDevice(const base::FilePath& image_path,
+ const base::FilePath& device_path);
+
// Fills |file| with |length| bytes of |pattern|, overwriting any existing
// data.
bool FillFile(const base::FilePath& file,
diff --git a/chrome/browser/extensions/api/image_writer_private/write_from_file_operation.cc b/chrome/browser/extensions/api/image_writer_private/write_from_file_operation.cc
index 09c56fd..47d1fea 100644
--- a/chrome/browser/extensions/api/image_writer_private/write_from_file_operation.cc
+++ b/chrome/browser/extensions/api/image_writer_private/write_from_file_operation.cc
@@ -30,12 +30,18 @@ void WriteFromFileOperation::StartImpl() {
return;
}
- Unzip(base::Bind(
- &WriteFromFileOperation::Write,
- this,
- base::Bind(&WriteFromFileOperation::VerifyWrite,
- this,
- base::Bind(&WriteFromFileOperation::Finish, this))));
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(
+ &WriteFromFileOperation::Unzip,
+ this,
+ base::Bind(
+ &WriteFromFileOperation::Write,
+ this,
+ base::Bind(&WriteFromFileOperation::VerifyWrite,
+ this,
+ base::Bind(&WriteFromFileOperation::Finish, this)))));
}
} // namespace image_writer
diff --git a/chrome/browser/extensions/api/image_writer_private/write_from_file_operation_unittest.cc b/chrome/browser/extensions/api/image_writer_private/write_from_file_operation_unittest.cc
index 1cab40b..995b586 100644
--- a/chrome/browser/extensions/api/image_writer_private/write_from_file_operation_unittest.cc
+++ b/chrome/browser/extensions/api/image_writer_private/write_from_file_operation_unittest.cc
@@ -40,7 +40,7 @@ TEST_F(ImageWriterFromFileTest, InvalidFile) {
base::RunLoop().RunUntilIdle();
}
-#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
+// Runs the entire WriteFromFile operation.
TEST_F(ImageWriterFromFileTest, WriteFromFileEndToEnd) {
MockOperationManager manager;
@@ -49,6 +49,10 @@ TEST_F(ImageWriterFromFileTest, WriteFromFileEndToEnd) {
kDummyExtensionId,
test_image_path_,
test_device_path_.AsUTF8Unsafe());
+#if !defined(OS_CHROMEOS)
+ scoped_refptr<FakeImageWriterClient> client = FakeImageWriterClient::Create();
+ op->SetUtilityClientForTesting(client);
+#endif
EXPECT_CALL(manager,
OnProgress(kDummyExtensionId, image_writer_api::STAGE_WRITE, _))
@@ -59,6 +63,9 @@ TEST_F(ImageWriterFromFileTest, WriteFromFileEndToEnd) {
EXPECT_CALL(manager,
OnProgress(kDummyExtensionId, image_writer_api::STAGE_WRITE, 100))
.Times(AtLeast(1));
+
+#if !defined(OS_CHROMEOS)
+ // Chrome OS doesn't verify.
EXPECT_CALL(
manager,
OnProgress(kDummyExtensionId, image_writer_api::STAGE_VERIFYWRITE, _))
@@ -71,16 +78,27 @@ TEST_F(ImageWriterFromFileTest, WriteFromFileEndToEnd) {
manager,
OnProgress(kDummyExtensionId, image_writer_api::STAGE_VERIFYWRITE, 100))
.Times(AtLeast(1));
+#endif
+
EXPECT_CALL(manager, OnComplete(kDummyExtensionId)).Times(1);
EXPECT_CALL(manager, OnError(kDummyExtensionId, _, _, _)).Times(0);
op->Start();
base::RunLoop().RunUntilIdle();
-
- EXPECT_TRUE(base::ContentsEqual(test_image_path_, test_device_path_));
-}
+#if !defined(OS_CHROMEOS)
+ client->Progress(0);
+ client->Progress(50);
+ client->Progress(100);
+ client->Success();
+ base::RunLoop().RunUntilIdle();
+ client->Progress(0);
+ client->Progress(50);
+ client->Progress(100);
+ client->Success();
+ base::RunLoop().RunUntilIdle();
#endif
+}
} // namespace image_writer
} // namespace extensions
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 1900034..af8baba 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -265,6 +265,12 @@
'utility/cloud_print/pwg_encoder.h',
'utility/extensions/unpacker.cc',
'utility/extensions/unpacker.h',
+ 'utility/image_writer/error_messages.cc',
+ 'utility/image_writer/error_messages.h',
+ 'utility/image_writer/image_writer.cc',
+ 'utility/image_writer/image_writer.h',
+ 'utility/image_writer/image_writer_handler.cc',
+ 'utility/image_writer/image_writer_handler.h',
'utility/importer/bookmark_html_reader.cc',
'utility/importer/bookmark_html_reader.h',
'utility/importer/bookmarks_file_importer.cc',
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index c91e918..e22e339 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -330,11 +330,11 @@
'browser/extensions/api/image_writer_private/operation.cc',
'browser/extensions/api/image_writer_private/operation.h',
'browser/extensions/api/image_writer_private/operation_chromeos.cc',
- 'browser/extensions/api/image_writer_private/operation_linux.cc',
- 'browser/extensions/api/image_writer_private/operation_mac.cc',
+ 'browser/extensions/api/image_writer_private/operation_nonchromeos.cc',
'browser/extensions/api/image_writer_private/operation_manager.cc',
'browser/extensions/api/image_writer_private/operation_manager.h',
- 'browser/extensions/api/image_writer_private/operation_win.cc',
+ 'browser/extensions/api/image_writer_private/image_writer_utility_client.cc',
+ 'browser/extensions/api/image_writer_private/image_writer_utility_client.h',
'browser/extensions/api/image_writer_private/image_writer_private_api.cc',
'browser/extensions/api/image_writer_private/image_writer_private_api.h',
'browser/extensions/api/image_writer_private/removable_storage_provider.h',
@@ -932,7 +932,7 @@
'sources!': [
'browser/extensions/api/audio/audio_service.cc',
'browser/extensions/api/feedback_private/feedback_service_nonchromeos.cc',
- 'browser/extensions/api/image_writer_private/operation_linux.cc',
+ 'browser/extensions/api/image_writer_private/operation_nonchromeos.cc',
'browser/extensions/api/system_display/display_info_provider_aura.cc',
'browser/extensions/default_apps.cc',
'browser/extensions/default_apps.h',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index e1c04c6..d6619dc 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -1918,6 +1918,7 @@
'tools/profile_reset/jtl_parser_unittest.cc',
'utility/cloud_print/pwg_encoder_unittest.cc',
'utility/extensions/unpacker_unittest.cc',
+ 'utility/image_writer/image_writer_unittest.cc',
'utility/importer/bookmark_html_reader_unittest.cc',
'utility/importer/bookmarks_file_importer_unittest.cc',
'utility/importer/firefox_importer_unittest.cc',
diff --git a/chrome/common/chrome_utility_messages.h b/chrome/common/chrome_utility_messages.h
index 92bc06e..3e8f6c3 100644
--- a/chrome/common/chrome_utility_messages.h
+++ b/chrome/common/chrome_utility_messages.h
@@ -273,6 +273,21 @@ IPC_MESSAGE_CONTROL2(ChromeUtilityMsg_RequestBlobBytes_Finished,
std::string /* bytes */)
#endif // !defined(OS_ANDROID) && !defined(OS_IOS)
+// Requests that the utility process write the contents of the source file to
+// the removable drive listed in the target file. The target will be restricted
+// to removable drives by the utility process.
+IPC_MESSAGE_CONTROL2(ChromeUtilityMsg_ImageWriter_Write,
+ base::FilePath /* source file */,
+ base::FilePath /* target file */)
+// Requests that the utility process verify that the contents of the source file
+// was written to the target. As above the target will be restricted to
+// removable drives by the utility process.
+IPC_MESSAGE_CONTROL2(ChromeUtilityMsg_ImageWriter_Verify,
+ base::FilePath /* source file */,
+ base::FilePath /* target file */)
+// Cancels a pending write or verify operation.
+IPC_MESSAGE_CONTROL0(ChromeUtilityMsg_ImageWriter_Cancel)
+
//------------------------------------------------------------------------------
// Utility process host messages:
// These are messages from the utility process to the browser.
@@ -439,3 +454,14 @@ IPC_MESSAGE_CONTROL3(ChromeUtilityHostMsg_RequestBlobBytes,
int64 /* start_byte */,
int64 /* length */)
#endif // !defined(OS_ANDROID) && !defined(OS_IOS)
+
+// Reply when a write or verify operation succeeds.
+IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_ImageWriter_Succeeded)
+// Reply when a write or verify operation has been fully cancelled.
+IPC_MESSAGE_CONTROL0(ChromeUtilityHostMsg_ImageWriter_Cancelled)
+// Reply when a write or verify operation fails to complete.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ImageWriter_Failed,
+ std::string /* message */)
+// Periodic status update about the progress of an operation.
+IPC_MESSAGE_CONTROL1(ChromeUtilityHostMsg_ImageWriter_Progress,
+ int64 /* number of bytes processed */)
diff --git a/chrome/utility/chrome_content_utility_client.cc b/chrome/utility/chrome_content_utility_client.cc
index c66abcf..d7ec6a7 100644
--- a/chrome/utility/chrome_content_utility_client.cc
+++ b/chrome/utility/chrome_content_utility_client.cc
@@ -24,6 +24,7 @@
#include "chrome/utility/cloud_print/bitmap_image.h"
#include "chrome/utility/cloud_print/pwg_encoder.h"
#include "chrome/utility/extensions/unpacker.h"
+#include "chrome/utility/image_writer/image_writer_handler.h"
#include "chrome/utility/profile_import_handler.h"
#include "chrome/utility/web_resource_unpacker.h"
#include "content/public/child/image_decoder_utils.h"
@@ -318,6 +319,8 @@ ChromeContentUtilityClient::ChromeContentUtilityClient()
handlers_.push_back(new local_discovery::ServiceDiscoveryMessageHandler());
}
#endif // ENABLE_MDNS
+
+ handlers_.push_back(new image_writer::ImageWriterHandler());
}
ChromeContentUtilityClient::~ChromeContentUtilityClient() {
diff --git a/chrome/utility/chrome_content_utility_ipc_whitelist.cc b/chrome/utility/chrome_content_utility_ipc_whitelist.cc
index 5715a9cc..ece9095 100644
--- a/chrome/utility/chrome_content_utility_ipc_whitelist.cc
+++ b/chrome/utility/chrome_content_utility_ipc_whitelist.cc
@@ -3,10 +3,13 @@
// found in the LICENSE file.
#include "chrome/utility/chrome_content_utility_ipc_whitelist.h"
+#include "chrome/common/chrome_utility_messages.h"
namespace chrome {
-const uint32 kMessageWhitelist[] = {0};
+const uint32 kMessageWhitelist[] = {ChromeUtilityMsg_ImageWriter_Cancel::ID,
+ ChromeUtilityMsg_ImageWriter_Write::ID,
+ ChromeUtilityMsg_ImageWriter_Verify::ID};
const size_t kMessageWhitelistSize = arraysize(kMessageWhitelist);
} // namespace chrome
diff --git a/chrome/utility/image_writer/OWNERS b/chrome/utility/image_writer/OWNERS
new file mode 100644
index 0000000..75892de
--- /dev/null
+++ b/chrome/utility/image_writer/OWNERS
@@ -0,0 +1,2 @@
+haven@chromium.org
+stephenlin@chromium.org
diff --git a/chrome/utility/image_writer/error_messages.cc b/chrome/utility/image_writer/error_messages.cc
new file mode 100644
index 0000000..40fceb0
--- /dev/null
+++ b/chrome/utility/image_writer/error_messages.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 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 "chrome/utility/image_writer/error_messages.h"
+
+namespace image_writer {
+namespace error {
+
+const char kCleanUp[] = "Failed to clean up after write operation.";
+const char kCloseDevice[] = "Failed to close usb device file.";
+const char kCloseImage[] = "Failed to close image file.";
+const char kInvalidDevice[] = "Invalid device path.";
+const char kNoOperationInProgress[] = "No operation in progress.";
+const char kOpenDevice[] = "Failed to open device.";
+const char kOpenImage[] = "Failed to open image.";
+const char kOperationAlreadyInProgress[] = "Operation already in progress.";
+const char kReadDevice[] = "Failed to read device.";
+const char kReadImage[] = "Failed to read image.";
+const char kWriteImage[] = "Writing image to device failed.";
+const char kVerificationFailed[] = "Verification failed.";
+
+} // namespace error
+} // namespace image_writer
diff --git a/chrome/utility/image_writer/error_messages.h b/chrome/utility/image_writer/error_messages.h
new file mode 100644
index 0000000..02ff14a
--- /dev/null
+++ b/chrome/utility/image_writer/error_messages.h
@@ -0,0 +1,27 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_IMAGE_WRITER_PRIVATE_ERROR_MESSAGES_H_
+#define CHROME_BROWSER_EXTENSIONS_API_IMAGE_WRITER_PRIVATE_ERROR_MESSAGES_H_
+
+namespace image_writer {
+namespace error {
+
+extern const char kCleanUp[];
+extern const char kCloseDevice[];
+extern const char kCloseImage[];
+extern const char kInvalidDevice[];
+extern const char kNoOperationInProgress[];
+extern const char kOpenDevice[];
+extern const char kOpenImage[];
+extern const char kOperationAlreadyInProgress[];
+extern const char kReadDevice[];
+extern const char kReadImage[];
+extern const char kWriteImage[];
+extern const char kVerificationFailed[];
+
+} // namespace error
+} // namespace image_writer
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_IMAGE_WRITER_PRIVATE_ERROR_MESSAGES_H_
diff --git a/chrome/utility/image_writer/image_writer.cc b/chrome/utility/image_writer/image_writer.cc
new file mode 100644
index 0000000..710e183
--- /dev/null
+++ b/chrome/utility/image_writer/image_writer.cc
@@ -0,0 +1,244 @@
+// Copyright 2014 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 "base/platform_file.h"
+#include "chrome/utility/image_writer/error_messages.h"
+#include "chrome/utility/image_writer/image_writer.h"
+#include "chrome/utility/image_writer/image_writer_handler.h"
+#include "content/public/utility/utility_thread.h"
+
+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;
+
+ImageWriter::ImageWriter(ImageWriterHandler* handler)
+ : image_file_(base::kInvalidPlatformFileValue),
+ device_file_(base::kInvalidPlatformFileValue),
+ bytes_processed_(0),
+ handler_(handler) {}
+
+ImageWriter::~ImageWriter() { CleanUp(); }
+
+void ImageWriter::Write(const base::FilePath& image_path,
+ const base::FilePath& device_path) {
+ if (IsRunning()) {
+ handler_->SendFailed(error::kOperationAlreadyInProgress);
+ return;
+ }
+
+ image_path_ = image_path;
+ device_path_ = device_path;
+ bytes_processed_ = 0;
+
+ base::PlatformFileError error;
+
+ image_file_ = base::CreatePlatformFile(
+ image_path_,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
+ NULL,
+ &error);
+
+ if (error != base::PLATFORM_FILE_OK) {
+ DLOG(ERROR) << "Unable to open file for read: " << image_path_.value();
+ Error(error::kOpenImage);
+ return;
+ }
+
+#if defined(OS_WIN)
+ // Windows requires that device files be opened with FILE_FLAG_NO_BUFFERING
+ // and FILE_FLAG_WRITE_THROUGH. These two flags are not part of
+ // PlatformFile::CreatePlatformFile.
+ device_file_ = CreateFile(device_path.value().c_str(),
+ GENERIC_WRITE,
+ FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
+ INVALID_HANDLE_VALUE);
+
+ if (device_file_ == base::kInvalidPlatformFileValue) {
+ Error(error::kOpenDevice);
+ return;
+ }
+#else
+ device_file_ = base::CreatePlatformFile(
+ device_path_,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_WRITE,
+ NULL,
+ &error);
+
+ if (error != base::PLATFORM_FILE_OK) {
+ DLOG(ERROR) << "Unable to open file for write(" << error
+ << "): " << device_path_.value();
+ Error(error::kOpenDevice);
+ return;
+ }
+#endif
+
+ PostProgress(0);
+
+ PostTask(base::Bind(&ImageWriter::WriteChunk, AsWeakPtr()));
+}
+
+void ImageWriter::Verify(const base::FilePath& image_path,
+ const base::FilePath& device_path) {
+ if (IsRunning()) {
+ handler_->SendFailed(error::kOperationAlreadyInProgress);
+ return;
+ }
+
+ image_path_ = image_path;
+ device_path_ = device_path;
+ bytes_processed_ = 0;
+
+ base::PlatformFileError error;
+
+ image_file_ = base::CreatePlatformFile(
+ image_path_,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
+ NULL,
+ &error);
+
+ if (error != base::PLATFORM_FILE_OK) {
+ DLOG(ERROR) << "Unable to open file for read: " << image_path_.value();
+ Error(error::kOpenImage);
+ return;
+ }
+
+ device_file_ = base::CreatePlatformFile(
+ device_path_,
+ base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ,
+ NULL,
+ &error);
+
+ if (error != base::PLATFORM_FILE_OK) {
+ DLOG(ERROR) << "Unable to open file for read: " << device_path_.value();
+ Error(error::kOpenDevice);
+ return;
+ }
+
+ PostProgress(0);
+
+ PostTask(base::Bind(&ImageWriter::VerifyChunk, AsWeakPtr()));
+}
+
+void ImageWriter::Cancel() {
+ CleanUp();
+ handler_->SendCancelled();
+}
+
+bool ImageWriter::IsRunning() const {
+ return image_file_ != base::kInvalidPlatformFileValue ||
+ device_file_ != base::kInvalidPlatformFileValue;
+}
+
+void ImageWriter::PostTask(const base::Closure& task) {
+ base::MessageLoop::current()->PostTask(FROM_HERE, task);
+}
+
+void ImageWriter::PostProgress(int64 progress) {
+ handler_->SendProgress(progress);
+}
+
+void ImageWriter::Error(const std::string& message) {
+ CleanUp();
+ handler_->SendFailed(message);
+}
+
+void ImageWriter::WriteChunk() {
+ if (!IsRunning()) {
+ return;
+ }
+
+ scoped_ptr<char[]> buffer(new char[kBurningBlockSize]);
+ base::PlatformFileInfo info;
+
+ int bytes_read = base::ReadPlatformFile(
+ image_file_, bytes_processed_, buffer.get(), kBurningBlockSize);
+
+ if (bytes_read > 0) {
+ // Always attempt to write a whole block, as Windows requires 512-byte
+ // aligned writes to devices.
+ int bytes_written = base::WritePlatformFile(
+ device_file_, bytes_processed_, buffer.get(), kBurningBlockSize);
+
+ if (bytes_written < bytes_read) {
+ Error(error::kWriteImage);
+ return;
+ }
+
+ bytes_processed_ += bytes_read;
+ PostProgress(bytes_processed_);
+
+ PostTask(base::Bind(&ImageWriter::WriteChunk, AsWeakPtr()));
+ } else if (bytes_read == 0) {
+ // End of file.
+ base::FlushPlatformFile(device_file_);
+ CleanUp();
+ handler_->SendSucceeded();
+ } else {
+ // Unable to read entire file.
+ Error(error::kReadImage);
+ }
+}
+
+void ImageWriter::VerifyChunk() {
+ if (!IsRunning()) {
+ return;
+ }
+
+ scoped_ptr<char[]> image_buffer(new char[kBurningBlockSize]);
+ scoped_ptr<char[]> device_buffer(new char[kBurningBlockSize]);
+
+ int bytes_read = base::ReadPlatformFile(
+ image_file_, bytes_processed_, image_buffer.get(), kBurningBlockSize);
+
+ if (bytes_read > 0) {
+ if (base::ReadPlatformFile(device_file_,
+ bytes_processed_,
+ device_buffer.get(),
+ kBurningBlockSize) < 0) {
+ LOG(ERROR) << "Failed to read " << kBurningBlockSize << " 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::Bind(&ImageWriter::VerifyChunk, AsWeakPtr()));
+ } else if (bytes_read == 0) {
+ // End of file.
+ CleanUp();
+ handler_->SendSucceeded();
+ } else {
+ // Unable to read entire file.
+ LOG(ERROR) << "Failed to read " << kBurningBlockSize << " bytes of image "
+ << "at offset " << bytes_processed_;
+ Error(error::kReadImage);
+ }
+}
+
+void ImageWriter::CleanUp() {
+ if (image_file_ != base::kInvalidPlatformFileValue) {
+ base::ClosePlatformFile(image_file_);
+ image_file_ = base::kInvalidPlatformFileValue;
+ }
+ if (device_file_ != base::kInvalidPlatformFileValue) {
+ base::ClosePlatformFile(device_file_);
+ device_file_ = base::kInvalidPlatformFileValue;
+ }
+}
+
+} // namespace image_writer
diff --git a/chrome/utility/image_writer/image_writer.h b/chrome/utility/image_writer/image_writer.h
new file mode 100644
index 0000000..8a9fd5b
--- /dev/null
+++ b/chrome/utility/image_writer/image_writer.h
@@ -0,0 +1,63 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_UTILITY_IMAGE_WRITER_IMAGE_WRITER_H_
+#define CHROME_UTILITY_IMAGE_WRITER_IMAGE_WRITER_H_
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+
+namespace image_writer {
+
+class ImageWriterHandler;
+
+// Manages a write within the utility thread. This class holds all the state
+// around the writing and communicates with the ImageWriterHandler to dispatch
+// messages.
+class ImageWriter : public base::SupportsWeakPtr<ImageWriter> {
+ public:
+ explicit ImageWriter(ImageWriterHandler* handler);
+ virtual ~ImageWriter();
+
+ // Starts a write from |image_path| to |device_path|.
+ void Write(const base::FilePath& image_path,
+ const base::FilePath& device_path);
+ // Starts verifying that |image_path| and |device_path| have the same size and
+ // contents.
+ void Verify(const base::FilePath& image_path,
+ const base::FilePath& device_path);
+ // Cancels any pending writes or verifications.
+ void Cancel();
+
+ // Returns whether an operation is in progress.
+ bool IsRunning() const;
+
+ private:
+ // Convenience wrappers.
+ void PostTask(const base::Closure& task);
+ void PostProgress(int64 progress);
+ void Error(const std::string& message);
+
+ // Work loops.
+ void WriteChunk();
+ void VerifyChunk();
+
+ // Cleans up file handles.
+ void CleanUp();
+
+ base::FilePath image_path_;
+ base::FilePath device_path_;
+
+ base::PlatformFile image_file_;
+ base::PlatformFile device_file_;
+ int64 bytes_processed_;
+
+ ImageWriterHandler* handler_;
+};
+
+} // namespace image_writer
+
+#endif // CHROME_UTILITY_IMAGE_WRITER_IMAGE_WRITER_H_
diff --git a/chrome/utility/image_writer/image_writer_handler.cc b/chrome/utility/image_writer/image_writer_handler.cc
new file mode 100644
index 0000000..e9a258e
--- /dev/null
+++ b/chrome/utility/image_writer/image_writer_handler.cc
@@ -0,0 +1,151 @@
+// Copyright 2014 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 "base/files/file_path.h"
+#include "chrome/common/chrome_utility_messages.h"
+#include "chrome/utility/image_writer/error_messages.h"
+#include "chrome/utility/image_writer/image_writer_handler.h"
+#include "content/public/utility/utility_thread.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <setupapi.h>
+#include <winioctl.h>
+#endif
+
+namespace image_writer {
+
+#if defined(OS_WIN)
+const size_t kStorageQueryBufferSize = 1024;
+#endif
+
+ImageWriterHandler::ImageWriterHandler() : image_writer_(this) {}
+ImageWriterHandler::~ImageWriterHandler() {}
+
+void ImageWriterHandler::SendSucceeded() {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Succeeded());
+ content::UtilityThread::Get()->ReleaseProcessIfNeeded();
+}
+
+void ImageWriterHandler::SendCancelled() {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Cancelled());
+ content::UtilityThread::Get()->ReleaseProcessIfNeeded();
+}
+
+void ImageWriterHandler::SendFailed(const std::string& message) {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Failed(message));
+ content::UtilityThread::Get()->ReleaseProcessIfNeeded();
+}
+
+void ImageWriterHandler::SendProgress(int64 progress) {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Progress(progress));
+}
+
+void ImageWriterHandler::Send(IPC::Message* msg) {
+ content::UtilityThread::Get()->Send(msg);
+}
+
+bool ImageWriterHandler::OnMessageReceived(const IPC::Message& message) {
+ bool handled = true;
+ IPC_BEGIN_MESSAGE_MAP(ImageWriterHandler, message)
+ IPC_MESSAGE_HANDLER(ChromeUtilityMsg_ImageWriter_Write, OnWriteStart)
+ IPC_MESSAGE_HANDLER(ChromeUtilityMsg_ImageWriter_Verify, OnVerifyStart)
+ IPC_MESSAGE_HANDLER(ChromeUtilityMsg_ImageWriter_Cancel, OnCancel)
+ IPC_MESSAGE_UNHANDLED(handled = false)
+ IPC_END_MESSAGE_MAP()
+ return handled;
+}
+
+void ImageWriterHandler::OnWriteStart(const base::FilePath& image,
+ const base::FilePath& device) {
+ if (!IsValidDevice(device)) {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Failed(error::kInvalidDevice));
+ return;
+ }
+
+ if (image_writer_.IsRunning()) {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Failed(
+ error::kOperationAlreadyInProgress));
+ return;
+ }
+ image_writer_.Write(image, device);
+}
+
+void ImageWriterHandler::OnVerifyStart(const base::FilePath& image,
+ const base::FilePath& device) {
+ if (!IsValidDevice(device)) {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Failed(error::kInvalidDevice));
+ return;
+ }
+
+ if (image_writer_.IsRunning()) {
+ Send(new ChromeUtilityHostMsg_ImageWriter_Failed(
+ error::kOperationAlreadyInProgress));
+ return;
+ }
+ image_writer_.Verify(image, device);
+}
+
+void ImageWriterHandler::OnCancel() {
+ if (image_writer_.IsRunning()) {
+ image_writer_.Cancel();
+ } else {
+ SendCancelled();
+ }
+}
+
+bool ImageWriterHandler::IsValidDevice(const base::FilePath& device) {
+#if defined(OS_WIN)
+ base::win::ScopedHandle device_handle(
+ CreateFile(device.value().c_str(),
+ // Desired access, which is none as we only need metadata.
+ 0,
+ // Required to be read + write for devices.
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, // Optional security attributes.
+ OPEN_EXISTING, // Devices already exist.
+ 0, // No optional flags.
+ NULL)); // No template file.
+
+ if (!device_handle) {
+ PLOG(ERROR) << "Opening device handle failed.";
+ return false;
+ }
+
+ STORAGE_PROPERTY_QUERY query = STORAGE_PROPERTY_QUERY();
+ query.PropertyId = StorageDeviceProperty;
+ query.QueryType = PropertyStandardQuery;
+ DWORD bytes_returned;
+
+ scoped_ptr<char[]> output_buf(new char[kStorageQueryBufferSize]);
+ BOOL status = DeviceIoControl(
+ device_handle, // Device handle.
+ IOCTL_STORAGE_QUERY_PROPERTY, // Flag to request device properties.
+ &query, // Query parameters.
+ sizeof(STORAGE_PROPERTY_QUERY), // query parameters size.
+ output_buf.get(), // output buffer.
+ kStorageQueryBufferSize, // Size of buffer.
+ &bytes_returned, // Number of bytes returned.
+ // Must not be null.
+ NULL); // Optional unused overlapped perameter.
+
+ if (status == FALSE) {
+ PLOG(ERROR) << "Storage property query failed.";
+ return false;
+ }
+
+ STORAGE_DEVICE_DESCRIPTOR* device_descriptor =
+ reinterpret_cast<STORAGE_DEVICE_DESCRIPTOR*>(output_buf.get());
+
+ if (device_descriptor->RemovableMedia)
+ return true;
+
+#else
+ // Other platforms will have to be added as they are supported.
+ NOTIMPLEMENTED();
+#endif
+ return false;
+}
+
+} // namespace image_writer
diff --git a/chrome/utility/image_writer/image_writer_handler.h b/chrome/utility/image_writer/image_writer_handler.h
new file mode 100644
index 0000000..5763f96
--- /dev/null
+++ b/chrome/utility/image_writer/image_writer_handler.h
@@ -0,0 +1,51 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_UTILITY_IMAGE_WRITER_IMAGE_WRITER_HANDLER_H_
+#define CHROME_UTILITY_IMAGE_WRITER_IMAGE_WRITER_HANDLER_H_
+
+#include "chrome/utility/image_writer/image_writer.h"
+#include "chrome/utility/utility_message_handler.h"
+#include "ipc/ipc_message.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace image_writer {
+
+// A handler for messages related to writing images. This class is added as a
+// handler in chrome::ChromeContentUtilityClient.
+class ImageWriterHandler : public chrome::UtilityMessageHandler {
+ public:
+ ImageWriterHandler();
+ virtual ~ImageWriterHandler();
+
+ // Methods for sending the different messages back to the browser process.
+ // Generally should be called by chrome::image_writer::ImageWriter.
+ virtual void SendSucceeded();
+ virtual void SendCancelled();
+ virtual void SendFailed(const std::string& message);
+ virtual void SendProgress(int64 progress);
+
+ private:
+ virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
+
+ // Small wrapper for sending on the UtilityProcess.
+ void Send(IPC::Message* msg);
+
+ // Message handlers.
+ void OnWriteStart(const base::FilePath& image, const base::FilePath& device);
+ void OnVerifyStart(const base::FilePath& image, const base::FilePath& device);
+ void OnCancel();
+
+ // Checks if a path is a valid target device.
+ bool IsValidDevice(const base::FilePath& device);
+
+ ImageWriter image_writer_;
+};
+
+} // namespace image_writer
+
+#endif // CHROME_UTILITY_IMAGE_WRITER_IMAGE_WRITER_HANDLER_H_
diff --git a/chrome/utility/image_writer/image_writer_unittest.cc b/chrome/utility/image_writer/image_writer_unittest.cc
new file mode 100644
index 0000000..33f11d9b
--- /dev/null
+++ b/chrome/utility/image_writer/image_writer_unittest.cc
@@ -0,0 +1,182 @@
+// Copyright 2014 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 "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "chrome/utility/image_writer/error_messages.h"
+#include "chrome/utility/image_writer/image_writer.h"
+#include "chrome/utility/image_writer/image_writer_handler.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace image_writer {
+
+using testing::_;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Lt;
+
+namespace {
+
+const int64 kTestFileSize = 1 << 15; // 32 kB
+const int kTestPattern = 0x55555555;
+
+class ImageWriterUtilityTest : public testing::Test {
+ protected:
+ virtual void SetUp() OVERRIDE {
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &image_path_));
+ ASSERT_TRUE(
+ base::CreateTemporaryFileInDir(temp_dir_.path(), &device_path_));
+ }
+
+ virtual void TearDown() OVERRIDE {}
+
+ void FillFile(const base::FilePath& path) {
+ scoped_ptr<char[]> buffer(new char[kTestFileSize]);
+ memset(buffer.get(), kTestPattern, kTestFileSize);
+
+ ASSERT_TRUE(file_util::WriteFile(path, buffer.get(), kTestFileSize));
+ }
+
+ base::FilePath image_path_;
+ base::FilePath device_path_;
+
+ private:
+ base::MessageLoop message_loop_;
+ base::ScopedTempDir temp_dir_;
+};
+
+class MockHandler : public ImageWriterHandler {
+ public:
+ MOCK_METHOD1(SendProgress, void(int64));
+ MOCK_METHOD1(SendFailed, void(const std::string& message));
+ MOCK_METHOD0(SendSucceeded, void());
+ MOCK_METHOD1(OnMessageReceived, bool(const IPC::Message& message));
+};
+
+} // namespace
+
+TEST_F(ImageWriterUtilityTest, WriteSuccessful) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(AnyNumber());
+ EXPECT_CALL(mock_handler, SendProgress(kTestFileSize)).Times(1);
+ EXPECT_CALL(mock_handler, SendProgress(0)).Times(1);
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(1);
+ EXPECT_CALL(mock_handler, SendFailed(_)).Times(0);
+
+ FillFile(image_path_);
+ image_writer.Write(image_path_, device_path_);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ImageWriterUtilityTest, WriteInvalidImageFile) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(0);
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(0);
+ EXPECT_CALL(mock_handler, SendFailed(error::kOpenImage)).Times(1);
+
+ ASSERT_TRUE(base::DeleteFile(image_path_, false));
+ image_writer.Write(image_path_, device_path_);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ImageWriterUtilityTest, WriteInvalidDeviceFile) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(0);
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(0);
+ EXPECT_CALL(mock_handler, SendFailed(error::kOpenDevice)).Times(1);
+
+ ASSERT_TRUE(base::DeleteFile(device_path_, false));
+ image_writer.Write(image_path_, device_path_);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ImageWriterUtilityTest, VerifySuccessful) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(AnyNumber());
+ EXPECT_CALL(mock_handler, SendProgress(kTestFileSize)).Times(1);
+ EXPECT_CALL(mock_handler, SendProgress(0)).Times(1);
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(1);
+ EXPECT_CALL(mock_handler, SendFailed(_)).Times(0);
+
+ FillFile(image_path_);
+ FillFile(device_path_);
+
+ image_writer.Verify(image_path_, device_path_);
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ImageWriterUtilityTest, VerifyInvalidImageFile) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(0);
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(0);
+ EXPECT_CALL(mock_handler, SendFailed(error::kOpenImage)).Times(1);
+
+ ASSERT_TRUE(base::DeleteFile(image_path_, false));
+
+ image_writer.Verify(image_path_, device_path_);
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ImageWriterUtilityTest, VerifyInvalidDeviceFile) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(0);
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(0);
+ EXPECT_CALL(mock_handler, SendFailed(error::kOpenDevice)).Times(1);
+
+ ASSERT_TRUE(base::DeleteFile(device_path_, false));
+
+ image_writer.Verify(image_path_, device_path_);
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ImageWriterUtilityTest, VerifyFailed) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(AnyNumber());
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(0);
+ EXPECT_CALL(mock_handler, SendFailed(error::kVerificationFailed)).Times(1);
+
+ FillFile(image_path_);
+ image_writer.Verify(image_path_, device_path_);
+
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(ImageWriterUtilityTest, WriteThenVerify) {
+ MockHandler mock_handler;
+ ImageWriter image_writer(&mock_handler);
+
+ EXPECT_CALL(mock_handler, SendProgress(_)).Times(AnyNumber());
+ EXPECT_CALL(mock_handler, SendSucceeded()).Times(2);
+ EXPECT_CALL(mock_handler, SendFailed(_)).Times(0);
+
+ image_writer.Write(image_path_, device_path_);
+
+ base::RunLoop().RunUntilIdle();
+
+ image_writer.Verify(image_path_, device_path_);
+
+ base::RunLoop().RunUntilIdle();
+}
+
+} // namespace image_writer