diff options
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 |