| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <limits.h> | |
| #include <stdlib.h> | |
| #include <sys/stat.h> | |
| #include <sys/sysmacros.h> | |
| #include <sys/types.h> | |
| #include <time.h> | |
| #include <deque> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/files/file_util.h> | |
| #include <base/logging.h> | |
| #include <base/strings/string_split.h> | |
| #include <base/strings/string_util.h> | |
| #include <base/values.h> | |
| #include <gtest/gtest.h> | |
| #include <libstorage/platform/fake_platform.h> | |
| #include <libstorage/platform/mock_platform.h> | |
| #include "init/startup/chromeos_startup.h" | |
| #include "init/startup/fake_startup_dep_impl.h" | |
| #include "init/startup/standard_mount_helper.h" | |
| #include "init/startup/startup_dep_impl.h" | |
| using testing::_; | |
| using testing::ByMove; | |
| using testing::Return; | |
| using testing::StrictMock; | |
| namespace { | |
| const char kImageVarsContent[] = R"""( | |
| { | |
| "load_base_vars": { | |
| "FORMAT_STATE": "base", | |
| "PLATFORM_FORMAT_STATE": "ext4", | |
| "PLATFORM_OPTIONS_STATE": "", | |
| "PARTITION_NUM_STATE": 1 | |
| }, | |
| "load_partition_vars": { | |
| "FORMAT_STATE": "partition", | |
| "PLATFORM_FORMAT_STATE": "ext4", | |
| "PLATFORM_OPTIONS_STATE": "", | |
| "PARTITION_NUM_STATE": 1 | |
| } | |
| })"""; | |
| constexpr char kStatefulPartition[] = "mnt/stateful_partition"; | |
| } // namespace | |
| class GetImageVarsTest : public ::testing::Test { | |
| protected: | |
| void SetUp() override { | |
| platform_ = std::make_unique<libstorage::FakePlatform>(); | |
| startup_dep_ = std::make_unique<startup::FakeStartupDep>(platform_.get()); | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| json_file_ = base_dir.Append("vars.json"); | |
| ASSERT_TRUE(platform_->WriteStringToFile(json_file_, kImageVarsContent)); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, base_dir, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| } | |
| base::FilePath json_file_; | |
| startup::Flags flags_{}; | |
| std::unique_ptr<startup::StatefulMount> stateful_mount_; | |
| base::FilePath base_dir{"/"}; | |
| std::unique_ptr<libstorage::FakePlatform> platform_; | |
| std::unique_ptr<startup::FakeStartupDep> startup_dep_; | |
| std::unique_ptr<startup::StandardMountHelper> mount_helper_; | |
| }; | |
| TEST_F(GetImageVarsTest, BaseVars) { | |
| std::optional<base::Value> vars = | |
| stateful_mount_->GetImageVars(json_file_, "load_base_vars"); | |
| ASSERT_TRUE(vars); | |
| EXPECT_TRUE(vars->is_dict()); | |
| const std::string* format = vars->GetDict().FindString("FORMAT_STATE"); | |
| EXPECT_NE(format, nullptr); | |
| EXPECT_EQ(*format, "base"); | |
| } | |
| TEST_F(GetImageVarsTest, PartitionVars) { | |
| std::optional<base::Value> vars = | |
| stateful_mount_->GetImageVars(json_file_, "load_partition_vars"); | |
| ASSERT_TRUE(vars); | |
| EXPECT_TRUE(vars->is_dict()); | |
| const std::string* format = vars->GetDict().FindString("FORMAT_STATE"); | |
| LOG(INFO) << "FORMAT_STATE is: " << *format; | |
| EXPECT_NE(format, nullptr); | |
| EXPECT_EQ(*format, "partition"); | |
| } | |
| class Ext4FeaturesTest : public ::testing::Test { | |
| protected: | |
| void SetUp() override { | |
| platform_ = std::make_unique<libstorage::FakePlatform>(); | |
| startup_dep_ = std::make_unique<startup::FakeStartupDep>(platform_.get()); | |
| } | |
| startup::Flags flags_{}; | |
| std::unique_ptr<startup::StatefulMount> stateful_mount_; | |
| base::FilePath base_dir{"/"}; | |
| std::unique_ptr<libstorage::FakePlatform> platform_; | |
| std::unique_ptr<startup::FakeStartupDep> startup_dep_; | |
| std::unique_ptr<startup::StandardMountHelper> mount_helper_; | |
| }; | |
| TEST_F(Ext4FeaturesTest, Encrypt) { | |
| flags_.direncryption = true; | |
| base::FilePath encrypt_file = | |
| base_dir.Append("sys/fs/ext4/features/encryption"); | |
| ASSERT_TRUE(platform_->WriteStringToFile(encrypt_file, "1")); | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, base_dir, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| std::vector<std::string> features = stateful_mount_->GenerateExt4Features(); | |
| std::string features_str = base::JoinString(features, " "); | |
| EXPECT_EQ(features_str, | |
| "-g 20119 -Qusrquota,grpquota -Q^prjquota -O encrypt,quota"); | |
| } | |
| TEST_F(Ext4FeaturesTest, Verity) { | |
| flags_.fsverity = true; | |
| base::FilePath verity_file = base_dir.Append("sys/fs/ext4/features/verity"); | |
| ASSERT_TRUE(platform_->WriteStringToFile(verity_file, "1")); | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, base_dir, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| std::vector<std::string> features = stateful_mount_->GenerateExt4Features(); | |
| std::string features_str = base::JoinString(features, " "); | |
| EXPECT_EQ(features_str, | |
| "-g 20119 -Qusrquota,grpquota -Q^prjquota -O verity,quota"); | |
| } | |
| TEST_F(Ext4FeaturesTest, ReservedBlocksGID) { | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, base_dir, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| std::vector<std::string> features = stateful_mount_->GenerateExt4Features(); | |
| std::string features_str = base::JoinString(features, " "); | |
| EXPECT_EQ(features_str, "-g 20119 -Qusrquota,grpquota -Q^prjquota -O quota"); | |
| } | |
| TEST_F(Ext4FeaturesTest, EnableQuotaWithPrjQuota) { | |
| flags_.prjquota = true; | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, base_dir, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| std::vector<std::string> features = stateful_mount_->GenerateExt4Features(); | |
| std::string features_str = base::JoinString(features, " "); | |
| EXPECT_EQ(features_str, "-g 20119 -Qusrquota,grpquota -Qprjquota -O quota"); | |
| } | |
| TEST_F(Ext4FeaturesTest, EnableQuotaNoPrjQuota) { | |
| flags_.prjquota = false; | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, base_dir, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| std::vector<std::string> features = stateful_mount_->GenerateExt4Features(); | |
| std::string features_str = base::JoinString(features, " "); | |
| EXPECT_EQ(features_str, "-g 20119 -Qusrquota,grpquota -Q^prjquota -O quota"); | |
| } | |
| class DevUpdateStatefulTest : public ::testing::Test { | |
| protected: | |
| void SetUp() override { | |
| stateful = base_dir.Append(kStatefulPartition); | |
| platform_ = std::make_unique<libstorage::FakePlatform>(); | |
| startup_dep_ = std::make_unique<startup::FakeStartupDep>(platform_.get()); | |
| stateful_update_file = stateful.Append(".update_available"); | |
| var_new = stateful.Append("var_new"); | |
| var_target = stateful.Append("var_overlay"); | |
| developer_target = stateful.Append("dev_image"); | |
| developer_new = stateful.Append("dev_image_new"); | |
| preserve_dir = stateful.Append("unencrypted/preserve"); | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, stateful, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| } | |
| base::FilePath base_dir{"/"}; | |
| base::FilePath stateful; | |
| std::unique_ptr<startup::FakeStartupDep> startup_dep_; | |
| startup::Flags flags_{}; | |
| std::unique_ptr<startup::StandardMountHelper> mount_helper_; | |
| std::unique_ptr<libstorage::FakePlatform> platform_; | |
| std::unique_ptr<startup::StatefulMount> stateful_mount_; | |
| base::FilePath stateful_update_file; | |
| base::FilePath var_new; | |
| base::FilePath var_target; | |
| base::FilePath developer_target; | |
| base::FilePath developer_new; | |
| base::FilePath preserve_dir; | |
| }; | |
| TEST_F(DevUpdateStatefulTest, NoUpdateAvailable) { | |
| EXPECT_TRUE(stateful_mount_->DevUpdateStatefulPartition("")); | |
| } | |
| TEST_F(DevUpdateStatefulTest, NewDevAndVarNoClobber) { | |
| ASSERT_TRUE(platform_->CreateDirectory(developer_new)); | |
| ASSERT_TRUE(platform_->CreateDirectory(var_new)); | |
| ASSERT_TRUE(platform_->WriteStringToFile(stateful_update_file, "1")); | |
| LOG(INFO) << "var new test: " << var_new.value(); | |
| LOG(INFO) << "developer_new test: " << developer_new.value(); | |
| ASSERT_TRUE( | |
| platform_->WriteStringToFile(developer_new.Append("dev_new_file"), "1")); | |
| ASSERT_TRUE( | |
| platform_->WriteStringToFile(var_new.Append("var_new_file"), "1")); | |
| ASSERT_TRUE(platform_->WriteStringToFile( | |
| developer_target.Append("dev_target_file"), "1")); | |
| ASSERT_TRUE( | |
| platform_->WriteStringToFile(var_target.Append("var_target_file"), "1")); | |
| EXPECT_TRUE(stateful_mount_->DevUpdateStatefulPartition("")); | |
| EXPECT_FALSE(platform_->FileExists(developer_new.Append("dev_new_file"))); | |
| EXPECT_FALSE(platform_->FileExists(var_new.Append("var_new_file"))); | |
| EXPECT_FALSE( | |
| platform_->FileExists(developer_target.Append("dev_target_file"))); | |
| EXPECT_FALSE(platform_->FileExists(var_target.Append("var_target_file"))); | |
| EXPECT_FALSE(platform_->FileExists(stateful_update_file)); | |
| EXPECT_TRUE(platform_->FileExists(var_target.Append("var_new_file"))); | |
| EXPECT_TRUE(platform_->FileExists(developer_target.Append("dev_new_file"))); | |
| std::string message = | |
| "Updating from " + developer_new.value() + " && " + var_new.value() + "."; | |
| std::string res; | |
| startup_dep_->GetClobberLog(&res); | |
| EXPECT_EQ(res, message); | |
| } | |
| TEST_F(DevUpdateStatefulTest, NoNewDevAndVarWithClobber) { | |
| ASSERT_TRUE(platform_->WriteStringToFile(stateful_update_file, "clobber")); | |
| base::FilePath labmachine = stateful.Append(".labmachine"); | |
| base::FilePath test_dir = stateful.Append("test"); | |
| base::FilePath test = test_dir.Append("test"); | |
| base::FilePath preserve_test = preserve_dir.Append("test"); | |
| base::FilePath empty = stateful.Append("empty"); | |
| ASSERT_TRUE(platform_->CreateDirectory(empty)); | |
| ASSERT_TRUE(platform_->CreateDirectory(test_dir)); | |
| ASSERT_TRUE(platform_->WriteStringToFile( | |
| developer_target.Append("dev_target_file"), "1")); | |
| ASSERT_TRUE( | |
| platform_->WriteStringToFile(var_target.Append("var_target_file"), "1")); | |
| ASSERT_TRUE(platform_->WriteStringToFile(labmachine, "1")); | |
| ASSERT_TRUE(platform_->WriteStringToFile(test, "1")); | |
| ASSERT_TRUE(platform_->WriteStringToFile(preserve_test, "1")); | |
| EXPECT_TRUE(stateful_mount_->DevUpdateStatefulPartition("")); | |
| EXPECT_TRUE( | |
| platform_->FileExists(developer_target.Append("dev_target_file"))); | |
| EXPECT_TRUE(platform_->FileExists(var_target.Append("var_target_file"))); | |
| EXPECT_TRUE(platform_->FileExists(labmachine)); | |
| EXPECT_FALSE(platform_->DirectoryExists(test_dir)); | |
| EXPECT_TRUE(platform_->FileExists(preserve_test)); | |
| EXPECT_FALSE(platform_->FileExists(empty)); | |
| std::string message = "Stateful update did not find " + | |
| developer_new.value() + " & " + var_new.value() + | |
| ".'\n'Keeping old development tools."; | |
| std::string res; | |
| startup_dep_->GetClobberLog(&res); | |
| EXPECT_EQ(res, message); | |
| } | |
| class DevGatherLogsTest : public ::testing::Test { | |
| protected: | |
| void SetUp() override { | |
| stateful = base_dir.Append(kStatefulPartition); | |
| platform_ = std::make_unique<libstorage::FakePlatform>(); | |
| startup_dep_ = std::make_unique<startup::FakeStartupDep>(platform_.get()); | |
| mount_helper_ = std::make_unique<startup::StandardMountHelper>( | |
| platform_.get(), startup_dep_.get(), flags_, base_dir, base_dir); | |
| stateful_mount_ = std::make_unique<startup::StatefulMount>( | |
| flags_, base_dir, stateful, platform_.get(), startup_dep_.get(), | |
| mount_helper_.get()); | |
| lab_preserve_logs_ = stateful.Append(".gatherme"); | |
| prior_log_dir_ = stateful.Append("unencrypted/prior_logs"); | |
| var_dir_ = base_dir.Append("var"); | |
| home_chronos_ = base_dir.Append("home/chronos"); | |
| ASSERT_TRUE(platform_->CreateDirectory(prior_log_dir_)); | |
| ASSERT_TRUE(platform_->CreateDirectory(var_dir_)); | |
| ASSERT_TRUE(platform_->CreateDirectory(home_chronos_)); | |
| } | |
| base::FilePath base_dir{"/"}; | |
| base::FilePath stateful; | |
| base::FilePath lab_preserve_logs_; | |
| base::FilePath prior_log_dir_; | |
| base::FilePath var_dir_; | |
| base::FilePath home_chronos_; | |
| std::unique_ptr<libstorage::FakePlatform> platform_; | |
| std::unique_ptr<startup::FakeStartupDep> startup_dep_; | |
| startup::Flags flags_{}; | |
| std::unique_ptr<startup::StandardMountHelper> mount_helper_; | |
| std::unique_ptr<startup::StatefulMount> stateful_mount_; | |
| }; | |
| TEST_F(DevGatherLogsTest, NoPreserveLogs) { | |
| ASSERT_TRUE(platform_->WriteStringToFile(lab_preserve_logs_, "#")); | |
| stateful_mount_->DevGatherLogs(base_dir); | |
| } | |
| TEST_F(DevGatherLogsTest, PreserveLogs) { | |
| base::FilePath test = base_dir.Append("test"); | |
| base::FilePath test1 = test.Append("test1"); | |
| base::FilePath test2 = test.Append("test2"); | |
| base::FilePath standalone = base_dir.Append("parent/standalone"); | |
| base::FilePath var_logs = base_dir.Append("var/logs"); | |
| base::FilePath log1 = var_logs.Append("log1"); | |
| base::FilePath home_chronos = base_dir.Append("home/chronos/test"); | |
| base::FilePath prior_test = prior_log_dir_.Append("test"); | |
| base::FilePath prior_test1 = prior_test.Append("test1"); | |
| base::FilePath prior_test2 = prior_test.Append("test2"); | |
| base::FilePath prior_standalone = prior_log_dir_.Append("standalone"); | |
| base::FilePath prior_log1 = prior_log_dir_.Append("logs/log1"); | |
| std::string preserve_str("#\n"); | |
| preserve_str.append(test.value()); | |
| preserve_str.append("\n"); | |
| preserve_str.append(standalone.value()); | |
| preserve_str.append("\n#ignore\n\n"); | |
| preserve_str.append(var_logs.value()); | |
| ASSERT_TRUE(platform_->WriteStringToFile(lab_preserve_logs_, preserve_str)); | |
| ASSERT_TRUE(platform_->WriteStringToFile(test1, "#")); | |
| ASSERT_TRUE(platform_->WriteStringToFile(test2, "#")); | |
| ASSERT_TRUE(platform_->WriteStringToFile(standalone, "#")); | |
| ASSERT_TRUE(platform_->WriteStringToFile(log1, "#")); | |
| ASSERT_TRUE(platform_->WriteStringToFile(home_chronos, "#")); | |
| EXPECT_TRUE(platform_->FileExists(home_chronos)); | |
| stateful_mount_->DevGatherLogs(base_dir); | |
| EXPECT_TRUE(platform_->FileExists(prior_test1)); | |
| EXPECT_TRUE(platform_->FileExists(prior_test2)); | |
| EXPECT_TRUE(platform_->FileExists(prior_standalone)); | |
| EXPECT_TRUE(platform_->FileExists(prior_log1)); | |
| EXPECT_TRUE(platform_->FileExists(standalone)); | |
| EXPECT_FALSE(platform_->FileExists(lab_preserve_logs_)); | |
| } |