Implement Rollback to previously booted partitions.
This CL implements rollback to whatever partition we ran from before.
We expose this functionality via dbus under AttemptRollback and expose
a new command-line option to update_engine_client that a developer can
use.
BUG=chromium:242665
TEST=Unittests, full update, update + rollback and verified.
Change-Id: Ie59f90b9a0b777dc1329592449090c70892236bf
Reviewed-on: https://gerrit.chromium.org/gerrit/58427
Commit-Queue: Chris Sosa <[email protected]>
Reviewed-by: Chris Sosa <[email protected]>
Tested-by: Chris Sosa <[email protected]>
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index 4c6cd1c..c83d6f2 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -18,6 +18,9 @@
send_member="AttemptUpdate"/>
<allow send_destination="org.chromium.UpdateEngine"
send_interface="org.chromium.UpdateEngineInterface"
+ send_member="AttemptRollback"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
send_member="ResetStatus"/>
<allow send_destination="org.chromium.UpdateEngine"
send_interface="org.chromium.UpdateEngineInterface"
diff --git a/dbus_service.cc b/dbus_service.cc
index 97aae35..f90b90e 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -101,6 +101,14 @@
return TRUE;
}
+gboolean update_engine_service_attempt_rollback(UpdateEngineService* self,
+ bool powerwash,
+ GError **error) {
+ LOG(INFO) << "Attempting rollback to non-active partitions.";
+ self->system_state_->update_attempter()->Rollback(powerwash);
+ return TRUE;
+}
+
gboolean update_engine_service_reset_status(UpdateEngineService* self,
GError **error) {
*error = NULL;
diff --git a/dbus_service.h b/dbus_service.h
index 71e7997..d5e0240 100644
--- a/dbus_service.h
+++ b/dbus_service.h
@@ -51,6 +51,10 @@
gchar* omaha_url,
GError **error);
+gboolean update_engine_service_attempt_rollback(UpdateEngineService* self,
+ bool powerwash,
+ GError **error);
+
gboolean update_engine_service_reset_status(UpdateEngineService* self,
GError **error);
diff --git a/download_action.h b/download_action.h
index 318ece8..020aa9c 100644
--- a/download_action.h
+++ b/download_action.h
@@ -41,19 +41,9 @@
virtual void BytesReceived(uint64_t bytes_received, uint64_t total) = 0;
};
-class DownloadAction;
-class NoneType;
class PrefsInterface;
-template<>
-class ActionTraits<DownloadAction> {
- public:
- // Takes and returns an InstallPlan
- typedef InstallPlan InputObjectType;
- typedef InstallPlan OutputObjectType;
-};
-
-class DownloadAction : public Action<DownloadAction>,
+class DownloadAction : public InstallPlanAction,
public HttpFetcherDelegate {
public:
// Takes ownership of the passed in HttpFetcher. Useful for testing.
@@ -63,8 +53,6 @@
SystemState* system_state,
HttpFetcher* http_fetcher);
virtual ~DownloadAction();
- typedef ActionTraits<DownloadAction>::InputObjectType InputObjectType;
- typedef ActionTraits<DownloadAction>::OutputObjectType OutputObjectType;
void PerformAction();
void TerminateProcessing();
diff --git a/filesystem_copier_action.h b/filesystem_copier_action.h
index acbdd05..4a61298 100644
--- a/filesystem_copier_action.h
+++ b/filesystem_copier_action.h
@@ -24,25 +24,11 @@
namespace chromeos_update_engine {
-class FilesystemCopierAction;
-template<>
-class ActionTraits<FilesystemCopierAction> {
- public:
- // Takes the install plan as input
- typedef InstallPlan InputObjectType;
- // Passes the install plan as output
- typedef InstallPlan OutputObjectType;
-};
-
-class FilesystemCopierAction : public Action<FilesystemCopierAction> {
+class FilesystemCopierAction : public InstallPlanAction {
public:
FilesystemCopierAction(bool copying_kernel_install_path, bool verify_hash);
- typedef ActionTraits<FilesystemCopierAction>::InputObjectType
- InputObjectType;
- typedef ActionTraits<FilesystemCopierAction>::OutputObjectType
- OutputObjectType;
void PerformAction();
void TerminateProcessing();
diff --git a/install_plan.h b/install_plan.h
index d6ff9e0..b070cbe 100644
--- a/install_plan.h
+++ b/install_plan.h
@@ -10,6 +10,8 @@
#include <base/basictypes.h>
+#include "update_engine/action.h"
+
// InstallPlan is a simple struct that contains relevant info for many
// parts of the update system about the install that should happen.
namespace chromeos_update_engine {
@@ -39,7 +41,7 @@
std::string download_url; // url to download from
uint64_t payload_size; // size of the payload
- std::string payload_hash ; // SHA256 hash of the payload
+ std::string payload_hash; // SHA256 hash of the payload
uint64_t metadata_size; // size of the metadata
std::string metadata_signature; // signature of the metadata
std::string install_path; // path to install device
@@ -70,6 +72,44 @@
bool powerwash_required;
};
+class InstallPlanAction;
+
+template<>
+class ActionTraits<InstallPlanAction> {
+ public:
+ // Takes the install plan as input
+ typedef InstallPlan InputObjectType;
+ // Passes the install plan as output
+ typedef InstallPlan OutputObjectType;
+};
+
+// Basic action that only receives and sends Install Plans.
+// Can be used to construct an Install Plan to send to any other Action that
+// accept an InstallPlan.
+class InstallPlanAction : public Action<InstallPlanAction> {
+ public:
+ InstallPlanAction() {}
+ InstallPlanAction(const InstallPlan& install_plan):
+ install_plan_(install_plan) {}
+
+ virtual void PerformAction() {
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
+ processor_->ActionComplete(this, kErrorCodeSuccess);
+ }
+
+ virtual std::string Type() const { return "InstallPlanAction"; }
+
+ typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
+ typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;
+
+ private:
+ InstallPlan install_plan_;
+
+ DISALLOW_COPY_AND_ASSIGN (InstallPlanAction);
+};
+
} // namespace chromeos_update_engine
#endif // CHROMEOS_PLATFORM_UPDATE_ENGINE_INSTALL_PLAN_H__
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index c876d94..358988c 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -72,7 +72,7 @@
}
install_plan_.is_full_update = !response.is_delta_payload;
- TEST_AND_RETURN(GetInstallDev(
+ TEST_AND_RETURN(utils::GetInstallDev(
(!boot_device_.empty() ? boot_device_ : utils::BootDevice()),
&install_plan_.install_path));
install_plan_.kernel_install_path =
@@ -104,18 +104,6 @@
completer.set_code(kErrorCodeSuccess);
}
-bool OmahaResponseHandlerAction::GetInstallDev(const std::string& boot_dev,
- std::string* install_dev) {
- TEST_AND_RETURN_FALSE(utils::StringHasPrefix(boot_dev, "/dev/"));
- string ret(boot_dev);
- string::reverse_iterator it = ret.rbegin(); // last character in string
- // Right now, we just switch '3' and '5' partition numbers.
- TEST_AND_RETURN_FALSE((*it == '3') || (*it == '5'));
- *it = (*it == '3') ? '5' : '3';
- *install_dev = ret;
- return true;
-}
-
bool OmahaResponseHandlerAction::AreHashChecksMandatory(
const OmahaResponse& response) {
// All our internal testing uses dev server which doesn't generate metadata
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
index 27d4cec..82e99bf 100644
--- a/omaha_response_handler_action.h
+++ b/omaha_response_handler_action.h
@@ -59,13 +59,6 @@
private:
FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
- // Assumes you want to install on the "other" device, where the other
- // device is what you get if you swap 1 for 2 or 3 for 4 or vice versa
- // for the number at the end of the boot device. E.g., /dev/sda1 -> /dev/sda2
- // or /dev/sda4 -> /dev/sda3
- static bool GetInstallDev(const std::string& boot_dev,
- std::string* install_dev);
-
// Returns true if payload hash checks are mandatory based on the state
// of the system and the contents of the Omaha response. False otherwise.
bool AreHashChecksMandatory(const OmahaResponse& response);
diff --git a/postinstall_runner_action.cc b/postinstall_runner_action.cc
index 20dfae9..1143447 100644
--- a/postinstall_runner_action.cc
+++ b/postinstall_runner_action.cc
@@ -23,8 +23,8 @@
void PostinstallRunnerAction::PerformAction() {
CHECK(HasInputObject());
- const InstallPlan install_plan = GetInputObject();
- const string install_device = install_plan.install_path;
+ install_plan_ = GetInputObject();
+ const string install_device = install_plan_.install_path;
ScopedActionCompleter completer(processor_, this);
// Make mountpoint.
@@ -38,8 +38,10 @@
"ext2",
mountflags,
NULL);
+ // TODO(sosa): Remove once crbug.com/208022 is resolved.
if (rc < 0) {
- LOG(INFO) << "Failed to mount install part as ext2. Trying ext3.";
+ LOG(INFO) << "Failed to mount install part "
+ << install_device << " as ext2. Trying ext3.";
rc = mount(install_device.c_str(),
temp_rootfs_dir_.c_str(),
"ext3",
@@ -55,7 +57,7 @@
temp_dir_remover.set_should_remove(false);
completer.set_should_complete(false);
- if (install_plan.powerwash_required) {
+ if (install_plan_.powerwash_required) {
if (utils::CreatePowerwashMarkerFile()) {
powerwash_marker_created_ = true;
} else {
@@ -94,11 +96,9 @@
}
LOG(INFO) << "Postinst command succeeded";
- CHECK(HasInputObject());
- const InstallPlan install_plan = GetInputObject();
-
- if (HasOutputPipe())
- SetOutputObject(install_plan);
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
completer.set_code(kErrorCodeSuccess);
}
diff --git a/postinstall_runner_action.h b/postinstall_runner_action.h
index 6979975..2308a0f 100644
--- a/postinstall_runner_action.h
+++ b/postinstall_runner_action.h
@@ -15,27 +15,10 @@
namespace chromeos_update_engine {
-class PostinstallRunnerAction;
-class NoneType;
-
-template<>
-class ActionTraits<PostinstallRunnerAction> {
+class PostinstallRunnerAction : public InstallPlanAction {
public:
- // Takes the device path as input
- typedef InstallPlan InputObjectType;
- // Passes the device path as output
- typedef InstallPlan OutputObjectType;
-};
+ PostinstallRunnerAction(): powerwash_marker_created_(false) {}
-class PostinstallRunnerAction : public Action<PostinstallRunnerAction> {
- public:
- PostinstallRunnerAction()
- : powerwash_marker_created_(false) {}
-
- typedef ActionTraits<PostinstallRunnerAction>::InputObjectType
- InputObjectType;
- typedef ActionTraits<PostinstallRunnerAction>::OutputObjectType
- OutputObjectType;
void PerformAction();
// Note that there's no support for terminating this action currently.
@@ -52,6 +35,7 @@
const std::string& output,
void* p);
+ InstallPlan install_plan_;
std::string temp_rootfs_dir_;
// True if Powerwash Marker was created before invoking post-install script.
diff --git a/update_attempter.cc b/update_attempter.cc
index 3aa6a1e..3a6988e 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -77,6 +77,8 @@
return "UPDATE_STATUS_UPDATED_NEED_REBOOT";
case UPDATE_STATUS_REPORTING_ERROR_EVENT:
return "UPDATE_STATUS_REPORTING_ERROR_EVENT";
+ case UPDATE_STATUS_ATTEMPTING_ROLLBACK:
+ return "UPDATE_STATUS_ATTEMPTING_ROLLBACK";
default:
return "unknown status";
}
@@ -433,6 +435,15 @@
omaha_request_params_->waiting_period().InSeconds());
}
+void UpdateAttempter::BuildPostInstallActions(
+ InstallPlanAction* previous_action) {
+ shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
+ new PostinstallRunnerAction());
+ actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+ BondActions(previous_action,
+ postinstall_runner_action.get());
+}
+
void UpdateAttempter::BuildUpdateActions(bool interactive) {
CHECK(!processor_->IsRunning());
processor_->set_delegate(this);
@@ -483,8 +494,6 @@
new FilesystemCopierAction(false, true));
shared_ptr<FilesystemCopierAction> kernel_filesystem_verifier_action(
new FilesystemCopierAction(true, true));
- shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
- new PostinstallRunnerAction);
shared_ptr<OmahaRequestAction> update_complete_action(
new OmahaRequestAction(system_state_,
new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
@@ -506,16 +515,8 @@
actions_.push_back(shared_ptr<AbstractAction>(download_action));
actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
- actions_.push_back(shared_ptr<AbstractAction>(
- kernel_filesystem_verifier_action));
- actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
- actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
-
- // Enqueue the actions
- for (vector<shared_ptr<AbstractAction> >::iterator it = actions_.begin();
- it != actions_.end(); ++it) {
- processor_->EnqueueAction(it->get());
- }
+ actions_.push_back(shared_ptr<AbstractAction>(
+ kernel_filesystem_verifier_action));
// Bond them together. We have to use the leaf-types when calling
// BondActions().
@@ -531,8 +532,52 @@
filesystem_verifier_action.get());
BondActions(filesystem_verifier_action.get(),
kernel_filesystem_verifier_action.get());
- BondActions(kernel_filesystem_verifier_action.get(),
- postinstall_runner_action.get());
+
+ BuildPostInstallActions(kernel_filesystem_verifier_action.get());
+
+ actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
+
+ // Enqueue the actions
+ for (vector<shared_ptr<AbstractAction> >::iterator it = actions_.begin();
+ it != actions_.end(); ++it) {
+ processor_->EnqueueAction(it->get());
+ }
+}
+
+void UpdateAttempter::Rollback(bool powerwash) {
+ CHECK(!processor_->IsRunning());
+ processor_->set_delegate(this);
+
+ LOG(INFO) << "Setting rollback options.";
+ InstallPlan install_plan;
+ TEST_AND_RETURN(utils::GetInstallDev(utils::BootDevice(),
+ &install_plan.install_path));
+ install_plan.kernel_install_path = utils::BootKernelDevice(
+ install_plan.install_path);
+ install_plan.powerwash_required = powerwash;
+
+ LOG(INFO) << "Using this install plan:";
+ install_plan.Dump();
+
+ shared_ptr<InstallPlanAction> install_plan_action(
+ new InstallPlanAction(install_plan));
+ actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
+
+ BuildPostInstallActions(install_plan_action.get());
+
+ // Enqueue the actions
+ for (vector<shared_ptr<AbstractAction> >::iterator it = actions_.begin();
+ it != actions_.end(); ++it) {
+ processor_->EnqueueAction(it->get());
+ }
+ SetStatusAndNotify(UPDATE_STATUS_ATTEMPTING_ROLLBACK,
+ kUpdateNoticeUnspecified);
+
+ // Just in case we didn't update boot flags yet, make sure they're updated
+ // before any update processing starts. This also schedules the start of the
+ // actions we just posted.
+ start_action_processor_ = true;
+ UpdateBootFlags();
}
void UpdateAttempter::CheckForUpdate(const string& app_version,
@@ -574,7 +619,6 @@
// Delegate methods:
void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
ErrorCode code) {
- CHECK(response_handler_action_);
LOG(INFO) << "Processing Done.";
actions_.clear();
@@ -1132,4 +1176,5 @@
prefs_->Delete(kPrefsUpdateCheckCount);
return false;
}
+
} // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
index 315e545..d33d69e 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -18,6 +18,7 @@
#include "update_engine/action_processor.h"
#include "update_engine/chrome_browser_proxy_resolver.h"
#include "update_engine/download_action.h"
+#include "update_engine/filesystem_copier_action.h"
#include "update_engine/omaha_request_params.h"
#include "update_engine/omaha_response_handler_action.h"
#include "update_engine/proxy_resolver.h"
@@ -45,6 +46,7 @@
UPDATE_STATUS_FINALIZING,
UPDATE_STATUS_UPDATED_NEED_REBOOT,
UPDATE_STATUS_REPORTING_ERROR_EVENT,
+ UPDATE_STATUS_ATTEMPTING_ROLLBACK
};
enum UpdateNotice {
@@ -142,6 +144,12 @@
const std::string& omaha_url,
bool is_interactive);
+ // This is the internal entry point for going through a rollback. This will
+ // attempt to run the postinstall on the non-active partition and set it as
+ // the partition to boot from. If |powerwash| is True, perform a powerwash
+ // as part of rollback.
+ void Rollback(bool powerwash);
+
// Initiates a reboot if the current state is
// UPDATED_NEED_REBOOT. Returns true on sucess, false otherwise.
bool RebootIfNeeded();
@@ -270,9 +278,15 @@
// scatter factor value specified from policy.
void GenerateNewWaitingPeriod();
+ // Helper method of Update() and Rollback() to construct the sequence of
+ // actions to be performed for the postinstall.
+ // |previous_action| is the previous action to get
+ // bonded with the install_plan that gets passed to postinstall.
+ void BuildPostInstallActions(InstallPlanAction* previous_action);
+
// Helper method of Update() to construct the sequence of actions to
// be performed for an update check. Please refer to
- // Update() method for the meaning of the parametes.
+ // Update() method for the meaning of the parameters.
void BuildUpdateActions(bool interactive);
// Decrements the count in the kUpdateCheckCountFilePath.
diff --git a/update_engine.xml b/update_engine.xml
index 0ce0962..195bde8 100644
--- a/update_engine.xml
+++ b/update_engine.xml
@@ -11,6 +11,9 @@
<arg type="s" name="app_version" />
<arg type="s" name="omaha_url" />
</method>
+ <method name="AttemptRollback">
+ <arg type="b" name="powerwash" />
+ </method>
<method name="ResetStatus">
</method>
<method name="GetStatus">
diff --git a/update_engine_client.cc b/update_engine_client.cc
index e28801c..b4a4bda 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -23,15 +23,17 @@
using std::string;
DEFINE_string(app_version, "", "Force the current app version.");
-DEFINE_bool(check_for_update, false, "Initiate check for updates.");
-DEFINE_string(omaha_url, "", "The URL of the Omaha update server.");
-DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
-DEFINE_bool(show_channel, false, "Show the current and target channels.");
-DEFINE_bool(status, false, "Print the status to stdout.");
-DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
DEFINE_string(channel, "",
"Set the target channel. The device will be powerwashed if the target "
"channel is more stable than the current channel.");
+DEFINE_bool(check_for_update, false, "Initiate check for updates.");
+DEFINE_string(omaha_url, "", "The URL of the Omaha update server.");
+DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
+DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
+DEFINE_bool(rollback, false, "Perform a rollback to the previous partition.");
+DEFINE_bool(show_channel, false, "Show the current and target channels.");
+DEFINE_bool(powerwash, true, "When performing a rollback, do a powerwash.");
+DEFINE_bool(status, false, "Print the status to stdout.");
DEFINE_bool(update, false, "Forces an update and waits for its completion. "
"Exit status is 0 if the update succeeded, and 1 otherwise.");
DEFINE_bool(watch_for_updates, false,
@@ -176,6 +178,21 @@
g_main_loop_unref(loop);
}
+bool Rollback(bool rollback) {
+ DBusGProxy* proxy;
+ GError* error = NULL;
+
+ CHECK(GetProxy(&proxy));
+
+ gboolean rc =
+ org_chromium_UpdateEngineInterface_attempt_rollback(proxy,
+ rollback,
+ &error);
+ CHECK_EQ(rc, TRUE) << "Error with rollback request: "
+ << GetAndFreeGError(&error);
+ return true;
+}
+
bool CheckForUpdates(const string& app_version, const string& omaha_url) {
DBusGProxy* proxy;
GError* error = NULL;
@@ -313,11 +330,27 @@
LOG(INFO) << "Target Channel (pending update): " << target_channel;
}
+ bool do_update_request = FLAGS_check_for_update | FLAGS_update |
+ !FLAGS_app_version.empty() | !FLAGS_omaha_url.empty();
+
+ if (!FLAGS_powerwash && !FLAGS_rollback) {
+ LOG(FATAL) << "Skipping powerwash only works with rollback";
+ return 1;
+ }
+
+ if (do_update_request && FLAGS_rollback) {
+ LOG(FATAL) << "Update should not be requested with rollback!";
+ return 1;
+ }
+
+ if(FLAGS_rollback) {
+ LOG(INFO) << "Requesting rollback.";
+ CHECK(Rollback(FLAGS_powerwash)) << "Request for rollback failed.";
+ return 0;
+ }
+
// Initiate an update check, if necessary.
- if (FLAGS_check_for_update ||
- FLAGS_update ||
- !FLAGS_app_version.empty() ||
- !FLAGS_omaha_url.empty()) {
+ if (do_update_request) {
LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
string app_version = FLAGS_app_version;
if (FLAGS_update && app_version.empty()) {
diff --git a/utils.cc b/utils.cc
index d24a61f..62581f4 100644
--- a/utils.cc
+++ b/utils.cc
@@ -1021,6 +1021,17 @@
return result;
}
+bool GetInstallDev(const std::string& boot_dev, std::string* install_dev) {
+ TEST_AND_RETURN_FALSE(StringHasPrefix(boot_dev, "/dev/"));
+ string ret(boot_dev);
+ string::reverse_iterator it = ret.rbegin(); // last character in string
+ // Right now, we just switch '3' and '5' partition numbers.
+ TEST_AND_RETURN_FALSE((*it == '3') || (*it == '5'));
+ *it = (*it == '3') ? '5' : '3';
+ *install_dev = ret;
+ return true;
+}
+
} // namespace utils
} // namespace chromeos_update_engine
diff --git a/utils.h b/utils.h
index 5193845..0fe2948 100644
--- a/utils.h
+++ b/utils.h
@@ -323,6 +323,13 @@
// Returns true if successfully deleted. False otherwise.
bool DeletePowerwashMarkerFile();
+// Assumes you want to install on the "other" device, where the other
+// device is what you get if you swap 1 for 2 or 3 for 4 or vice versa
+// for the number at the end of the boot device. E.g., /dev/sda1 -> /dev/sda2
+// or /dev/sda4 -> /dev/sda3. See
+// http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
+bool GetInstallDev(const std::string& boot_dev, std::string* install_dev);
+
} // namespace utils
diff --git a/utils_unittest.cc b/utils_unittest.cc
index 4d9dea4..c0e0dfa 100644
--- a/utils_unittest.cc
+++ b/utils_unittest.cc
@@ -283,6 +283,20 @@
EXPECT_EQ(10 * 1024 * 1024 / 4096, block_count);
}
+TEST(UtilsTest, GetInstallDevTest) {
+ string boot_dev = "/dev/sda5";
+ string install_dev;
+ EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+ EXPECT_EQ(install_dev, "/dev/sda3");
+
+ boot_dev = "/dev/sda3";
+ EXPECT_TRUE(utils::GetInstallDev(boot_dev, &install_dev));
+ EXPECT_EQ(install_dev, "/dev/sda5");
+
+ boot_dev = "/dev/sda12";
+ EXPECT_FALSE(utils::GetInstallDev(boot_dev, &install_dev));
+}
+
namespace {
gboolean TerminateScheduleCrashReporterUploadTest(void* arg) {
GMainLoop* loop = reinterpret_cast<GMainLoop*>(arg);