blob: 15169e4fab04d65d367015898b931ac741309d3a [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS 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 "update_engine/postinstall_runner_action.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <memory>
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <gtest/gtest.h>
#include "update_engine/constants.h"
#include "update_engine/test_utils.h"
#include "update_engine/utils.h"
using chromeos_update_engine::test_utils::System;
using chromeos_update_engine::test_utils::WriteFileString;
using std::string;
using std::unique_ptr;
using std::vector;
namespace chromeos_update_engine {
namespace {
gboolean StartProcessorInRunLoop(gpointer data) {
ActionProcessor *processor = reinterpret_cast<ActionProcessor*>(data);
processor->StartProcessing();
return FALSE;
}
} // namespace
class PostinstallRunnerActionTest : public ::testing::Test {
public:
// DoTest with various combinations of do_losetup, err_code and
// powerwash_required.
void DoTest(bool do_losetup, int err_code, bool powerwash_required);
private:
static const char* kImageMountPointTemplate;
};
class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
public:
PostinstActionProcessorDelegate()
: loop_(nullptr),
code_(ErrorCode::kError),
code_set_(false) {}
void ProcessingDone(const ActionProcessor* processor,
ErrorCode code) {
ASSERT_TRUE(loop_);
g_main_loop_quit(loop_);
}
void ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {
if (action->Type() == PostinstallRunnerAction::StaticType()) {
code_ = code;
code_set_ = true;
}
}
GMainLoop* loop_;
ErrorCode code_;
bool code_set_;
};
TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
ASSERT_EQ(0, getuid());
DoTest(true, 0, false);
}
TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
ASSERT_EQ(0, getuid());
DoTest(true, 0, true);
}
TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
ASSERT_EQ(0, getuid());
DoTest(false, 0, true);
}
TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
ASSERT_EQ(0, getuid());
DoTest(true, 1, false);
}
TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
ASSERT_EQ(0, getuid());
DoTest(true, 3, false);
}
TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareROErrScriptTest) {
ASSERT_EQ(0, getuid());
DoTest(true, 4, false);
}
const char* PostinstallRunnerActionTest::kImageMountPointTemplate =
"au_destination-XXXXXX";
void PostinstallRunnerActionTest::DoTest(
bool do_losetup,
int err_code,
bool powerwash_required) {
ASSERT_EQ(0, getuid()) << "Run me as root. Ideally don't run other tests "
<< "as root, tho.";
// True if the post-install action is expected to succeed.
bool should_succeed = do_losetup && !err_code;
string orig_cwd;
{
vector<char> buf(1000);
ASSERT_EQ(&buf[0], getcwd(&buf[0], buf.size()));
orig_cwd = string(&buf[0], strlen(&buf[0]));
}
// Create a unique named working directory and chdir into it.
string cwd;
ASSERT_TRUE(utils::MakeTempDirectory(
"postinstall_runner_action_unittest-XXXXXX",
&cwd));
ASSERT_EQ(0, test_utils::Chdir(cwd));
// Create a 10MiB sparse file to be used as image; format it as ext2.
ASSERT_EQ(0, System(
"dd if=/dev/zero of=image.dat seek=10485759 bs=1 count=1"));
ASSERT_EQ(0, System("mkfs.ext2 -F image.dat"));
// Create a uniquely named image mount point, mount the image.
ASSERT_EQ(0, System(string("mkdir -p ") + kStatefulPartition));
string mountpoint;
ASSERT_TRUE(utils::MakeTempDirectory(
string(kStatefulPartition) + "/" + kImageMountPointTemplate,
&mountpoint));
ASSERT_EQ(0, System(string("mount -o loop image.dat ") + mountpoint));
// Generate a fake postinst script inside the image.
string script = (err_code ?
base::StringPrintf("#!/bin/bash\nexit %d", err_code) :
base::StringPrintf(
"#!/bin/bash\n"
"mount | grep au_postint_mount | grep ext2\n"
"if [ $? -eq 0 ]; then\n"
" touch %s/postinst_called\n"
"fi\n",
cwd.c_str()));
const string script_file_name = mountpoint + "/postinst";
ASSERT_TRUE(WriteFileString(script_file_name, script));
ASSERT_EQ(0, System(string("chmod a+x ") + script_file_name));
// Unmount image; do not remove the uniquely named directory as it will be
// reused during the test.
ASSERT_TRUE(utils::UnmountFilesystem(mountpoint));
// get a loop device we can use for the install device
string dev = "/dev/null";
unique_ptr<test_utils::ScopedLoopbackDeviceBinder> loop_releaser;
if (do_losetup) {
loop_releaser.reset(new test_utils::ScopedLoopbackDeviceBinder(
cwd + "/image.dat", &dev));
}
// We use a test-specific powerwash marker file, to avoid race conditions.
string powerwash_marker_file = mountpoint + "/factory_install_reset";
LOG(INFO) << ">>> powerwash_marker_file=" << powerwash_marker_file;
ActionProcessor processor;
ObjectFeederAction<InstallPlan> feeder_action;
InstallPlan install_plan;
install_plan.install_path = dev;
install_plan.download_url = "http://devserver:8080/update";
install_plan.powerwash_required = powerwash_required;
feeder_action.set_obj(install_plan);
PostinstallRunnerAction runner_action(powerwash_marker_file.c_str());
BondActions(&feeder_action, &runner_action);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&runner_action, &collector_action);
PostinstActionProcessorDelegate delegate;
processor.EnqueueAction(&feeder_action);
processor.EnqueueAction(&runner_action);
processor.EnqueueAction(&collector_action);
processor.set_delegate(&delegate);
GMainLoop* loop = g_main_loop_new(g_main_context_default(), FALSE);
delegate.loop_ = loop;
g_timeout_add(0, &StartProcessorInRunLoop, &processor);
g_main_loop_run(loop);
g_main_loop_unref(loop);
ASSERT_FALSE(processor.IsRunning());
EXPECT_TRUE(delegate.code_set_);
EXPECT_EQ(should_succeed, delegate.code_ == ErrorCode::kSuccess);
EXPECT_EQ(should_succeed, !collector_action.object().install_path.empty());
if (should_succeed)
EXPECT_TRUE(install_plan == collector_action.object());
const base::FilePath kPowerwashMarkerPath(powerwash_marker_file);
string actual_cmd;
if (should_succeed && powerwash_required) {
EXPECT_TRUE(base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
EXPECT_EQ(kPowerwashCommand, actual_cmd);
} else {
EXPECT_FALSE(
base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
}
if (err_code == 2)
EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB, delegate.code_);
struct stat stbuf;
int rc = lstat((string(cwd) + "/postinst_called").c_str(), &stbuf);
if (should_succeed)
ASSERT_EQ(0, rc);
else
ASSERT_LT(rc, 0);
if (do_losetup) {
loop_releaser.reset(nullptr);
}
// Remove unique stateful directory.
ASSERT_EQ(0, System(string("rm -fr ") + mountpoint));
// Remove the temporary work directory.
ASSERT_EQ(0, test_utils::Chdir(orig_cwd));
ASSERT_EQ(0, System(string("rm -fr ") + cwd));
}
// Death tests don't seem to be working on Hardy
TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) {
ASSERT_EQ(0, getuid());
PostinstallRunnerAction runner_action;
ASSERT_DEATH({ runner_action.TerminateProcessing(); },
"postinstall_runner_action.h:.*] Check failed");
}
} // namespace chromeos_update_engine