AU: FilesystemCopierAction: copy bit-exactly
Review URL: http://codereview.chromium.org/1700018
diff --git a/SConstruct b/SConstruct
index 9598975..6855425 100644
--- a/SConstruct
+++ b/SConstruct
@@ -138,7 +138,8 @@
if os.environ.has_key(key):
env['ENV'][key] = os.environ[key]
-env.ParseConfig('pkg-config --cflags --libs glib-2.0 dbus-1 dbus-glib-1')
+env.ParseConfig('pkg-config --cflags --libs '
+ 'dbus-1 dbus-glib-1 gio-2.0 gio-unix-2.0 glib-2.0')
env.ProtocolBuffer('update_metadata.pb.cc', 'update_metadata.proto')
env.DbusBindings('update_engine.dbusclient.h', 'update_engine.xml')
@@ -190,6 +191,7 @@
extent_mapper_unittest.cc
extent_writer_unittest.cc
file_writer_unittest.cc
+ filesystem_copier_action_unittest.cc
filesystem_iterator_unittest.cc
graph_utils_unittest.cc
http_fetcher_unittest.cc
diff --git a/filesystem_copier_action.cc b/filesystem_copier_action.cc
old mode 100644
new mode 100755
index 2a361ee..18bab44
--- a/filesystem_copier_action.cc
+++ b/filesystem_copier_action.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 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.
@@ -12,6 +12,10 @@
#include <map>
#include <string>
#include <vector>
+#include <gio/gio.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <glib.h>
#include "update_engine/filesystem_iterator.h"
#include "update_engine/subprocess.h"
#include "update_engine/utils.h"
@@ -24,284 +28,144 @@
namespace chromeos_update_engine {
namespace {
-const char* kMountpointTemplate = "/tmp/au_dest_mnt.XXXXXX";
-const off_t kCopyFileBufferSize = 4 * 1024 * 1024;
-const char* kCopyExclusionPrefix = "/lost+found";
+const off_t kCopyFileBufferSize = 2 * 1024 * 1024;
} // namespace {}
void FilesystemCopierAction::PerformAction() {
+ // Will tell the ActionProcessor we've failed if we return.
+ ScopedActionCompleter abort_action_completer(processor_, this);
+
if (!HasInputObject()) {
- LOG(ERROR) << "No input object. Aborting.";
- processor_->ActionComplete(this, false);
+ LOG(ERROR) << "FilesystemCopierAction missing input object.";
return;
}
install_plan_ = GetInputObject();
if (install_plan_.is_full_update) {
- // No copy needed.
- processor_->ActionComplete(this, true);
+ // No copy needed. Done!
+ abort_action_completer.set_success(true);
return;
}
- {
- // Set up dest_path_
- char *dest_path_temp = strdup(kMountpointTemplate);
- CHECK(dest_path_temp);
- CHECK_EQ(mkdtemp(dest_path_temp), dest_path_temp);
- CHECK_NE(dest_path_temp[0], '\0');
- dest_path_ = dest_path_temp;
- free(dest_path_temp);
+ const string source =
+ copy_source_.empty() ? utils::BootDevice() : copy_source_;
+ LOG(INFO) << "Copying from " << source << " to "
+ << install_plan_.install_path;
+
+ int src_fd = open(source.c_str(), O_RDONLY);
+ if (src_fd < 0) {
+ PLOG(ERROR) << "Unable to open " << source << " for reading:";
+ return;
+ }
+ int dst_fd = open(install_plan_.install_path.c_str(),
+ O_WRONLY | O_TRUNC | O_CREAT,
+ 0644);
+ if (dst_fd < 0) {
+ close(src_fd);
+ PLOG(ERROR) << "Unable to open " << install_plan_.install_path
+ << " for writing:";
+ return;
}
- // Make sure we're properly mounted
- if (Mount(install_plan_.install_path, dest_path_)) {
- bool done_early = false;
- if (utils::FileExists(
- (dest_path_ +
- FilesystemCopierAction::kCompleteFilesystemMarker).c_str())) {
- // We're done!
- done_early = true;
- skipped_copy_ = true;
- if (HasOutputPipe())
- SetOutputObject(install_plan_);
- }
- if (!Unmount(dest_path_)) {
- LOG(ERROR) << "Unmount failed. Aborting.";
- processor_->ActionComplete(this, false);
- return;
- }
- if (done_early) {
- CHECK(!is_mounted_);
- if (rmdir(dest_path_.c_str()) != 0)
- LOG(ERROR) << "Unable to remove " << dest_path_;
- processor_->ActionComplete(this, true);
- return;
- }
- }
- LOG(ERROR) << "not mounted; spawning thread";
- // If we get here, mount failed or we're not done yet. Reformat and copy.
- CHECK_EQ(pthread_create(&helper_thread_, NULL, HelperThreadMainStatic, this),
- 0);
+ src_stream_ = g_unix_input_stream_new(src_fd, TRUE);
+ dst_stream_ = g_unix_output_stream_new(dst_fd, TRUE);
+
+ buffer_.resize(kCopyFileBufferSize);
+
+ // Set up the first read
+ canceller_ = g_cancellable_new();
+
+ g_input_stream_read_async(src_stream_,
+ &buffer_[0],
+ buffer_.size(),
+ G_PRIORITY_DEFAULT,
+ canceller_,
+ &FilesystemCopierAction::StaticAsyncReadyCallback,
+ this);
+ read_in_flight_ = true;
+
+ abort_action_completer.set_should_complete(false);
}
void FilesystemCopierAction::TerminateProcessing() {
- if (is_mounted_) {
- LOG(ERROR) << "Aborted processing, but left a filesystem mounted.";
+ if (canceller_) {
+ g_cancellable_cancel(canceller_);
}
}
-bool FilesystemCopierAction::Mount(const string& device,
- const string& mountpoint) {
- CHECK(!is_mounted_);
- if(utils::MountFilesystem(device, mountpoint, 0))
- is_mounted_ = true;
- return is_mounted_;
-}
-
-bool FilesystemCopierAction::Unmount(const string& mountpoint) {
- CHECK(is_mounted_);
- if (utils::UnmountFilesystem(mountpoint))
- is_mounted_ = false;
- return !is_mounted_;
-}
-
-void* FilesystemCopierAction::HelperThreadMain() {
- // First, format the drive
- vector<string> cmd;
- cmd.push_back("/sbin/mkfs.ext3");
- cmd.push_back("-F");
- cmd.push_back(install_plan_.install_path);
- int return_code = 1;
- bool success = Subprocess::SynchronousExec(cmd, &return_code);
- if (return_code != 0) {
- LOG(INFO) << "Format of " << install_plan_.install_path
- << " failed. Exit code: " << return_code;
- success = false;
- }
- if (success) {
- if (!Mount(install_plan_.install_path, dest_path_)) {
- LOG(ERROR) << "Mount failed. Aborting";
- success = false;
- }
- }
- if (success) {
- success = CopySynchronously();
- }
- if (success) {
- // Place our marker to avoid copies again in the future
- int r = open((dest_path_ +
- FilesystemCopierAction::kCompleteFilesystemMarker).c_str(),
- O_CREAT | O_WRONLY, 0644);
- if (r >= 0)
- close(r);
- }
- // Unmount
- if (!Unmount(dest_path_)) {
- LOG(ERROR) << "Unmount failed. Aborting";
- success = false;
- }
- if (HasOutputPipe())
+void FilesystemCopierAction::Cleanup(bool success, bool was_cancelled) {
+ g_object_unref(src_stream_);
+ src_stream_ = NULL;
+ g_object_unref(dst_stream_);
+ dst_stream_ = NULL;
+ if (was_cancelled)
+ return;
+ if (success && HasOutputPipe())
SetOutputObject(install_plan_);
-
- // Tell main thread that we're done
- g_timeout_add(0, CollectThreadStatic, this);
- return reinterpret_cast<void*>(success ? 0 : 1);
-}
-
-void FilesystemCopierAction::CollectThread() {
- void *thread_ret_value = NULL;
- CHECK_EQ(pthread_join(helper_thread_, &thread_ret_value), 0);
- bool success = (thread_ret_value == 0);
- CHECK(!is_mounted_);
- if (rmdir(dest_path_.c_str()) != 0)
- LOG(INFO) << "Unable to remove " << dest_path_;
- LOG(INFO) << "FilesystemCopierAction done";
processor_->ActionComplete(this, success);
}
-bool FilesystemCopierAction::CreateDirSynchronously(const std::string& new_path,
- const struct stat& stbuf) {
- int r = mkdir(new_path.c_str(), stbuf.st_mode);
- TEST_AND_RETURN_FALSE_ERRNO(r == 0);
- return true;
-}
+void FilesystemCopierAction::AsyncReadyCallback(GObject *source_object,
+ GAsyncResult *res) {
+ GError* error = NULL;
+ CHECK(canceller_);
+ bool was_cancelled = g_cancellable_is_cancelled(canceller_) == TRUE;
+ g_object_unref(canceller_);
+ canceller_ = NULL;
-bool FilesystemCopierAction::CopyFileSynchronously(const std::string& old_path,
- const std::string& new_path,
- const struct stat& stbuf) {
- int fd_out = open(new_path.c_str(), O_CREAT | O_EXCL | O_WRONLY,
- stbuf.st_mode);
- TEST_AND_RETURN_FALSE_ERRNO(fd_out >= 0);
- ScopedFdCloser fd_out_closer(&fd_out);
- int fd_in = open(old_path.c_str(), O_RDONLY, 0);
- TEST_AND_RETURN_FALSE_ERRNO(fd_in >= 0);
- ScopedFdCloser fd_in_closer(&fd_in);
-
- vector<char> buf(min(kCopyFileBufferSize, stbuf.st_size));
- off_t bytes_written = 0;
- while (true) {
- // Make sure we don't need to abort early:
- TEST_AND_RETURN_FALSE(!g_atomic_int_get(&thread_should_exit_));
-
- ssize_t read_size = read(fd_in, &buf[0], buf.size());
- TEST_AND_RETURN_FALSE_ERRNO(read_size >= 0);
- if (0 == read_size) // EOF
- break;
-
- ssize_t write_size = 0;
- while (write_size < read_size) {
- ssize_t r = write(fd_out, &buf[write_size], read_size - write_size);
- TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
- write_size += r;
+ if (read_in_flight_) {
+ ssize_t bytes_read = g_input_stream_read_finish(src_stream_, res, &error);
+ if (bytes_read < 0) {
+ LOG(ERROR) << "Read failed:" << utils::GetGErrorMessage(error);
+ Cleanup(false, was_cancelled);
+ return;
}
- CHECK_EQ(write_size, read_size);
- bytes_written += write_size;
- CHECK_LE(bytes_written, stbuf.st_size);
- if (bytes_written == stbuf.st_size)
- break;
+
+ if (bytes_read == 0) {
+ // We're done!
+ Cleanup(true, was_cancelled);
+ return;
+ }
+ // Kick off a write
+ read_in_flight_ = false;
+ buffer_valid_size_ = bytes_read;
+ canceller_ = g_cancellable_new();
+ g_output_stream_write_async(
+ dst_stream_,
+ &buffer_[0],
+ bytes_read,
+ G_PRIORITY_DEFAULT,
+ canceller_,
+ &FilesystemCopierAction::StaticAsyncReadyCallback,
+ this);
+ return;
}
- CHECK_EQ(bytes_written, stbuf.st_size);
- return true;
-}
-bool FilesystemCopierAction::CreateHardLinkSynchronously(
- const std::string& old_path,
- const std::string& new_path) {
- int r = link(old_path.c_str(), new_path.c_str());
- TEST_AND_RETURN_FALSE_ERRNO(r == 0);
- return true;
-}
-
-bool FilesystemCopierAction::CopySymlinkSynchronously(
- const std::string& old_path,
- const std::string& new_path,
- const struct stat& stbuf) {
- vector<char> buf(PATH_MAX + 1);
- ssize_t r = readlink(old_path.c_str(), &buf[0], buf.size());
- TEST_AND_RETURN_FALSE_ERRNO(r >= 0);
- // Make sure we got the entire link
- TEST_AND_RETURN_FALSE(static_cast<unsigned>(r) < buf.size());
- buf[r] = '\0';
- int rc = symlink(&buf[0], new_path.c_str());
- TEST_AND_RETURN_FALSE_ERRNO(rc == 0);
- return true;
-}
-
-bool FilesystemCopierAction::CreateNodeSynchronously(
- const std::string& new_path,
- const struct stat& stbuf) {
- int r = mknod(new_path.c_str(), stbuf.st_mode, stbuf.st_rdev);
- TEST_AND_RETURN_FALSE_ERRNO(r == 0);
- return true;
-}
-
-// Returns true on success
-bool FilesystemCopierAction::CopySynchronously() {
- // This map is a map from inode # to new_path.
- map<ino_t, string> hard_links;
- FilesystemIterator iter(copy_source_,
- utils::SetWithValue<string>(kCopyExclusionPrefix));
- bool success = true;
- for (; !g_atomic_int_get(&thread_should_exit_) &&
- !iter.IsEnd(); iter.Increment()) {
- const string old_path = iter.GetFullPath();
- const string new_path = dest_path_ + iter.GetPartialPath();
- LOG(INFO) << "copying " << old_path << " to " << new_path;
- const struct stat stbuf = iter.GetStat();
- success = false;
-
- // Skip lost+found
- CHECK_NE(kCopyExclusionPrefix, iter.GetPartialPath());
-
- // Directories can't be hard-linked, so check for directories first
- if (iter.GetPartialPath().empty()) {
- // Root has an empty path.
- // We don't need to create anything for the root, which is the first
- // thing we get from the iterator.
- success = true;
- } else if (S_ISDIR(stbuf.st_mode)) {
- success = CreateDirSynchronously(new_path, stbuf);
+ ssize_t bytes_written = g_output_stream_write_finish(dst_stream_,
+ res,
+ &error);
+ if (bytes_written < static_cast<ssize_t>(buffer_valid_size_)) {
+ if (bytes_written < 0) {
+ LOG(ERROR) << "Write failed:" << utils::GetGErrorMessage(error);
} else {
- if (stbuf.st_nlink > 1 &&
- utils::MapContainsKey(hard_links, stbuf.st_ino)) {
- success = CreateHardLinkSynchronously(hard_links[stbuf.st_ino],
- new_path);
- } else {
- if (stbuf.st_nlink > 1)
- hard_links[stbuf.st_ino] = new_path;
- if (S_ISREG(stbuf.st_mode)) {
- success = CopyFileSynchronously(old_path, new_path, stbuf);
- } else if (S_ISLNK(stbuf.st_mode)) {
- success = CopySymlinkSynchronously(old_path, new_path, stbuf);
- } else if (S_ISFIFO(stbuf.st_mode) ||
- S_ISCHR(stbuf.st_mode) ||
- S_ISBLK(stbuf.st_mode) ||
- S_ISSOCK(stbuf.st_mode)) {
- success = CreateNodeSynchronously(new_path, stbuf);
- } else {
- CHECK(false) << "Unable to copy file " << old_path << " with mode "
- << stbuf.st_mode;
- }
- }
+ LOG(ERROR) << "Write was short: wrote " << bytes_written
+ << " but expected to write " << buffer_valid_size_;
}
- TEST_AND_RETURN_FALSE(success);
-
- // chmod new file
- if (!S_ISLNK(stbuf.st_mode)) {
- int r = chmod(new_path.c_str(), stbuf.st_mode);
- TEST_AND_RETURN_FALSE_ERRNO(r == 0);
- }
-
- // Set uid/gid.
- int r = lchown(new_path.c_str(), stbuf.st_uid, stbuf.st_gid);
- TEST_AND_RETURN_FALSE_ERRNO(r == 0);
+ Cleanup(false, was_cancelled);
+ return;
}
- TEST_AND_RETURN_FALSE(!iter.IsErr());
- // Success!
- return true;
-}
-const char* FilesystemCopierAction::kCompleteFilesystemMarker(
- "/update_engine_copy_success");
+ // Kick off a read
+ read_in_flight_ = true;
+ canceller_ = g_cancellable_new();
+ g_input_stream_read_async(
+ src_stream_,
+ &buffer_[0],
+ buffer_.size(),
+ G_PRIORITY_DEFAULT,
+ canceller_,
+ &FilesystemCopierAction::StaticAsyncReadyCallback,
+ this);
+}
} // namespace chromeos_update_engine
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
index 3a330ef..9e7f060 100644
--- a/filesystem_copier_action.h
+++ b/filesystem_copier_action.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 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.
@@ -8,30 +8,14 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <string>
+#include <vector>
+#include <gio/gio.h>
#include <glib.h>
#include "update_engine/action.h"
#include "update_engine/install_plan.h"
// This action will only do real work if it's a delta update. It will
-// format the install partition as ext3/4, copy the root filesystem into it,
-// and then terminate.
-
-// Implementation notes: This action uses a helper thread, which seems to
-// violate the design decision to only have a single thread and use
-// asynchronous i/o. The issue is that (to the best of my knowledge),
-// there are no linux APIs to crawl a filesystem's metadata asynchronously.
-// The suggested way seems to be to open the raw device and parse the ext
-// filesystem. That's not a good approach for a number of reasons:
-// - ties us to ext filesystem
-// - although this wouldn't happen at the time of writing, it may not handle
-// changes to the source fs during the copy as gracefully.
-// - requires us to have read-access to the source filesystem device, which
-// may be a security issue.
-//
-// Having said this, using a helper thread is not ideal, but it's acceptable:
-// we still honor the Action API. That is, all interaction between the action
-// and other objects in the system (e.g. the ActionProcessor) happens on the
-// main thread. The helper thread is fully encapsulated by the action.
+// copy the root partition to install partition, and then terminate.
namespace chromeos_update_engine {
@@ -49,10 +33,11 @@
class FilesystemCopierAction : public Action<FilesystemCopierAction> {
public:
FilesystemCopierAction()
- : thread_should_exit_(0),
- is_mounted_(false),
- copy_source_("/"),
- skipped_copy_(false) {}
+ : src_stream_(NULL),
+ dst_stream_(NULL),
+ canceller_(NULL),
+ read_in_flight_(false),
+ buffer_valid_size_(0) {}
typedef ActionTraits<FilesystemCopierAction>::InputObjectType
InputObjectType;
typedef ActionTraits<FilesystemCopierAction>::OutputObjectType
@@ -64,81 +49,51 @@
void set_copy_source(const std::string& path) {
copy_source_ = path;
}
- // Returns true if we detected that a copy was unneeded and thus skipped it.
- bool skipped_copy() { return skipped_copy_; }
// Debugging/logging
static std::string StaticType() { return "FilesystemCopierAction"; }
std::string Type() const { return StaticType(); }
private:
- // These synchronously mount or unmount the given mountpoint
- bool Mount(const std::string& device, const std::string& mountpoint);
- bool Unmount(const std::string& mountpoint);
-
- // Performs a recursive file/directory copy from copy_source_ to dest_path_.
- // Doesn't return until the copy has completed. Returns true on success
- // or false on error.
- bool CopySynchronously();
-
- // There are helper functions for CopySynchronously. They handle creating
- // various types of files. They return true on success.
- bool CreateDirSynchronously(const std::string& new_path,
- const struct stat& stbuf);
- bool CopyFileSynchronously(const std::string& old_path,
- const std::string& new_path,
- const struct stat& stbuf);
- bool CreateHardLinkSynchronously(const std::string& old_path,
- const std::string& new_path);
- // Note: Here, old_path is an existing symlink that will be copied to
- // new_path. Thus, old_path is *not* the same as the old_path from
- // the symlink() syscall.
- bool CopySymlinkSynchronously(const std::string& old_path,
- const std::string& new_path,
- const struct stat& stbuf);
- bool CreateNodeSynchronously(const std::string& new_path,
- const struct stat& stbuf);
-
- // Returns NULL on success
- void* HelperThreadMain();
- static void* HelperThreadMainStatic(void* data) {
- FilesystemCopierAction* self =
- reinterpret_cast<FilesystemCopierAction*>(data);
- return self->HelperThreadMain();
+ // Callback from glib when the copy operation is done.
+ void AsyncReadyCallback(GObject *source_object, GAsyncResult *res);
+ static void StaticAsyncReadyCallback(GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data) {
+ reinterpret_cast<FilesystemCopierAction*>(user_data)->AsyncReadyCallback(
+ source_object, res);
}
-
- // Joins the thread and tells the processor that we're done
- void CollectThread();
- // GMainLoop callback function:
- static gboolean CollectThreadStatic(gpointer data) {
- FilesystemCopierAction* self =
- reinterpret_cast<FilesystemCopierAction*>(data);
- self->CollectThread();
- return FALSE;
- }
-
- pthread_t helper_thread_;
-
- volatile gint thread_should_exit_;
-
- static const char* kCompleteFilesystemMarker;
-
- // Whether or not the destination device is currently mounted.
- bool is_mounted_;
-
- // Where the destination device is mounted.
- std::string dest_path_;
-
- // The path to copy from. Usually left as the default "/", but tests can
- // change it.
+
+ // Cleans up all the variables we use for async operations and tells
+ // the ActionProcessor we're done w/ success as passed in.
+ // was_cancelled should be true if TerminateProcessing() was called.
+ void Cleanup(bool success, bool was_cancelled);
+
+ // The path to copy from. If empty (the default), the source is from the
+ // passed in InstallPlan.
std::string copy_source_;
+ // If non-NULL, these are GUnixInputStream objects for the opened
+ // source/destination partitions.
+ GInputStream* src_stream_;
+ GOutputStream* dst_stream_;
+
+ // If non-NULL, the cancellable object for the in-flight async call.
+ GCancellable* canceller_;
+
+ // True if we're waiting on a read to complete; false if we're
+ // waiting on a write.
+ bool read_in_flight_;
+
+ // The buffer for storing data we read/write.
+ std::vector<char> buffer_;
+
+ // Number of valid elements in buffer_.
+ std::vector<char>::size_type buffer_valid_size_;
+
// The install plan we're passed in via the input pipe.
InstallPlan install_plan_;
-
- // Set to true if we detected the copy was unneeded and thus we skipped it.
- bool skipped_copy_;
-
+
DISALLOW_COPY_AND_ASSIGN(FilesystemCopierAction);
};
diff --git a/filesystem_copier_action_unittest.cc b/filesystem_copier_action_unittest.cc
index 86c15ac..3ccea2b 100644
--- a/filesystem_copier_action_unittest.cc
+++ b/filesystem_copier_action_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Copyright (c) 2010 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.
@@ -7,6 +7,7 @@
#include <string>
#include <vector>
#include <gtest/gtest.h>
+#include "base/string_util.h"
#include "update_engine/filesystem_copier_action.h"
#include "update_engine/filesystem_iterator.h"
#include "update_engine/omaha_hash_calculator.h"
@@ -21,22 +22,28 @@
class FilesystemCopierActionTest : public ::testing::Test {
protected:
- void DoTest(bool double_copy, bool run_out_of_space);
- string TestDir() { return "./FilesystemCopierActionTestDir"; }
+ void DoTest(bool run_out_of_space, bool terminate_early);
void SetUp() {
- System(string("mkdir -p ") + TestDir());
}
void TearDown() {
- System(string("rm -rf ") + TestDir());
}
};
class FilesystemCopierActionTestDelegate : public ActionProcessorDelegate {
public:
FilesystemCopierActionTestDelegate() : ran_(false), success_(false) {}
- void ProcessingDone(const ActionProcessor* processor, bool success) {
+ void ExitMainLoop() {
+ while (g_main_context_pending(NULL)) {
+ g_main_context_iteration(NULL, false);
+ }
g_main_loop_quit(loop_);
}
+ void ProcessingDone(const ActionProcessor* processor, bool success) {
+ ExitMainLoop();
+ }
+ void ProcessingStopped(const ActionProcessor* processor) {
+ ExitMainLoop();
+ }
void ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
bool success) {
@@ -56,9 +63,21 @@
bool success_;
};
+struct StartProcessorCallbackArgs {
+ ActionProcessor* processor;
+ FilesystemCopierAction* filesystem_copier_action;
+ bool terminate_early;
+};
+
gboolean StartProcessorInRunLoop(gpointer data) {
- ActionProcessor* processor = reinterpret_cast<ActionProcessor*>(data);
+ StartProcessorCallbackArgs* args =
+ reinterpret_cast<StartProcessorCallbackArgs*>(data);
+ ActionProcessor* processor = args->processor;
processor->StartProcessing();
+ if (args->terminate_early) {
+ EXPECT_TRUE(args->filesystem_copier_action);
+ args->processor->StopProcessing();
+ }
return FALSE;
}
@@ -66,44 +85,54 @@
ASSERT_EQ(0, getuid());
DoTest(false, false);
}
-void FilesystemCopierActionTest::DoTest(bool double_copy,
- bool run_out_of_space) {
+void FilesystemCopierActionTest::DoTest(bool run_out_of_space,
+ bool terminate_early) {
GMainLoop *loop = g_main_loop_new(g_main_context_default(), FALSE);
- // make two populated ext images, mount them both (one in the other),
- // and copy to a loop device setup to correspond to another file.
+ string a_loop_file;
+ string b_loop_file;
+
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/a_loop_file.XXXXXX",
+ &a_loop_file,
+ NULL));
+ ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+ EXPECT_TRUE(utils::MakeTempFile("/tmp/b_loop_file.XXXXXX",
+ &b_loop_file,
+ NULL));
+ ScopedPathUnlinker b_loop_file_unlinker(b_loop_file);
+
+ // Make random data for a, zero filled data for b.
+ const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
+ vector<char> a_loop_data(kLoopFileSize);
+ FillWithData(&a_loop_data);
+ vector<char> b_loop_data(run_out_of_space ?
+ (kLoopFileSize - 1) :
+ kLoopFileSize,
+ '\0'); // Fill with 0s
- const string a_image(TestDir() + "/a_image");
- const string b_image(TestDir() + "/b_image");
- const string out_image(TestDir() + "/out_image");
+ // Write data to disk
+ EXPECT_TRUE(WriteFileVector(a_loop_file, a_loop_data));
+ EXPECT_TRUE(WriteFileVector(b_loop_file, b_loop_data));
- vector<string> expected_paths_vector;
- CreateExtImageAtPath(a_image, &expected_paths_vector);
- CreateExtImageAtPath(b_image, NULL);
+ // Make loop devices for the files
+ string a_dev = GetUnusedLoopDevice();
+ EXPECT_FALSE(a_dev.empty());
+ EXPECT_EQ(0, System(StringPrintf("losetup %s %s",
+ a_dev.c_str(),
+ a_loop_file.c_str())));
+ ScopedLoopbackDeviceReleaser a_dev_releaser(a_dev);
- // create 5 MiB file
- ASSERT_EQ(0, System(string("dd if=/dev/zero of=") + out_image
- + " seek=5242879 bs=1 count=1"));
+ string b_dev = GetUnusedLoopDevice();
+ EXPECT_FALSE(b_dev.empty());
+ EXPECT_EQ(0, System(StringPrintf("losetup %s %s",
+ b_dev.c_str(),
+ b_loop_file.c_str())));
+ ScopedLoopbackDeviceReleaser b_dev_releaser(b_dev);
- // mount them both
- System(("mkdir -p " + TestDir() + "/mnt").c_str());
- ASSERT_EQ(0, System(string("mount -o loop ") + a_image + " " +
- TestDir() + "/mnt"));
- ASSERT_EQ(0,
- System(string("mount -o loop ") + b_image + " " +
- TestDir() + "/mnt/some_dir/mnt"));
-
- if (run_out_of_space)
- ASSERT_EQ(0, System(string("dd if=/dev/zero of=") +
- TestDir() + "/mnt/big_zero bs=5M count=1"));
-
- string dev = GetUnusedLoopDevice();
-
- EXPECT_EQ(0, System(string("losetup ") + dev + " " + out_image));
-
+ // Set up the action objects
InstallPlan install_plan;
install_plan.is_full_update = false;
- install_plan.install_path = dev;
+ install_plan.install_path = b_dev;
ActionProcessor processor;
FilesystemCopierActionTestDelegate delegate;
@@ -112,72 +141,43 @@
ObjectFeederAction<InstallPlan> feeder_action;
FilesystemCopierAction copier_action;
- FilesystemCopierAction copier_action2;
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&feeder_action, &copier_action);
- if (double_copy) {
- BondActions(&copier_action, &copier_action2);
- BondActions(&copier_action2, &collector_action);
- } else {
- BondActions(&copier_action, &collector_action);
- }
+ BondActions(&copier_action, &collector_action);
processor.EnqueueAction(&feeder_action);
processor.EnqueueAction(&copier_action);
- if (double_copy)
- processor.EnqueueAction(&copier_action2);
processor.EnqueueAction(&collector_action);
- copier_action.set_copy_source(TestDir() + "/mnt");
+ copier_action.set_copy_source(a_dev);
feeder_action.set_obj(install_plan);
- g_timeout_add(0, &StartProcessorInRunLoop, &processor);
+ StartProcessorCallbackArgs start_callback_args;
+ start_callback_args.processor = &processor;
+ start_callback_args.filesystem_copier_action = &copier_action;
+ start_callback_args.terminate_early = terminate_early;
+
+ g_timeout_add(0, &StartProcessorInRunLoop, &start_callback_args);
g_main_loop_run(loop);
g_main_loop_unref(loop);
- EXPECT_EQ(0, System(string("losetup -d ") + dev));
- EXPECT_EQ(0, System(string("umount -d ") + TestDir() + "/mnt/some_dir/mnt"));
- EXPECT_EQ(0, System(string("umount -d ") + TestDir() + "/mnt"));
- EXPECT_EQ(0, unlink(a_image.c_str()));
- EXPECT_EQ(0, unlink(b_image.c_str()));
-
- EXPECT_TRUE(delegate.ran());
- if (run_out_of_space) {
+ if (!terminate_early)
+ EXPECT_TRUE(delegate.ran());
+ if (run_out_of_space || terminate_early) {
EXPECT_FALSE(delegate.success());
- EXPECT_EQ(0, unlink(out_image.c_str()));
- EXPECT_EQ(0, rmdir((TestDir() + "/mnt").c_str()));
return;
}
EXPECT_TRUE(delegate.success());
- EXPECT_EQ(0, System(string("mount -o loop ") + out_image + " " +
- TestDir() + "/mnt"));
// Make sure everything in the out_image is there
- expected_paths_vector.push_back("/update_engine_copy_success");
- for (vector<string>::iterator it = expected_paths_vector.begin();
- it != expected_paths_vector.end(); ++it) {
- *it = TestDir() + "/mnt" + *it;
- }
- set<string> expected_paths(expected_paths_vector.begin(),
- expected_paths_vector.end());
- VerifyAllPaths(TestDir() + "/mnt", expected_paths);
- string file_data;
- EXPECT_TRUE(utils::ReadFileToString(TestDir() + "/mnt/hi", &file_data));
- EXPECT_EQ("hi\n", file_data);
- EXPECT_TRUE(utils::ReadFileToString(TestDir() + "/mnt/hello", &file_data));
- EXPECT_EQ("hello\n", file_data);
- EXPECT_EQ("/some/target", Readlink(TestDir() + "/mnt/sym"));
- EXPECT_EQ(0, System(string("umount -d ") + TestDir() + "/mnt"));
+ vector<char> a_out;
+ vector<char> b_out;
+ EXPECT_TRUE(utils::ReadFile(a_dev, &a_out));
+ EXPECT_TRUE(utils::ReadFile(b_dev, &b_out));
+ EXPECT_TRUE(ExpectVectorsEq(a_out, b_out));
+ EXPECT_TRUE(ExpectVectorsEq(a_loop_data, a_out));
- EXPECT_EQ(0, unlink(out_image.c_str()));
- EXPECT_EQ(0, rmdir((TestDir() + "/mnt").c_str()));
-
- EXPECT_FALSE(copier_action.skipped_copy());
- LOG(INFO) << "collected plan:";
- collector_action.object().Dump();
- LOG(INFO) << "expected plan:";
- install_plan.Dump();
EXPECT_TRUE(collector_action.object() == install_plan);
}
@@ -262,12 +262,12 @@
EXPECT_FALSE(delegate.success_);
}
-TEST_F(FilesystemCopierActionTest, RunAsRootSkipUpdateTest) {
+TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
ASSERT_EQ(0, getuid());
DoTest(true, false);
}
-TEST_F(FilesystemCopierActionTest, RunAsRootNoSpaceTest) {
+TEST_F(FilesystemCopierActionTest, RunAsRootTerminateEarlyTest) {
ASSERT_EQ(0, getuid());
DoTest(false, true);
}
diff --git a/testrunner.cc b/testrunner.cc
index 0a74480..933c25d 100644
--- a/testrunner.cc
+++ b/testrunner.cc
@@ -4,14 +4,21 @@
// based on pam_google_testrunner.cc
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-bindings.h>
+#include <dbus/dbus-glib-lowlevel.h>
#include <glib.h>
+#include <glib-object.h>
#include <gtest/gtest.h>
-
+#include "base/command_line.h"
#include "update_engine/subprocess.h"
int main(int argc, char **argv) {
+ ::g_type_init();
g_thread_init(NULL);
+ dbus_g_thread_init();
chromeos_update_engine::Subprocess::Init();
+ CommandLine::Init(argc, argv);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
diff --git a/update_engine_client.cc b/update_engine_client.cc
index 1e273ed..bed7205 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -16,6 +16,7 @@
using chromeos_update_engine::kUpdateEngineServiceName;
using chromeos_update_engine::kUpdateEngineServicePath;
using chromeos_update_engine::kUpdateEngineServiceInterface;
+using chromeos_update_engine::utils::GetGErrorMessage;
DEFINE_bool(status, false, "Print the status to stdout.");
DEFINE_bool(force_update, false,
@@ -25,12 +26,6 @@
namespace {
-const char* GetErrorMessage(const GError* error) {
- if (!error)
- return "Unknown error.";
- return error->message;
-}
-
bool GetStatus() {
DBusGConnection *bus;
DBusGProxy *proxy;
@@ -46,7 +41,7 @@
kUpdateEngineServiceInterface,
&error);
if (!proxy) {
- LOG(FATAL) << "Error getting proxy: " << GetErrorMessage(error);
+ LOG(FATAL) << "Error getting proxy: " << GetGErrorMessage(error);
}
gint64 last_checked_time = 0;
@@ -64,7 +59,7 @@
&new_size,
&error);
if (rc == FALSE) {
- LOG(INFO) << "Error getting status: " << GetErrorMessage(error);
+ LOG(INFO) << "Error getting status: " << GetGErrorMessage(error);
}
printf("LAST_CHECKED_TIME=%" PRIi64 "\nPROGRESS=%f\nCURRENT_OP=%s\n"
"NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n",
diff --git a/utils.cc b/utils.cc
index 7f05ffb..724a4ba 100644
--- a/utils.cc
+++ b/utils.cc
@@ -333,6 +333,12 @@
return true;
}
+const char* GetGErrorMessage(const GError* error) {
+ if (!error)
+ return "Unknown error.";
+ return error->message;
+}
+
const char* const kStatefulPartition = "/mnt/stateful_partition";
} // namespace utils
diff --git a/utils.h b/utils.h
index bcbd8da..7b6fc10 100644
--- a/utils.h
+++ b/utils.h
@@ -10,6 +10,7 @@
#include <set>
#include <string>
#include <vector>
+#include <glib.h>
#include "update_engine/action.h"
#include "update_engine/action_processor.h"
@@ -78,6 +79,9 @@
unsigned long flags);
bool UnmountFilesystem(const std::string& mountpoint);
+// Returns the error message, if any, from a GError pointer.
+const char* GetGErrorMessage(const GError* error);
+
// Log a string in hex to LOG(INFO). Useful for debugging.
void HexDumpArray(const unsigned char* const arr, const size_t length);
inline void HexDumpString(const std::string& str) {