/*
 * Copyright (C) 2018 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 <linux/fs.h>
#include <mntent.h>

#include <algorithm>
#include <iterator>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <fs_mgr.h>
#include <fstab/fstab.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include "../fs_mgr_priv.h"

using namespace android::fs_mgr;
using namespace testing;

namespace {

const std::string cmdline =
        "rcupdate.rcu_expedited=1 rootwait ro "
        "init=/init androidboot.bootdevice=1d84000.ufshc "
        "androidboot.baseband=sdy androidboot.keymaster=1  skip_initramfs "
        "androidboot.serialno=BLAHBLAHBLAH androidboot.slot_suffix=_a "
        "androidboot.hardware.platform=sdw813 androidboot.hardware=foo "
        "androidboot.revision=EVT1.0 androidboot.bootloader=burp-0.1-7521 "
        "androidboot.hardware.sku=mary androidboot.hardware.radio.subtype=0 "
        "androidboot.dtbo_idx=2 androidboot.mode=normal "
        "androidboot.hardware.ddr=1GB,combuchi,LPDDR4X "
        "androidboot.ddr_info=combuchiandroidboot.ddr_size=2GB "
        "androidboot.hardware.ufs=2GB,combushi "
        "androidboot.boottime=0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123 "
        "androidboot.ramdump=disabled "
        "dm=\"1 vroot none ro 1,0 10416 verity 1 624684 fec_start 624684\" "
        "root=/dev/dm-0 "
        "androidboot.vbmeta.device=PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb "
        "androidboot.vbmeta.avb_version=\"1.1\" "
        "androidboot.vbmeta.device_state=unlocked "
        "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=5248 "
        "androidboot.vbmeta.digest="
        "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860 "
        "androidboot.vbmeta.invalidate_on_error=yes "
        "androidboot.veritymode=enforcing androidboot.verifiedbootstate=orange "
        "androidboot.space=\"sha256 5248 androidboot.nospace=nope\" "
        "printk.devkmsg=on msm_rtb.filter=0x237 ehci-hcd.park=3 "
        "\"string =\"\"string '\" "
        "service_locator.enable=1 firmware_class.path=/vendor/firmware "
        "cgroup.memory=nokmem lpm_levels.sleep_disabled=1 "
        "buildvariant=userdebug  console=null "
        "terminator=\"truncated";

const std::vector<std::pair<std::string, std::string>> result_space = {
        {"rcupdate.rcu_expedited", "1"},
        {"rootwait", ""},
        {"ro", ""},
        {"init", "/init"},
        {"androidboot.bootdevice", "1d84000.ufshc"},
        {"androidboot.baseband", "sdy"},
        {"androidboot.keymaster", "1"},
        {"skip_initramfs", ""},
        {"androidboot.serialno", "BLAHBLAHBLAH"},
        {"androidboot.slot_suffix", "_a"},
        {"androidboot.hardware.platform", "sdw813"},
        {"androidboot.hardware", "foo"},
        {"androidboot.revision", "EVT1.0"},
        {"androidboot.bootloader", "burp-0.1-7521"},
        {"androidboot.hardware.sku", "mary"},
        {"androidboot.hardware.radio.subtype", "0"},
        {"androidboot.dtbo_idx", "2"},
        {"androidboot.mode", "normal"},
        {"androidboot.hardware.ddr", "1GB,combuchi,LPDDR4X"},
        {"androidboot.ddr_info", "combuchiandroidboot.ddr_size=2GB"},
        {"androidboot.hardware.ufs", "2GB,combushi"},
        {"androidboot.boottime", "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123"},
        {"androidboot.ramdump", "disabled"},
        {"dm", "1 vroot none ro 1,0 10416 verity 1 624684 fec_start 624684"},
        {"root", "/dev/dm-0"},
        {"androidboot.vbmeta.device", "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb"},
        {"androidboot.vbmeta.avb_version", "1.1"},
        {"androidboot.vbmeta.device_state", "unlocked"},
        {"androidboot.vbmeta.hash_alg", "sha256"},
        {"androidboot.vbmeta.size", "5248"},
        {"androidboot.vbmeta.digest",
         "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860"},
        {"androidboot.vbmeta.invalidate_on_error", "yes"},
        {"androidboot.veritymode", "enforcing"},
        {"androidboot.verifiedbootstate", "orange"},
        {"androidboot.space", "sha256 5248 androidboot.nospace=nope"},
        {"printk.devkmsg", "on"},
        {"msm_rtb.filter", "0x237"},
        {"ehci-hcd.park", "3"},
        {"string ", "string '"},
        {"service_locator.enable", "1"},
        {"firmware_class.path", "/vendor/firmware"},
        {"cgroup.memory", "nokmem"},
        {"lpm_levels.sleep_disabled", "1"},
        {"buildvariant", "userdebug"},
        {"console", "null"},
        {"terminator", "truncated"},
};

const std::string bootconfig = R"(
androidboot.bootdevice = "1d84000.ufshc"
androidboot.boot_devices = "dev1", "dev2,withcomma", "dev3"
androidboot.baseband = "sdy"
androidboot.keymaster = "1"
androidboot.serialno = "BLAHBLAHBLAH"
androidboot.slot_suffix = "_a"
androidboot.hardware.platform = "sdw813"
androidboot.hardware = "foo"
androidboot.revision = "EVT1.0"
androidboot.bootloader = "burp-0.1-7521"
androidboot.hardware.sku = "mary"
androidboot.hardware.radio.subtype = "0"
androidboot.dtbo_idx = "2"
androidboot.mode = "normal"
androidboot.hardware.ddr = "1GB,combuchi,LPDDR4X"
androidboot.ddr_info = "combuchiandroidboot.ddr_size=2GB"
androidboot.hardware.ufs = "2GB,combushi"
androidboot.boottime = "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123"
androidboot.ramdump = "disabled"
androidboot.vbmeta.device = "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb"
androidboot.vbmeta.avb_version = "1.1"
androidboot.vbmeta.device_state = "unlocked"
androidboot.vbmeta.hash_alg = "sha256"
androidboot.vbmeta.size = "5248"
androidboot.vbmeta.digest = "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860"
androidboot.vbmeta.invalidate_on_error = "yes"
androidboot.veritymode = "enforcing"
androidboot.verifiedbootstate = "orange"
androidboot.space = "sha256 5248 androidboot.nospace = nope"
just.key
key.empty.value =
dessert.value = "ice, cream"
dessert.list = "ice", "cream"
ambiguous.list = ", ", ", "
)";

const std::vector<std::pair<std::string, std::string>> bootconfig_result_space = {
        {"androidboot.bootdevice", "1d84000.ufshc"},
        {"androidboot.boot_devices", "dev1, dev2,withcomma, dev3"},
        {"androidboot.baseband", "sdy"},
        {"androidboot.keymaster", "1"},
        {"androidboot.serialno", "BLAHBLAHBLAH"},
        {"androidboot.slot_suffix", "_a"},
        {"androidboot.hardware.platform", "sdw813"},
        {"androidboot.hardware", "foo"},
        {"androidboot.revision", "EVT1.0"},
        {"androidboot.bootloader", "burp-0.1-7521"},
        {"androidboot.hardware.sku", "mary"},
        {"androidboot.hardware.radio.subtype", "0"},
        {"androidboot.dtbo_idx", "2"},
        {"androidboot.mode", "normal"},
        {"androidboot.hardware.ddr", "1GB,combuchi,LPDDR4X"},
        {"androidboot.ddr_info", "combuchiandroidboot.ddr_size=2GB"},
        {"androidboot.hardware.ufs", "2GB,combushi"},
        {"androidboot.boottime", "0BLE:58,1BLL:22,1BLE:571,2BLL:105,ODT:0,AVB:123"},
        {"androidboot.ramdump", "disabled"},
        {"androidboot.vbmeta.device", "PARTUUID=aa08f1a4-c7c9-402e-9a66-9707cafa9ceb"},
        {"androidboot.vbmeta.avb_version", "1.1"},
        {"androidboot.vbmeta.device_state", "unlocked"},
        {"androidboot.vbmeta.hash_alg", "sha256"},
        {"androidboot.vbmeta.size", "5248"},
        {"androidboot.vbmeta.digest",
         "ac13147e959861c20f2a6da97d25fe79e60e902c022a371c5c039d31e7c68860"},
        {"androidboot.vbmeta.invalidate_on_error", "yes"},
        {"androidboot.veritymode", "enforcing"},
        {"androidboot.verifiedbootstate", "orange"},
        {"androidboot.space", "sha256 5248 androidboot.nospace = nope"},
        {"just.key", ""},
        {"key.empty.value", ""},
        {"dessert.value", "ice, cream"},
        {"dessert.list", "ice,cream"},
        {"ambiguous.list", ", ,, "},
};

bool CompareFlags(FstabEntry::FsMgrFlags& lhs, FstabEntry::FsMgrFlags& rhs) {
    // clang-format off
    return lhs.wait == rhs.wait &&
           lhs.check == rhs.check &&
           lhs.crypt == rhs.crypt &&
           lhs.nonremovable == rhs.nonremovable &&
           lhs.vold_managed == rhs.vold_managed &&
           lhs.recovery_only == rhs.recovery_only &&
           lhs.no_emulated_sd == rhs.no_emulated_sd &&
           lhs.no_trim == rhs.no_trim &&
           lhs.file_encryption == rhs.file_encryption &&
           lhs.formattable == rhs.formattable &&
           lhs.slot_select == rhs.slot_select &&
           lhs.late_mount == rhs.late_mount &&
           lhs.no_fail == rhs.no_fail &&
           lhs.quota == rhs.quota &&
           lhs.avb == rhs.avb &&
           lhs.logical == rhs.logical &&
           lhs.checkpoint_blk == rhs.checkpoint_blk &&
           lhs.checkpoint_fs == rhs.checkpoint_fs &&
           lhs.first_stage_mount == rhs.first_stage_mount &&
           lhs.slot_select_other == rhs.slot_select_other &&
           lhs.fs_verity == rhs.fs_verity;
    // clang-format on
}

}  // namespace

TEST(fs_mgr, ImportKernelCmdline) {
    std::vector<std::pair<std::string, std::string>> result;
    ImportKernelCmdlineFromString(
            cmdline, [&](std::string key, std::string value) { result.emplace_back(key, value); });
    EXPECT_THAT(result, ContainerEq(result_space));
}

TEST(fs_mgr, GetKernelCmdline) {
    std::string content;
    for (const auto& [key, value] : result_space) {
        EXPECT_TRUE(GetKernelCmdlineFromString(cmdline, key, &content)) << " for " << key;
        EXPECT_EQ(content, value);
    }

    const std::string kUnmodifiedToken = "<UNMODIFIED>";
    content = kUnmodifiedToken;
    EXPECT_FALSE(GetKernelCmdlineFromString(cmdline, "", &content));
    EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden";

    content = kUnmodifiedToken;
    EXPECT_FALSE(GetKernelCmdlineFromString(cmdline, "androidboot.vbmeta.avb_versio", &content));
    EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden";

    content = kUnmodifiedToken;
    EXPECT_FALSE(GetKernelCmdlineFromString(bootconfig, "androidboot.nospace", &content));
    EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden";
}

TEST(fs_mgr, ImportBootconfig) {
    std::vector<std::pair<std::string, std::string>> result;
    ImportBootconfigFromString(bootconfig, [&](std::string key, std::string value) {
        result.emplace_back(key, value);
    });
    EXPECT_THAT(result, ContainerEq(bootconfig_result_space));
}

TEST(fs_mgr, GetBootconfig) {
    std::string content;
    for (const auto& [key, value] : bootconfig_result_space) {
        EXPECT_TRUE(GetBootconfigFromString(bootconfig, key, &content)) << " for " << key;
        EXPECT_EQ(content, value);
    }

    const std::string kUnmodifiedToken = "<UNMODIFIED>";
    content = kUnmodifiedToken;
    EXPECT_FALSE(GetBootconfigFromString(bootconfig, "", &content));
    EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden";

    content = kUnmodifiedToken;
    EXPECT_FALSE(GetBootconfigFromString(bootconfig, "androidboot.vbmeta.avb_versio", &content));
    EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden";

    content = kUnmodifiedToken;
    EXPECT_FALSE(GetBootconfigFromString(bootconfig, "androidboot.nospace", &content));
    EXPECT_EQ(content, kUnmodifiedToken) << "output parameter shouldn't be overridden";
}

TEST(fs_mgr, fs_mgr_read_fstab_file_proc_mounts) {
    Fstab fstab;
    ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &fstab));

    std::unique_ptr<std::FILE, int (*)(std::FILE*)> mounts(setmntent("/proc/mounts", "re"),
                                                           endmntent);
    ASSERT_NE(mounts, nullptr);

    mntent* mentry;
    size_t i = 0;
    while ((mentry = getmntent(mounts.get())) != nullptr) {
        ASSERT_LT(i, fstab.size());
        auto& entry = fstab[i];

        EXPECT_EQ(mentry->mnt_fsname, entry.blk_device);
        EXPECT_EQ(mentry->mnt_dir, entry.mount_point);
        EXPECT_EQ(mentry->mnt_type, entry.fs_type);

        std::set<std::string> mnt_opts;
        for (auto& s : android::base::Split(mentry->mnt_opts, ",")) {
            mnt_opts.emplace(s);
        }
        std::set<std::string> fs_options;
        if (!entry.fs_options.empty()) {
            for (auto& s : android::base::Split(entry.fs_options, ",")) {
                fs_options.emplace(s);
            }
        }
        // matches private content in fs_mgr_fstab.c
        static struct flag_list {
            const char* name;
            unsigned int flag;
        } mount_flags[] = {
                {"noatime", MS_NOATIME},
                {"noexec", MS_NOEXEC},
                {"nosuid", MS_NOSUID},
                {"nodev", MS_NODEV},
                {"nodiratime", MS_NODIRATIME},
                {"ro", MS_RDONLY},
                {"rw", 0},
                {"sync", MS_SYNCHRONOUS},
                {"remount", MS_REMOUNT},
                {"bind", MS_BIND},
                {"rec", MS_REC},
                {"unbindable", MS_UNBINDABLE},
                {"private", MS_PRIVATE},
                {"slave", MS_SLAVE},
                {"shared", MS_SHARED},
                {"defaults", 0},
                {0, 0},
        };
        for (auto f = 0; mount_flags[f].name; ++f) {
            if (mount_flags[f].flag & entry.flags) {
                fs_options.emplace(mount_flags[f].name);
            }
        }
        if (!(entry.flags & MS_RDONLY)) {
            fs_options.emplace("rw");
        }
        EXPECT_EQ(mnt_opts, fs_options) << "At line " << i;
        ++i;
    }
    EXPECT_EQ(i, fstab.size());
}

TEST(fs_mgr, ReadFstabFromFile_MountOptions) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source /            ext4    ro,barrier=1                    wait,avb
source /metadata    ext4    noatime,nosuid,nodev,discard    wait,formattable

source /data        f2fs    noatime,nosuid,nodev,discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier    latemount,wait,check,fileencryption=ice,keydirectory=/metadata/vold/metadata_encryption,quota,formattable,sysfs_path=/sys/devices/platform/soc/1d84000.ufshc,reservedsize=128M

source /misc        emmc    defaults                        defaults

source /vendor/firmware_mnt    vfat    ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0    wait

source auto         vfat    defaults                        voldmanaged=usb:auto
source none         swap    defaults                        zramsize=1073741824,max_comp_streams=8
source none2        swap    nodiratime,remount,bind         zramsize=1073741824,max_comp_streams=8
source none3        swap    unbindable,private,slave        zramsize=1073741824,max_comp_streams=8
source none4        swap    noexec,shared,rec               zramsize=1073741824,max_comp_streams=8
source none5        swap    rw                              zramsize=1073741824,max_comp_streams=8
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(11U, fstab.size());

    FstabEntry* entry = GetEntryForMountPoint(&fstab, "/");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
    EXPECT_EQ("barrier=1", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "/metadata");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
    EXPECT_EQ("discard", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "/data");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(static_cast<unsigned long>(MS_NOATIME | MS_NOSUID | MS_NODEV), entry->flags);
    EXPECT_EQ("discard,reserve_root=32768,resgid=1065,fsync_mode=nobarrier", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "/misc");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(0U, entry->flags);
    EXPECT_EQ("", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "/vendor/firmware_mnt");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(static_cast<unsigned long>(MS_RDONLY), entry->flags);
    EXPECT_EQ(
            "shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,"
            "context=u:object_r:firmware_file:s0",
            entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "auto");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(0U, entry->flags);
    EXPECT_EQ("", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "none");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(0U, entry->flags);
    EXPECT_EQ("", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "none2");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(static_cast<unsigned long>(MS_NODIRATIME | MS_REMOUNT | MS_BIND), entry->flags);
    EXPECT_EQ("", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "none3");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(static_cast<unsigned long>(MS_UNBINDABLE | MS_PRIVATE | MS_SLAVE), entry->flags);
    EXPECT_EQ("", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "none4");
    ASSERT_NE(nullptr, entry);
    EXPECT_EQ(static_cast<unsigned long>(MS_NOEXEC | MS_SHARED | MS_REC), entry->flags);
    EXPECT_EQ("", entry->fs_options);

    entry = GetEntryForMountPoint(&fstab, "none5");
    ASSERT_NE(nullptr, entry);
    // rw is the default.
    EXPECT_EQ(0U, entry->flags);
    EXPECT_EQ("", entry->fs_options);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrFlags) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      wait,check,nonremovable,recoveryonly
source none1       swap   defaults      avb,noemulatedsd,notrim,formattable,nofail
source none2       swap   defaults      first_stage_mount,latemount,quota,logical
source none3       swap   defaults      checkpoint=block
source none4       swap   defaults      checkpoint=fs
source none5       swap   defaults      defaults
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(6U, fstab.size());

    FstabEntry* entry = GetEntryForMountPoint(&fstab, "none0");
    ASSERT_NE(nullptr, entry);
    {
        FstabEntry::FsMgrFlags flags = {};
        flags.wait = true;
        flags.check = true;
        flags.nonremovable = true;
        flags.recovery_only = true;
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }

    entry = GetEntryForMountPoint(&fstab, "none1");
    ASSERT_NE(nullptr, entry);
    {
        FstabEntry::FsMgrFlags flags = {};
        flags.avb = true;
        flags.no_emulated_sd = true;
        flags.no_trim = true;
        flags.formattable = true;
        flags.no_fail = true;
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }

    entry = GetEntryForMountPoint(&fstab, "none2");
    ASSERT_NE(nullptr, entry);
    {
        FstabEntry::FsMgrFlags flags = {};
        flags.first_stage_mount = true;
        flags.late_mount = true;
        flags.quota = true;
        flags.logical = true;
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }

    entry = GetEntryForMountPoint(&fstab, "none3");
    ASSERT_NE(nullptr, entry);
    {
        FstabEntry::FsMgrFlags flags = {};
        flags.checkpoint_blk = true;
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }

    entry = GetEntryForMountPoint(&fstab, "none4");
    ASSERT_NE(nullptr, entry);
    {
        FstabEntry::FsMgrFlags flags = {};
        flags.checkpoint_fs = true;
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }

    entry = GetEntryForMountPoint(&fstab, "none5");
    ASSERT_NE(nullptr, entry);
    {
        FstabEntry::FsMgrFlags flags = {};
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_AllBad) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      fileencryption,keydirectory,length,swapprio,zramsize,max_comp_streams,reservedsize,eraseblk,logicalblk,sysfs_path,zram_backingdev_size

source none1       swap   defaults      fileencryption=,keydirectory=,length=,swapprio=,zramsize=,max_comp_streams=,avb=,reservedsize=,eraseblk=,logicalblk=,sysfs_path=,zram_backingdev_size=

)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(2U, fstab.size());

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    {
        FstabEntry::FsMgrFlags flags = {};
        flags.file_encryption = true;
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }
    EXPECT_EQ("", entry->metadata_key_dir);
    EXPECT_EQ(0, entry->length);
    EXPECT_EQ("", entry->label);
    EXPECT_EQ(-1, entry->partnum);
    EXPECT_EQ(-1, entry->swap_prio);
    EXPECT_EQ(0, entry->max_comp_streams);
    EXPECT_EQ(0, entry->zram_size);
    EXPECT_EQ(0, entry->reserved_size);
    EXPECT_EQ("", entry->encryption_options);
    EXPECT_EQ(0, entry->erase_blk_size);
    EXPECT_EQ(0, entry->logical_blk_size);
    EXPECT_EQ("", entry->sysfs_path);
    EXPECT_EQ(0U, entry->zram_backingdev_size);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    {
        FstabEntry::FsMgrFlags flags = {};
        flags.file_encryption = true;
        flags.avb = true;
        EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    }
    EXPECT_EQ("", entry->metadata_key_dir);
    EXPECT_EQ(0, entry->length);
    EXPECT_EQ("", entry->label);
    EXPECT_EQ(-1, entry->partnum);
    EXPECT_EQ(-1, entry->swap_prio);
    EXPECT_EQ(0, entry->max_comp_streams);
    EXPECT_EQ(0, entry->zram_size);
    EXPECT_EQ(0, entry->reserved_size);
    EXPECT_EQ("", entry->encryption_options);
    EXPECT_EQ(0, entry->erase_blk_size);
    EXPECT_EQ(0, entry->logical_blk_size);
    EXPECT_EQ("", entry->sysfs_path);
    EXPECT_EQ(0U, entry->zram_backingdev_size);
}

// FDE is no longer supported, so an fstab with FDE enabled should be rejected.
TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FDE) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source /data        ext4    noatime    forceencrypt=footer
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_FALSE(ReadFstabFromFile(tf.path, &fstab));
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_AdoptableStorage) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      encryptable=userdata,voldmanaged=sdcard:auto
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(1U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};
    flags.crypt = true;
    flags.vold_managed = true;

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_VoldManaged) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      voldmanaged=:
source none1       swap   defaults      voldmanaged=sdcard
source none2       swap   defaults      voldmanaged=sdcard:3
source none3       swap   defaults      voldmanaged=sdcard:auto
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(4U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};
    flags.vold_managed = true;

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_TRUE(entry->label.empty());
    EXPECT_EQ(-1, entry->partnum);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_TRUE(entry->label.empty());
    EXPECT_EQ(-1, entry->partnum);
    entry++;

    EXPECT_EQ("none2", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ("sdcard", entry->label);
    EXPECT_EQ(3, entry->partnum);
    entry++;

    EXPECT_EQ("none3", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ("sdcard", entry->label);
    EXPECT_EQ(-1, entry->partnum);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Length) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      length=blah
source none1       swap   defaults      length=123456
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(2U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->length);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(123456, entry->length);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Swapprio) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      swapprio=blah
source none1       swap   defaults      swapprio=123456
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(2U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(-1, entry->swap_prio);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(123456, entry->swap_prio);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ZramSize) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      zramsize=blah
source none1       swap   defaults      zramsize=123456
source none2       swap   defaults      zramsize=blah%
source none3       swap   defaults      zramsize=5%
source none4       swap   defaults      zramsize=105%
source none5       swap   defaults      zramsize=%
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(6U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->zram_size);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(123456, entry->zram_size);
    entry++;

    EXPECT_EQ("none2", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->zram_size);
    entry++;

    EXPECT_EQ("none3", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_NE(0, entry->zram_size);
    entry++;

    EXPECT_EQ("none4", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->zram_size);
    entry++;

    EXPECT_EQ("none5", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->zram_size);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_FileEncryption) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      fileencryption=aes-256-xts:aes-256-cts:v1
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(1U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};
    flags.file_encryption = true;

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ("aes-256-xts:aes-256-cts:v1", entry->encryption_options);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_MaxCompStreams) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      max_comp_streams=blah
source none1       swap   defaults      max_comp_streams=123456
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(2U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->max_comp_streams);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(123456, entry->max_comp_streams);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_ReservedSize) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      reservedsize=blah
source none1       swap   defaults      reservedsize=2
source none2       swap   defaults      reservedsize=1K
source none3       swap   defaults      reservedsize=2m
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(4U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->reserved_size);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(2, entry->reserved_size);
    entry++;

    EXPECT_EQ("none2", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(1024, entry->reserved_size);
    entry++;

    EXPECT_EQ("none3", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(2 * 1024 * 1024, entry->reserved_size);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_EraseBlk) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      eraseblk=blah
source none1       swap   defaults      eraseblk=4000
source none2       swap   defaults      eraseblk=5000
source none3       swap   defaults      eraseblk=8192
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(4U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->erase_blk_size);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->erase_blk_size);
    entry++;

    EXPECT_EQ("none2", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->erase_blk_size);
    entry++;

    EXPECT_EQ("none3", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(8192, entry->erase_blk_size);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Logicalblk) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      logicalblk=blah
source none1       swap   defaults      logicalblk=4000
source none2       swap   defaults      logicalblk=5000
source none3       swap   defaults      logicalblk=8192
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(4U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->logical_blk_size);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->logical_blk_size);
    entry++;

    EXPECT_EQ("none2", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->logical_blk_size);
    entry++;

    EXPECT_EQ("none3", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(8192, entry->logical_blk_size);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Avb) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      avb=vbmeta_partition
source none1       swap   defaults      avb_keys=/path/to/test.avbpubkey
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(2U, fstab.size());

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);

    FstabEntry::FsMgrFlags flags = {};
    flags.avb = true;
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));

    EXPECT_EQ("vbmeta_partition", entry->vbmeta_partition);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    FstabEntry::FsMgrFlags empty_flags = {};  // no flags should be set for avb_keys.
    EXPECT_TRUE(CompareFlags(empty_flags, entry->fs_mgr_flags));
    EXPECT_EQ("/path/to/test.avbpubkey", entry->avb_keys);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_KeyDirectory) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      keydirectory=/dir/key
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(1U, fstab.size());

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);

    FstabEntry::FsMgrFlags flags = {};
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));

    EXPECT_EQ("/dir/key", entry->metadata_key_dir);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_MetadataEncryption) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      keydirectory=/dir/key,metadata_encryption=adiantum
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(1U, fstab.size());

    auto entry = fstab.begin();
    EXPECT_EQ("adiantum", entry->metadata_encryption_options);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_MetadataEncryption_WrappedKey) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      keydirectory=/dir/key,metadata_encryption=aes-256-xts:wrappedkey_v0
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(1U, fstab.size());

    auto entry = fstab.begin();
    EXPECT_EQ("aes-256-xts:wrappedkey_v0", entry->metadata_encryption_options);
    auto parts = android::base::Split(entry->metadata_encryption_options, ":");
    EXPECT_EQ(2U, parts.size());
    EXPECT_EQ("aes-256-xts", parts[0]);
    EXPECT_EQ("wrappedkey_v0", parts[1]);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_SysfsPath) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      sysfs_path=/sys/device
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(1U, fstab.size());

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);

    FstabEntry::FsMgrFlags flags = {};
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));

    EXPECT_EQ("/sys/device", entry->sysfs_path);
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Zram) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none1       swap   defaults      zram_backingdev_size=blah
source none2       swap   defaults      zram_backingdev_size=2
source none3       swap   defaults      zram_backingdev_size=1K
source none4       swap   defaults      zram_backingdev_size=2m

)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(4U, fstab.size());

    auto entry = fstab.begin();

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_EQ(0U, entry->zram_backingdev_size);
    entry++;

    EXPECT_EQ("none2", entry->mount_point);
    EXPECT_EQ(2U, entry->zram_backingdev_size);
    entry++;

    EXPECT_EQ("none3", entry->mount_point);
    EXPECT_EQ(1024U, entry->zram_backingdev_size);
    entry++;

    EXPECT_EQ("none4", entry->mount_point);
    EXPECT_EQ(2U * 1024U * 1024U, entry->zram_backingdev_size);
    entry++;
}

TEST(fs_mgr, DefaultFstabContainsUserdata) {
    Fstab fstab;
    ASSERT_TRUE(ReadDefaultFstab(&fstab)) << "Failed to read default fstab";
    ASSERT_NE(nullptr, GetEntryForMountPoint(&fstab, "/data"))
            << "Default fstab doesn't contain /data entry";
}

TEST(fs_mgr, UserdataMountedFromDefaultFstab) {
    if (getuid() != 0) {
        GTEST_SKIP() << "Must be run as root.";
        return;
    }
    Fstab fstab;
    ASSERT_TRUE(ReadDefaultFstab(&fstab)) << "Failed to read default fstab";
    Fstab proc_mounts;
    ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &proc_mounts)) << "Failed to read /proc/mounts";
    auto mounted_entry = GetEntryForMountPoint(&proc_mounts, "/data");
    ASSERT_NE(mounted_entry, nullptr) << "/data is not mounted";
    std::string block_device;
    ASSERT_TRUE(android::base::Realpath(mounted_entry->blk_device, &block_device));
    ASSERT_NE(nullptr, fs_mgr_get_mounted_entry_for_userdata(&fstab, block_device))
            << "/data wasn't mounted from default fstab";
}

TEST(fs_mgr, ReadFstabFromFile_FsMgrOptions_Readahead_Size_KB) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
source none0       swap   defaults      readahead_size_kb=blah
source none1       swap   defaults      readahead_size_kb=128
source none2       swap   defaults      readahead_size_kb=5%
source none3       swap   defaults      readahead_size_kb=5kb
source none4       swap   defaults      readahead_size_kb=16385
source none5       swap   defaults      readahead_size_kb=-128
source none6       swap   defaults      readahead_size_kb=0
)fs";
    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    ASSERT_LE(7U, fstab.size());

    FstabEntry::FsMgrFlags flags = {};

    auto entry = fstab.begin();
    EXPECT_EQ("none0", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(-1, entry->readahead_size_kb);
    entry++;

    EXPECT_EQ("none1", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(128, entry->readahead_size_kb);
    entry++;

    EXPECT_EQ("none2", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(-1, entry->readahead_size_kb);
    entry++;

    EXPECT_EQ("none3", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(-1, entry->readahead_size_kb);
    entry++;

    EXPECT_EQ("none4", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(-1, entry->readahead_size_kb);
    entry++;

    EXPECT_EQ("none5", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(-1, entry->readahead_size_kb);
    entry++;

    EXPECT_EQ("none6", entry->mount_point);
    EXPECT_TRUE(CompareFlags(flags, entry->fs_mgr_flags));
    EXPECT_EQ(0, entry->readahead_size_kb);
}

TEST(fs_mgr, TransformFstabForDsu) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
data   /data        f2fs    noatime     wait,latemount
system /system      erofs   ro  wait,logical,first_stage_mount
system /system      ext4    ro  wait,logical,first_stage_mount
vendor /vendor      ext4    ro  wait,logical,first_stage_mount
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    // If GSI is installed, ReadFstabFromFile() would have called TransformFstabForDsu() implicitly.
    // In other words, TransformFstabForDsu() would be called two times if running CTS-on-GSI,
    // which implies TransformFstabForDsu() should be idempotent.
    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
    ASSERT_EQ(4U, fstab.size());

    auto entry = fstab.begin();

    EXPECT_EQ("/data", entry->mount_point);
    EXPECT_EQ("userdata_gsi", entry->blk_device);
    entry++;

    EXPECT_EQ("/system", entry->mount_point);
    EXPECT_EQ("system_gsi", entry->blk_device);
    EXPECT_EQ("erofs", entry->fs_type);
    entry++;

    EXPECT_EQ("/system", entry->mount_point);
    EXPECT_EQ("system_gsi", entry->blk_device);
    EXPECT_EQ("ext4", entry->fs_type);
    entry++;

    EXPECT_EQ("/vendor", entry->mount_point);
    EXPECT_EQ("vendor", entry->blk_device);
    entry++;
}

TEST(fs_mgr, TransformFstabForDsu_synthesisExt4Entry) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
system /system      erofs   ro  wait,logical,first_stage_mount
vendor /vendor      ext4    ro  wait,logical,first_stage_mount
data   /data        f2fs    noatime     wait,latemount
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
    ASSERT_EQ(4U, fstab.size());

    auto entry = fstab.begin();

    EXPECT_EQ("/system", entry->mount_point);
    EXPECT_EQ("system_gsi", entry->blk_device);
    EXPECT_EQ("erofs", entry->fs_type);
    entry++;

    EXPECT_EQ("/system", entry->mount_point);
    EXPECT_EQ("system_gsi", entry->blk_device);
    EXPECT_EQ("ext4", entry->fs_type);
    entry++;

    EXPECT_EQ("/vendor", entry->mount_point);
    EXPECT_EQ("vendor", entry->blk_device);
    entry++;

    EXPECT_EQ("/data", entry->mount_point);
    EXPECT_EQ("userdata_gsi", entry->blk_device);
    entry++;
}

TEST(fs_mgr, TransformFstabForDsu_synthesisAllMissingEntries) {
    TemporaryFile tf;
    ASSERT_TRUE(tf.fd != -1);
    std::string fstab_contents = R"fs(
data   /data        f2fs    noatime     wait,latemount
vendor /vendor      ext4    ro  wait,logical,first_stage_mount
)fs";

    ASSERT_TRUE(android::base::WriteStringToFile(fstab_contents, tf.path));

    Fstab fstab;
    EXPECT_TRUE(ReadFstabFromFile(tf.path, &fstab));
    TransformFstabForDsu(&fstab, "dsu", {"system_gsi", "userdata_gsi"});
    ASSERT_EQ(4U, fstab.size());

    auto entry = fstab.begin();

    EXPECT_EQ("/data", entry->mount_point);
    EXPECT_EQ("userdata_gsi", entry->blk_device);
    entry++;

    EXPECT_EQ("/vendor", entry->mount_point);
    EXPECT_EQ("vendor", entry->blk_device);
    entry++;

    EXPECT_EQ("/system", entry->mount_point);
    EXPECT_EQ("system_gsi", entry->blk_device);
    EXPECT_EQ("ext4", entry->fs_type);
    entry++;

    EXPECT_EQ("/system", entry->mount_point);
    EXPECT_EQ("system_gsi", entry->blk_device);
    EXPECT_EQ("erofs", entry->fs_type);
    entry++;
}
