474 lines
18 KiB
C++
474 lines
18 KiB
C++
//
|
|
// Copyright (C) 2012 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#include "update_engine/payload_consumer/postinstall_runner_action.h"
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include <base/bind.h>
|
|
#include <base/files/file_util.h>
|
|
#if BASE_VER < 780000 // Android
|
|
#include <base/message_loop/message_loop.h>
|
|
#endif // BASE_VER < 780000
|
|
#include <base/strings/string_util.h>
|
|
#include <base/strings/stringprintf.h>
|
|
#if BASE_VER >= 780000 // CrOS
|
|
#include <base/task/single_thread_task_executor.h>
|
|
#endif // BASE_VER >= 780000
|
|
#include <brillo/message_loops/base_message_loop.h>
|
|
#include <brillo/message_loops/message_loop_utils.h>
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "update_engine/common/constants.h"
|
|
#include "update_engine/common/fake_boot_control.h"
|
|
#include "update_engine/common/fake_hardware.h"
|
|
#include "update_engine/common/subprocess.h"
|
|
#include "update_engine/common/test_utils.h"
|
|
#include "update_engine/common/utils.h"
|
|
|
|
using brillo::MessageLoop;
|
|
using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
|
|
using std::string;
|
|
|
|
namespace chromeos_update_engine {
|
|
|
|
class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
|
|
public:
|
|
PostinstActionProcessorDelegate() = default;
|
|
void ProcessingDone(const ActionProcessor* processor,
|
|
ErrorCode code) override {
|
|
MessageLoop::current()->BreakLoop();
|
|
processing_done_called_ = true;
|
|
}
|
|
void ProcessingStopped(const ActionProcessor* processor) override {
|
|
MessageLoop::current()->BreakLoop();
|
|
processing_stopped_called_ = true;
|
|
}
|
|
|
|
void ActionCompleted(ActionProcessor* processor,
|
|
AbstractAction* action,
|
|
ErrorCode code) override {
|
|
if (action->Type() == PostinstallRunnerAction::StaticType()) {
|
|
code_ = code;
|
|
code_set_ = true;
|
|
}
|
|
}
|
|
|
|
ErrorCode code_{ErrorCode::kError};
|
|
bool code_set_{false};
|
|
bool processing_done_called_{false};
|
|
bool processing_stopped_called_{false};
|
|
};
|
|
|
|
class MockPostinstallRunnerActionDelegate
|
|
: public PostinstallRunnerAction::DelegateInterface {
|
|
public:
|
|
MOCK_METHOD1(ProgressUpdate, void(double progress));
|
|
};
|
|
|
|
class PostinstallRunnerActionTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
loop_.SetAsCurrent();
|
|
async_signal_handler_.Init();
|
|
subprocess_.Init(&async_signal_handler_);
|
|
// These tests use the postinstall files generated by "generate_images.sh"
|
|
// stored in the "disk_ext2_unittest.img" image.
|
|
postinstall_image_ =
|
|
test_utils::GetBuildArtifactsPath("gen/disk_ext2_unittest.img");
|
|
}
|
|
|
|
// Setup an action processor and run the PostinstallRunnerAction with a single
|
|
// partition |device_path|, running the |postinstall_program| command from
|
|
// there.
|
|
void RunPostinstallAction(const string& device_path,
|
|
const string& postinstall_program,
|
|
bool powerwash_required,
|
|
bool is_rollback,
|
|
bool save_rollback_data);
|
|
|
|
void RunPostinstallActionWithInstallPlan(const InstallPlan& install_plan);
|
|
|
|
public:
|
|
void ResumeRunningAction() {
|
|
ASSERT_NE(nullptr, postinstall_action_);
|
|
postinstall_action_->ResumeAction();
|
|
}
|
|
|
|
void SuspendRunningAction() {
|
|
if (!postinstall_action_ || !postinstall_action_->current_command_ ||
|
|
test_utils::Readlink(base::StringPrintf(
|
|
"/proc/%d/fd/0", postinstall_action_->current_command_)) !=
|
|
"/dev/zero") {
|
|
// We need to wait for the postinstall command to start and flag that it
|
|
// is ready by redirecting its input to /dev/zero.
|
|
loop_.PostDelayedTask(
|
|
FROM_HERE,
|
|
base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
|
|
base::Unretained(this)),
|
|
base::TimeDelta::FromMilliseconds(100));
|
|
} else {
|
|
postinstall_action_->SuspendAction();
|
|
// Schedule to be resumed in a little bit.
|
|
loop_.PostDelayedTask(
|
|
FROM_HERE,
|
|
base::Bind(&PostinstallRunnerActionTest::ResumeRunningAction,
|
|
base::Unretained(this)),
|
|
base::TimeDelta::FromMilliseconds(100));
|
|
}
|
|
}
|
|
|
|
void CancelWhenStarted() {
|
|
if (!postinstall_action_ || !postinstall_action_->current_command_) {
|
|
// Wait for the postinstall command to run.
|
|
loop_.PostDelayedTask(
|
|
FROM_HERE,
|
|
base::Bind(&PostinstallRunnerActionTest::CancelWhenStarted,
|
|
base::Unretained(this)),
|
|
base::TimeDelta::FromMilliseconds(10));
|
|
} else {
|
|
CHECK(processor_);
|
|
// Must |PostDelayedTask()| here to be safe that |FileDescriptorWatcher|
|
|
// doesn't leak memory, do not directly call |StopProcessing()|.
|
|
loop_.PostDelayedTask(
|
|
FROM_HERE,
|
|
base::Bind(
|
|
[](ActionProcessor* processor) { processor->StopProcessing(); },
|
|
base::Unretained(processor_)),
|
|
base::TimeDelta::FromMilliseconds(100));
|
|
}
|
|
}
|
|
|
|
protected:
|
|
#if BASE_VER < 780000 // Android
|
|
base::MessageLoopForIO base_loop_;
|
|
brillo::BaseMessageLoop loop_{&base_loop_};
|
|
#else // CrOS
|
|
base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
|
|
brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
|
|
#endif // BASE_VER < 780000
|
|
brillo::AsynchronousSignalHandler async_signal_handler_;
|
|
Subprocess subprocess_;
|
|
|
|
// The path to the postinstall sample image.
|
|
string postinstall_image_;
|
|
|
|
FakeBootControl fake_boot_control_;
|
|
FakeHardware fake_hardware_;
|
|
PostinstActionProcessorDelegate processor_delegate_;
|
|
|
|
// The PostinstallRunnerAction delegate receiving the progress updates.
|
|
PostinstallRunnerAction::DelegateInterface* setup_action_delegate_{nullptr};
|
|
|
|
// A pointer to the posinstall_runner action and the processor.
|
|
PostinstallRunnerAction* postinstall_action_{nullptr};
|
|
ActionProcessor* processor_{nullptr};
|
|
};
|
|
|
|
void PostinstallRunnerActionTest::RunPostinstallAction(
|
|
const string& device_path,
|
|
const string& postinstall_program,
|
|
bool powerwash_required,
|
|
bool is_rollback,
|
|
bool save_rollback_data) {
|
|
InstallPlan::Partition part;
|
|
part.name = "part";
|
|
part.target_path = device_path;
|
|
part.readonly_target_path = device_path;
|
|
part.run_postinstall = true;
|
|
part.postinstall_path = postinstall_program;
|
|
InstallPlan install_plan;
|
|
install_plan.partitions = {part};
|
|
install_plan.download_url = "http://127.0.0.1:8080/update";
|
|
install_plan.powerwash_required = powerwash_required;
|
|
install_plan.is_rollback = is_rollback;
|
|
install_plan.rollback_data_save_requested = save_rollback_data;
|
|
RunPostinstallActionWithInstallPlan(install_plan);
|
|
}
|
|
|
|
void PostinstallRunnerActionTest::RunPostinstallActionWithInstallPlan(
|
|
const chromeos_update_engine::InstallPlan& install_plan) {
|
|
ActionProcessor processor;
|
|
processor_ = &processor;
|
|
auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
|
|
feeder_action->set_obj(install_plan);
|
|
auto runner_action = std::make_unique<PostinstallRunnerAction>(
|
|
&fake_boot_control_, &fake_hardware_);
|
|
postinstall_action_ = runner_action.get();
|
|
base::FilePath temp_dir;
|
|
TEST_AND_RETURN(base::CreateNewTempDirectory("postinstall", &temp_dir));
|
|
postinstall_action_->SetMountDir(temp_dir.value());
|
|
runner_action->set_delegate(setup_action_delegate_);
|
|
BondActions(feeder_action.get(), runner_action.get());
|
|
auto collector_action =
|
|
std::make_unique<ObjectCollectorAction<InstallPlan>>();
|
|
BondActions(runner_action.get(), collector_action.get());
|
|
processor.EnqueueAction(std::move(feeder_action));
|
|
processor.EnqueueAction(std::move(runner_action));
|
|
processor.EnqueueAction(std::move(collector_action));
|
|
processor.set_delegate(&processor_delegate_);
|
|
|
|
loop_.PostTask(
|
|
FROM_HERE,
|
|
base::Bind(
|
|
[](ActionProcessor* processor) { processor->StartProcessing(); },
|
|
base::Unretained(&processor)));
|
|
loop_.Run();
|
|
ASSERT_FALSE(processor.IsRunning());
|
|
postinstall_action_ = nullptr;
|
|
processor_ = nullptr;
|
|
EXPECT_TRUE(processor_delegate_.processing_stopped_called_ ||
|
|
processor_delegate_.processing_done_called_);
|
|
if (processor_delegate_.processing_done_called_) {
|
|
// Validation check that the code was set when the processor finishes.
|
|
EXPECT_TRUE(processor_delegate_.code_set_);
|
|
}
|
|
}
|
|
|
|
TEST_F(PostinstallRunnerActionTest, ProcessProgressLineTest) {
|
|
PostinstallRunnerAction action(&fake_boot_control_, &fake_hardware_);
|
|
testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_;
|
|
action.set_delegate(&mock_delegate_);
|
|
|
|
action.current_partition_ = 1;
|
|
action.partition_weight_ = {1, 2, 5};
|
|
action.accumulated_weight_ = 1;
|
|
action.total_weight_ = 8;
|
|
|
|
// 50% of the second action is 2/8 = 0.25 of the total.
|
|
EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25));
|
|
action.ProcessProgressLine("global_progress 0.5");
|
|
testing::Mock::VerifyAndClearExpectations(&mock_delegate_);
|
|
|
|
// 1.5 should be read as 100%, to catch rounding error cases like 1.000001.
|
|
// 100% of the second is 3/8 of the total.
|
|
EXPECT_CALL(mock_delegate_, ProgressUpdate(0.375));
|
|
action.ProcessProgressLine("global_progress 1.5");
|
|
testing::Mock::VerifyAndClearExpectations(&mock_delegate_);
|
|
|
|
// None of these should trigger a progress update.
|
|
action.ProcessProgressLine("foo_bar");
|
|
action.ProcessProgressLine("global_progress");
|
|
action.ProcessProgressLine("global_progress ");
|
|
action.ProcessProgressLine("global_progress NaN");
|
|
action.ProcessProgressLine("global_progress Exception in ... :)");
|
|
}
|
|
|
|
// Test that postinstall succeeds in the simple case of running the default
|
|
// /postinst command which only exits 0.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
|
|
RunPostinstallAction(
|
|
loop.dev(), kPostinstallDefaultScript, false, false, false);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
EXPECT_TRUE(processor_delegate_.processing_done_called_);
|
|
|
|
// Since powerwash_required was false, this should not trigger a powerwash.
|
|
EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
|
|
EXPECT_FALSE(fake_hardware_.GetIsRollbackPowerwashScheduled());
|
|
}
|
|
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
RunPostinstallAction(loop.dev(), "bin/postinst_link", false, false, false);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
}
|
|
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
// Run a simple postinstall program but requiring a powerwash.
|
|
RunPostinstallAction(loop.dev(),
|
|
"bin/postinst_example",
|
|
/*powerwash_required=*/true,
|
|
false,
|
|
false);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
|
|
// Check that powerwash was scheduled.
|
|
EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
|
|
EXPECT_FALSE(fake_hardware_.GetIsRollbackPowerwashScheduled());
|
|
}
|
|
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTestNoDataSave) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
|
|
// Run a simple postinstall program, rollback happened.
|
|
RunPostinstallAction(loop.dev(),
|
|
"bin/postinst_example",
|
|
false,
|
|
/*is_rollback=*/true,
|
|
/*save_rollback_data=*/false);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
|
|
// Check that powerwash was scheduled and that it's NOT a rollback powerwash.
|
|
EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
|
|
EXPECT_FALSE(fake_hardware_.GetIsRollbackPowerwashScheduled());
|
|
}
|
|
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTestWithDataSave) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
|
|
// Run a simple postinstall program, rollback happened.
|
|
RunPostinstallAction(loop.dev(),
|
|
"bin/postinst_example",
|
|
false,
|
|
/*is_rollback=*/true,
|
|
/*save_rollback_data=*/true);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
|
|
// Check that powerwash was scheduled and that it's a rollback powerwash.
|
|
EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
|
|
EXPECT_TRUE(fake_hardware_.GetIsRollbackPowerwashScheduled());
|
|
}
|
|
|
|
// Runs postinstall from a partition file that doesn't mount, so it should
|
|
// fail.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
|
|
RunPostinstallAction(
|
|
"/dev/null", kPostinstallDefaultScript, false, false, false);
|
|
EXPECT_EQ(ErrorCode::kPostInstallMountError, processor_delegate_.code_);
|
|
|
|
// In case of failure, Postinstall should not signal a powerwash even if it
|
|
// was requested.
|
|
EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled());
|
|
EXPECT_FALSE(fake_hardware_.GetIsRollbackPowerwashScheduled());
|
|
}
|
|
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootSkipOptionalPostinstallTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
InstallPlan::Partition part;
|
|
part.name = "part";
|
|
part.target_path = "/dev/null";
|
|
part.readonly_target_path = loop.dev();
|
|
part.run_postinstall = true;
|
|
part.postinstall_path = "non_existent_path";
|
|
part.postinstall_optional = true;
|
|
InstallPlan install_plan;
|
|
install_plan.partitions = {part};
|
|
install_plan.download_url = "http://127.0.0.1:8080/update";
|
|
|
|
// Optional postinstalls will be skipped, and the postinstall action succeeds.
|
|
RunPostinstallActionWithInstallPlan(install_plan);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
|
|
part.postinstall_optional = false;
|
|
install_plan.partitions = {part};
|
|
RunPostinstallActionWithInstallPlan(install_plan);
|
|
EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
|
|
}
|
|
|
|
// Check that the failures from the postinstall script cause the action to
|
|
// fail.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
RunPostinstallAction(loop.dev(), "bin/postinst_fail1", false, false, false);
|
|
EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
|
|
}
|
|
|
|
// The exit code 3 and 4 are a specials cases that would be reported back to
|
|
// UMA with a different error code. Test those cases are properly detected.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
RunPostinstallAction(loop.dev(), "bin/postinst_fail3", false, false, false);
|
|
EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB,
|
|
processor_delegate_.code_);
|
|
}
|
|
|
|
// Check that you can't specify an absolute path.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
RunPostinstallAction(loop.dev(), "/etc/../bin/sh", false, false, false);
|
|
EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
|
|
}
|
|
|
|
#ifdef __ANDROID__
|
|
// Check that the postinstall file is labeled to the postinstall_exec label.
|
|
// SElinux labels are only set on Android.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
RunPostinstallAction(
|
|
loop.dev(), "bin/self_check_context", false, false, false);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
}
|
|
|
|
// Check that the postinstall file is relabeled to the default postinstall
|
|
// label. SElinux labels are only set on Android.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootCheckDefaultFileContextsTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
RunPostinstallAction(
|
|
loop.dev(), "bin/self_check_default_context", false, false, false);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
}
|
|
#endif // __ANDROID__
|
|
|
|
// Check that you can suspend/resume postinstall actions.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootSuspendResumeActionTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
|
|
// We need to wait for the child to run and setup its signal handler.
|
|
loop_.PostTask(FROM_HERE,
|
|
base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
|
|
base::Unretained(this)));
|
|
RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false, false);
|
|
// postinst_suspend returns 0 only if it was suspended at some point.
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
EXPECT_TRUE(processor_delegate_.processing_done_called_);
|
|
}
|
|
|
|
// Test that we can cancel a postinstall action while it is running.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootCancelPostinstallActionTest) {
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
|
|
// Wait for the action to start and then cancel it.
|
|
CancelWhenStarted();
|
|
RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false, false);
|
|
// When canceling the action, the action never finished and therefore we had
|
|
// a ProcessingStopped call instead.
|
|
EXPECT_FALSE(processor_delegate_.code_set_);
|
|
EXPECT_TRUE(processor_delegate_.processing_stopped_called_);
|
|
}
|
|
|
|
// Test that we parse and process the progress reports from the progress
|
|
// file descriptor.
|
|
TEST_F(PostinstallRunnerActionTest, RunAsRootProgressUpdatesTest) {
|
|
testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_;
|
|
testing::InSequence s;
|
|
EXPECT_CALL(mock_delegate_, ProgressUpdate(0));
|
|
|
|
// The postinst_progress program will call with 0.25, 0.5 and 1.
|
|
EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25));
|
|
EXPECT_CALL(mock_delegate_, ProgressUpdate(0.5));
|
|
EXPECT_CALL(mock_delegate_, ProgressUpdate(1.));
|
|
|
|
EXPECT_CALL(mock_delegate_, ProgressUpdate(1.));
|
|
|
|
ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
|
|
setup_action_delegate_ = &mock_delegate_;
|
|
RunPostinstallAction(
|
|
loop.dev(), "bin/postinst_progress", false, false, false);
|
|
EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
|
|
}
|
|
|
|
} // namespace chromeos_update_engine
|