blob: 37cdf3801726276728882ef146547afb1a0dfb13 [file] [log] [blame]
//
// 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/filesystem_verifier_action.h"
#include <fcntl.h>
#include <set>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/posix/eintr_wrapper.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/message_loops/fake_message_loop.h>
#include <chromeos/message_loops/message_loop_utils.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "update_engine/fake_system_state.h"
#include "update_engine/mock_hardware.h"
#include "update_engine/omaha_hash_calculator.h"
#include "update_engine/test_utils.h"
#include "update_engine/utils.h"
using chromeos::MessageLoop;
using std::set;
using std::string;
using std::vector;
namespace chromeos_update_engine {
class FilesystemVerifierActionTest : public ::testing::Test {
protected:
void SetUp() override {
loop_.SetAsCurrent();
}
void TearDown() override {
EXPECT_EQ(0, chromeos::MessageLoopRunMaxIterations(&loop_, 1));
}
// Returns true iff test has completed successfully.
bool DoTest(bool terminate_early,
bool hash_fail,
PartitionType partition_type);
chromeos::FakeMessageLoop loop_{nullptr};
FakeSystemState fake_system_state_;
};
class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
public:
explicit FilesystemVerifierActionTestDelegate(
FilesystemVerifierAction* action)
: action_(action), ran_(false), code_(ErrorCode::kError) {}
void ExitMainLoop() {
// We need to wait for the Action to call Cleanup.
if (action_->IsCleanupPending()) {
LOG(INFO) << "Waiting for Cleanup() to be called.";
MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&FilesystemVerifierActionTestDelegate::ExitMainLoop,
base::Unretained(this)),
base::TimeDelta::FromMilliseconds(100));
} else {
MessageLoop::current()->BreakLoop();
}
}
void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
ExitMainLoop();
}
void ProcessingStopped(const ActionProcessor* processor) {
ExitMainLoop();
}
void ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {
if (action->Type() == FilesystemVerifierAction::StaticType()) {
ran_ = true;
code_ = code;
}
}
bool ran() const { return ran_; }
ErrorCode code() const { return code_; }
private:
FilesystemVerifierAction* action_;
bool ran_;
ErrorCode code_;
};
void StartProcessorInRunLoop(ActionProcessor* processor,
FilesystemVerifierAction* filesystem_copier_action,
bool terminate_early) {
processor->StartProcessing();
if (terminate_early) {
EXPECT_NE(nullptr, filesystem_copier_action);
processor->StopProcessing();
}
}
// TODO(garnold) Temporarily disabling this test, see chromium-os:31082 for
// details; still trying to track down the root cause for these rare write
// failures and whether or not they are due to the test setup or an inherent
// issue with the chroot environment, library versions we use, etc.
TEST_F(FilesystemVerifierActionTest, DISABLED_RunAsRootSimpleTest) {
ASSERT_EQ(0, getuid());
bool test = DoTest(false, false, PartitionType::kKernel);
EXPECT_TRUE(test);
if (!test)
return;
test = DoTest(false, false, PartitionType::kRootfs);
EXPECT_TRUE(test);
}
bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
bool hash_fail,
PartitionType partition_type) {
// We need MockHardware to verify MarkUnbootable calls, but don't want
// warnings about other usages.
testing::NiceMock<MockHardware> mock_hardware;
fake_system_state_.set_hardware(&mock_hardware);
string a_loop_file;
if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
ADD_FAILURE();
return false;
}
ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
// Make random data for a.
const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
chromeos::Blob a_loop_data(kLoopFileSize);
test_utils::FillWithData(&a_loop_data);
// Write data to disk
if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
ADD_FAILURE();
return false;
}
// Attach loop devices to the files
string a_dev;
test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(a_loop_file, &a_dev);
if (!(a_dev_releaser.is_bound())) {
ADD_FAILURE();
return false;
}
LOG(INFO) << "verifying: " << a_loop_file << " (" << a_dev << ")";
bool success = true;
// Set up the action objects
InstallPlan install_plan;
switch (partition_type) {
case PartitionType::kRootfs:
install_plan.rootfs_size = kLoopFileSize - (hash_fail ? 1 : 0);
install_plan.install_path = a_dev;
if (!OmahaHashCalculator::RawHashOfData(
a_loop_data, &install_plan.rootfs_hash)) {
ADD_FAILURE();
success = false;
}
break;
case PartitionType::kKernel:
install_plan.kernel_size = kLoopFileSize - (hash_fail ? 1 : 0);
install_plan.kernel_install_path = a_dev;
if (!OmahaHashCalculator::RawHashOfData(
a_loop_data, &install_plan.kernel_hash)) {
ADD_FAILURE();
success = false;
}
break;
case PartitionType::kSourceRootfs:
install_plan.source_path = a_dev;
if (!OmahaHashCalculator::RawHashOfData(
a_loop_data, &install_plan.source_rootfs_hash)) {
ADD_FAILURE();
success = false;
}
break;
case PartitionType::kSourceKernel:
install_plan.kernel_source_path = a_dev;
if (!OmahaHashCalculator::RawHashOfData(
a_loop_data, &install_plan.source_kernel_hash)) {
ADD_FAILURE();
success = false;
}
break;
}
EXPECT_CALL(mock_hardware,
MarkKernelUnbootable(a_dev)).Times(
partition_type == PartitionType::kKernel ? 1 : 0);
ActionProcessor processor;
ObjectFeederAction<InstallPlan> feeder_action;
FilesystemVerifierAction copier_action(&fake_system_state_, partition_type);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&feeder_action, &copier_action);
BondActions(&copier_action, &collector_action);
FilesystemVerifierActionTestDelegate delegate(&copier_action);
processor.set_delegate(&delegate);
processor.EnqueueAction(&feeder_action);
processor.EnqueueAction(&copier_action);
processor.EnqueueAction(&collector_action);
feeder_action.set_obj(install_plan);
loop_.PostTask(FROM_HERE, base::Bind(&StartProcessorInRunLoop,
&processor,
&copier_action,
terminate_early));
loop_.Run();
if (!terminate_early) {
bool is_delegate_ran = delegate.ran();
EXPECT_TRUE(is_delegate_ran);
success = success && is_delegate_ran;
} else {
EXPECT_EQ(ErrorCode::kError, delegate.code());
return (ErrorCode::kError == delegate.code());
}
if (hash_fail) {
ErrorCode expected_exit_code =
((partition_type == PartitionType::kKernel ||
partition_type == PartitionType::kSourceKernel) ?
ErrorCode::kNewKernelVerificationError :
ErrorCode::kNewRootfsVerificationError);
EXPECT_EQ(expected_exit_code, delegate.code());
return (expected_exit_code == delegate.code());
}
EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
// Make sure everything in the out_image is there
chromeos::Blob a_out;
if (!utils::ReadFile(a_dev, &a_out)) {
ADD_FAILURE();
return false;
}
const bool is_a_file_reading_eq =
test_utils::ExpectVectorsEq(a_loop_data, a_out);
EXPECT_TRUE(is_a_file_reading_eq);
success = success && is_a_file_reading_eq;
bool is_install_plan_eq = (collector_action.object() == install_plan);
EXPECT_TRUE(is_install_plan_eq);
success = success && is_install_plan_eq;
LOG(INFO) << "Verifying bootable flag on: " << a_dev;
bool bootable;
EXPECT_TRUE(mock_hardware.fake().IsKernelBootable(a_dev, &bootable));
// We should always mark a partition as unbootable if it's a kernel
// partition, but never if it's anything else.
EXPECT_EQ(bootable, (partition_type != PartitionType::kKernel));
return success;
}
class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
public:
void ActionCompleted(ActionProcessor* processor,
AbstractAction* action,
ErrorCode code) {
if (action->Type() == FilesystemVerifierAction::StaticType()) {
ran_ = true;
code_ = code;
}
}
bool ran_;
ErrorCode code_;
};
TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
ActionProcessor processor;
FilesystemVerifierActionTest2Delegate delegate;
processor.set_delegate(&delegate);
FilesystemVerifierAction copier_action(&fake_system_state_,
PartitionType::kRootfs);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&copier_action, &collector_action);
processor.EnqueueAction(&copier_action);
processor.EnqueueAction(&collector_action);
processor.StartProcessing();
EXPECT_FALSE(processor.IsRunning());
EXPECT_TRUE(delegate.ran_);
EXPECT_EQ(ErrorCode::kError, delegate.code_);
}
TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
ActionProcessor processor;
FilesystemVerifierActionTest2Delegate delegate;
processor.set_delegate(&delegate);
ObjectFeederAction<InstallPlan> feeder_action;
InstallPlan install_plan(false,
false,
"",
0,
"",
0,
"",
"/no/such/file",
"/no/such/file",
"/no/such/file",
"/no/such/file",
"");
feeder_action.set_obj(install_plan);
FilesystemVerifierAction verifier_action(&fake_system_state_,
PartitionType::kRootfs);
ObjectCollectorAction<InstallPlan> collector_action;
BondActions(&verifier_action, &collector_action);
processor.EnqueueAction(&feeder_action);
processor.EnqueueAction(&verifier_action);
processor.EnqueueAction(&collector_action);
processor.StartProcessing();
EXPECT_FALSE(processor.IsRunning());
EXPECT_TRUE(delegate.ran_);
EXPECT_EQ(ErrorCode::kError, delegate.code_);
}
TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
ASSERT_EQ(0, getuid());
EXPECT_TRUE(DoTest(false, false, PartitionType::kRootfs));
EXPECT_TRUE(DoTest(false, false, PartitionType::kKernel));
EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceRootfs));
EXPECT_TRUE(DoTest(false, false, PartitionType::kSourceKernel));
}
TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
ASSERT_EQ(0, getuid());
EXPECT_TRUE(DoTest(false, true, PartitionType::kRootfs));
EXPECT_TRUE(DoTest(false, true, PartitionType::kKernel));
}
TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
ASSERT_EQ(0, getuid());
EXPECT_TRUE(DoTest(true, false, PartitionType::kKernel));
// TerminateEarlyTest may leak some null callbacks from the Stream class.
while (loop_.RunOnce(false)) {}
}
TEST_F(FilesystemVerifierActionTest, RunAsRootDetermineFilesystemSizeTest) {
string img;
EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
ScopedPathUnlinker img_unlinker(img);
test_utils::CreateExtImageAtPath(img, nullptr);
// Extend the "partition" holding the file system from 10MiB to 20MiB.
EXPECT_EQ(0, truncate(img.c_str(), 20 * 1024 * 1024));
{
FilesystemVerifierAction action(&fake_system_state_,
PartitionType::kSourceKernel);
EXPECT_EQ(kint64max, action.remaining_size_);
action.DetermineFilesystemSize(img);
EXPECT_EQ(kint64max, action.remaining_size_);
}
{
FilesystemVerifierAction action(&fake_system_state_,
PartitionType::kSourceRootfs);
action.DetermineFilesystemSize(img);
EXPECT_EQ(10 * 1024 * 1024, action.remaining_size_);
}
}
} // namespace chromeos_update_engine