blob: 9d2b0bbc1c15a6a9225630d0d920e382973754b6 [file] [log] [blame]
// 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 "init/startup/stateful_mount.h"
#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/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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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,
std::unique_ptr<startup::MountVarAndHomeChronosInterface>(),
std::unique_ptr<libstorage::StorageContainerFactory>());
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_));
}