Loading fs_mgr/libfiemap/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,9 @@ cc_test { test_options: { min_shipping_api_level: 29, }, header_libs: [ "libstorage_literals_headers", ], require_root: true, } Loading fs_mgr/libfiemap/fiemap_writer_test.cpp +78 −40 Original line number Diff line number Diff line Loading @@ -22,21 +22,25 @@ #include <string.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <sys/types.h> #include <sys/vfs.h> #include <unistd.h> #include <string> #include <utility> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/stringprintf.h> #include <android-base/unique_fd.h> #include <fstab/fstab.h> #include <gtest/gtest.h> #include <libdm/loop_control.h> #include <libfiemap/fiemap_writer.h> #include <libfiemap/split_fiemap_writer.h> #include <libgsi/libgsi.h> #include <storage_literals/storage_literals.h> #include "utility.h" Loading @@ -46,6 +50,7 @@ namespace fiemap { using namespace std; using namespace std::string_literals; using namespace android::fiemap; using namespace android::storage_literals; using unique_fd = android::base::unique_fd; using LoopDevice = android::dm::LoopDevice; Loading Loading @@ -427,90 +432,123 @@ TEST_F(SplitFiemapTest, WritePastEnd) { ASSERT_FALSE(ptr->Write(buffer.get(), kSize)); } class VerifyBlockWritesExt4 : public ::testing::Test { // Get max file size and free space. std::pair<uint64_t, uint64_t> GetBigFileLimit(const std::string& mount_point) { struct statvfs fs; if (statvfs(mount_point.c_str(), &fs) < 0) { PLOG(ERROR) << "statfs failed"; return {0, 0}; } auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1); auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize; LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free; return {fs_limit, fs_free}; } class FsTest : public ::testing::Test { protected: // 2GB Filesystem and 4k block size by default static constexpr uint64_t block_size = 4096; static constexpr uint64_t fs_size = 2147483648; static constexpr uint64_t fs_size = 64 * 1024 * 1024; protected: void SetUp() override { fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img"; void SetUp() { android::fs_mgr::Fstab fstab; ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab)); ASSERT_EQ(access(tmpdir_.path, F_OK), 0); fs_path_ = tmpdir_.path + "/fs_image"s; mntpoint_ = tmpdir_.path + "/mnt_point"s; auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data"); ASSERT_NE(entry, nullptr); if (entry->fs_type == "ext4") { SetUpExt4(); } else if (entry->fs_type == "f2fs") { SetUpF2fs(); } else { FAIL() << "Unrecognized fs_type: " << entry->fs_type; } } void SetUpExt4() { uint64_t count = fs_size / block_size; std::string dd_cmd = ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64 " count=%" PRIu64 " > /dev/null 2>&1", fs_path.c_str(), block_size, count); fs_path_.c_str(), block_size, count); std::string mkfs_cmd = ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str()); ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path_.c_str()); // create mount point mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt"; ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0); ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0); // create file for the file system int ret = system(dd_cmd.c_str()); ASSERT_EQ(ret, 0); // Get and attach a loop device to the filesystem we created LoopDevice loop_dev(fs_path, 10s); LoopDevice loop_dev(fs_path_, 10s); ASSERT_TRUE(loop_dev.valid()); // create file system ret = system(mkfs_cmd.c_str()); ASSERT_EQ(ret, 0); // mount the file system ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0); ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "ext4", 0, nullptr), 0); } void TearDown() override { umount(mntpoint.c_str()); rmdir(mntpoint.c_str()); unlink(fs_path.c_str()); } std::string mntpoint; std::string fs_path; }; class VerifyBlockWritesF2fs : public ::testing::Test { // 2GB Filesystem and 4k block size by default static constexpr uint64_t block_size = 4096; static constexpr uint64_t fs_size = 2147483648; protected: void SetUp() override { fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img"; void SetUpF2fs() { uint64_t count = fs_size / block_size; std::string dd_cmd = ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64 " count=%" PRIu64 " > /dev/null 2>&1", fs_path.c_str(), block_size, count); fs_path_.c_str(), block_size, count); std::string mkfs_cmd = ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str()); ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path_.c_str()); // create mount point mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt"; ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0); ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0); // create file for the file system int ret = system(dd_cmd.c_str()); ASSERT_EQ(ret, 0); // Get and attach a loop device to the filesystem we created LoopDevice loop_dev(fs_path, 10s); LoopDevice loop_dev(fs_path_, 10s); ASSERT_TRUE(loop_dev.valid()); // create file system ret = system(mkfs_cmd.c_str()); ASSERT_EQ(ret, 0); // mount the file system ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0); ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "f2fs", 0, nullptr), 0); } void TearDown() override { umount(mntpoint.c_str()); rmdir(mntpoint.c_str()); unlink(fs_path.c_str()); umount(mntpoint_.c_str()); rmdir(mntpoint_.c_str()); unlink(fs_path_.c_str()); } std::string mntpoint; std::string fs_path; TemporaryDir tmpdir_; std::string mntpoint_; std::string fs_path_; }; TEST_F(FsTest, LowSpaceError) { auto limits = GetBigFileLimit(mntpoint_); ASSERT_GE(limits.first, 0); FiemapUniquePtr ptr; auto test_file = mntpoint_ + "/big_file"; auto status = FiemapWriter::Open(test_file, limits.first, &ptr); ASSERT_FALSE(status.is_ok()); ASSERT_EQ(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE); // Also test for EFBIG. status = FiemapWriter::Open(test_file, 16_TiB, &ptr); ASSERT_FALSE(status.is_ok()); ASSERT_NE(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE); } bool DetermineBlockSize() { struct statfs s; if (statfs(gTestDir.c_str(), &s)) { Loading fs_mgr/libsnapshot/snapshot_test.cpp +0 −86 Original line number Diff line number Diff line Loading @@ -2372,60 +2372,6 @@ TEST_F(SnapshotUpdateTest, Overflow) { << "FinishedSnapshotWrites should detect overflow of CoW device."; } // Get max file size and free space. std::pair<uint64_t, uint64_t> GetBigFileLimit() { struct statvfs fs; if (statvfs("/data", &fs) < 0) { PLOG(ERROR) << "statfs failed"; return {0, 0}; } auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1); auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize; LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free; return {fs_limit, fs_free}; } TEST_F(SnapshotUpdateTest, LowSpace) { // To make the low space test more reliable, we force a large cow estimate. // However legacy VAB ignores the COW estimate and uses InstallOperations // to compute the exact size required for dm-snapshot. It's difficult to // make this work reliably (we'd need to somehow fake an extremely large // super partition, and we don't have that level of dependency injection). // // For now, just skip this test on legacy VAB. if (!snapuserd_required_) { GTEST_SKIP() << "Skipping test on legacy VAB"; } auto fs = GetBigFileLimit(); ASSERT_NE(fs.first, 0); constexpr uint64_t partition_size = 10_MiB; SetSize(sys_, partition_size); SetSize(vnd_, partition_size); SetSize(prd_, partition_size); sys_->set_estimate_cow_size(fs.first); vnd_->set_estimate_cow_size(fs.first); prd_->set_estimate_cow_size(fs.first); AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); auto res = sm->CreateUpdateSnapshots(manifest_); ASSERT_FALSE(res); ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code()); // It's hard to predict exactly how much free space is needed, since /data // is writable and the test is not the only process running. Divide by two // as a rough lower bound, and adjust this in the future as necessary. auto expected_delta = fs.first - fs.second; ASSERT_GE(res.required_size(), expected_delta / 2); } TEST_F(SnapshotUpdateTest, AddPartition) { group_->add_partition_names("dlkm"); Loading Loading @@ -2796,38 +2742,6 @@ INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), B "Merge"s; }); class ImageManagerTest : public SnapshotTest { protected: void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); SnapshotTest::SetUp(); } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); CleanUp(); SnapshotTest::TearDown(); } void CleanUp() { if (!image_manager_) { return; } EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) || image_manager_->DeleteBackingImage(kImageName)); } static constexpr const char* kImageName = "my_image"; }; TEST_F(ImageManagerTest, CreateImageNoSpace) { auto fs = GetBigFileLimit(); ASSERT_NE(fs.first, 0); auto res = image_manager_->CreateBackingImage(kImageName, fs.first, IImageManager::CREATE_IMAGE_DEFAULT); ASSERT_FALSE(res); ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string(); } bool Mkdir(const std::string& path) { if (mkdir(path.c_str(), 0700) && errno != EEXIST) { std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; Loading fs_mgr/libstorage_literals/storage_literals/storage_literals.h +6 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ using B = Size<0>; using KiB = Size<10>; using MiB = Size<20>; using GiB = Size<30>; using TiB = Size<40>; constexpr B operator""_B(unsigned long long v) { // NOLINT return B{v}; Loading @@ -54,6 +55,10 @@ constexpr GiB operator""_GiB(unsigned long long v) { // NOLINT return GiB{v}; } constexpr TiB operator""_TiB(unsigned long long v) { // NOLINT return TiB{v}; } template <typename Dest, typename Src> constexpr Dest size_cast(Src src) { if (Src::power < Dest::power) { Loading @@ -69,6 +74,7 @@ static_assert(1_B == 1); static_assert(1_KiB == 1 << 10); static_assert(1_MiB == 1 << 20); static_assert(1_GiB == 1 << 30); static_assert(1_TiB == 1ULL << 40); static_assert(size_cast<KiB>(1_B).count() == 0); static_assert(size_cast<KiB>(1024_B).count() == 1); static_assert(size_cast<KiB>(1_MiB).count() == 1024); Loading Loading
fs_mgr/libfiemap/Android.bp +3 −0 Original line number Diff line number Diff line Loading @@ -93,6 +93,9 @@ cc_test { test_options: { min_shipping_api_level: 29, }, header_libs: [ "libstorage_literals_headers", ], require_root: true, } Loading
fs_mgr/libfiemap/fiemap_writer_test.cpp +78 −40 Original line number Diff line number Diff line Loading @@ -22,21 +22,25 @@ #include <string.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <sys/types.h> #include <sys/vfs.h> #include <unistd.h> #include <string> #include <utility> #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/stringprintf.h> #include <android-base/unique_fd.h> #include <fstab/fstab.h> #include <gtest/gtest.h> #include <libdm/loop_control.h> #include <libfiemap/fiemap_writer.h> #include <libfiemap/split_fiemap_writer.h> #include <libgsi/libgsi.h> #include <storage_literals/storage_literals.h> #include "utility.h" Loading @@ -46,6 +50,7 @@ namespace fiemap { using namespace std; using namespace std::string_literals; using namespace android::fiemap; using namespace android::storage_literals; using unique_fd = android::base::unique_fd; using LoopDevice = android::dm::LoopDevice; Loading Loading @@ -427,90 +432,123 @@ TEST_F(SplitFiemapTest, WritePastEnd) { ASSERT_FALSE(ptr->Write(buffer.get(), kSize)); } class VerifyBlockWritesExt4 : public ::testing::Test { // Get max file size and free space. std::pair<uint64_t, uint64_t> GetBigFileLimit(const std::string& mount_point) { struct statvfs fs; if (statvfs(mount_point.c_str(), &fs) < 0) { PLOG(ERROR) << "statfs failed"; return {0, 0}; } auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1); auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize; LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free; return {fs_limit, fs_free}; } class FsTest : public ::testing::Test { protected: // 2GB Filesystem and 4k block size by default static constexpr uint64_t block_size = 4096; static constexpr uint64_t fs_size = 2147483648; static constexpr uint64_t fs_size = 64 * 1024 * 1024; protected: void SetUp() override { fs_path = std::string(getenv("TMPDIR")) + "/ext4_2G.img"; void SetUp() { android::fs_mgr::Fstab fstab; ASSERT_TRUE(android::fs_mgr::ReadFstabFromFile("/proc/mounts", &fstab)); ASSERT_EQ(access(tmpdir_.path, F_OK), 0); fs_path_ = tmpdir_.path + "/fs_image"s; mntpoint_ = tmpdir_.path + "/mnt_point"s; auto entry = android::fs_mgr::GetEntryForMountPoint(&fstab, "/data"); ASSERT_NE(entry, nullptr); if (entry->fs_type == "ext4") { SetUpExt4(); } else if (entry->fs_type == "f2fs") { SetUpF2fs(); } else { FAIL() << "Unrecognized fs_type: " << entry->fs_type; } } void SetUpExt4() { uint64_t count = fs_size / block_size; std::string dd_cmd = ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64 " count=%" PRIu64 " > /dev/null 2>&1", fs_path.c_str(), block_size, count); fs_path_.c_str(), block_size, count); std::string mkfs_cmd = ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path.c_str()); ::android::base::StringPrintf("/system/bin/mkfs.ext4 -q %s", fs_path_.c_str()); // create mount point mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt"; ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0); ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0); // create file for the file system int ret = system(dd_cmd.c_str()); ASSERT_EQ(ret, 0); // Get and attach a loop device to the filesystem we created LoopDevice loop_dev(fs_path, 10s); LoopDevice loop_dev(fs_path_, 10s); ASSERT_TRUE(loop_dev.valid()); // create file system ret = system(mkfs_cmd.c_str()); ASSERT_EQ(ret, 0); // mount the file system ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "ext4", 0, nullptr), 0); ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "ext4", 0, nullptr), 0); } void TearDown() override { umount(mntpoint.c_str()); rmdir(mntpoint.c_str()); unlink(fs_path.c_str()); } std::string mntpoint; std::string fs_path; }; class VerifyBlockWritesF2fs : public ::testing::Test { // 2GB Filesystem and 4k block size by default static constexpr uint64_t block_size = 4096; static constexpr uint64_t fs_size = 2147483648; protected: void SetUp() override { fs_path = std::string(getenv("TMPDIR")) + "/f2fs_2G.img"; void SetUpF2fs() { uint64_t count = fs_size / block_size; std::string dd_cmd = ::android::base::StringPrintf("/system/bin/dd if=/dev/zero of=%s bs=%" PRIu64 " count=%" PRIu64 " > /dev/null 2>&1", fs_path.c_str(), block_size, count); fs_path_.c_str(), block_size, count); std::string mkfs_cmd = ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path.c_str()); ::android::base::StringPrintf("/system/bin/make_f2fs -q %s", fs_path_.c_str()); // create mount point mntpoint = std::string(getenv("TMPDIR")) + "/fiemap_mnt"; ASSERT_EQ(mkdir(mntpoint.c_str(), S_IRWXU), 0); ASSERT_EQ(mkdir(mntpoint_.c_str(), S_IRWXU), 0); // create file for the file system int ret = system(dd_cmd.c_str()); ASSERT_EQ(ret, 0); // Get and attach a loop device to the filesystem we created LoopDevice loop_dev(fs_path, 10s); LoopDevice loop_dev(fs_path_, 10s); ASSERT_TRUE(loop_dev.valid()); // create file system ret = system(mkfs_cmd.c_str()); ASSERT_EQ(ret, 0); // mount the file system ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint.c_str(), "f2fs", 0, nullptr), 0); ASSERT_EQ(mount(loop_dev.device().c_str(), mntpoint_.c_str(), "f2fs", 0, nullptr), 0); } void TearDown() override { umount(mntpoint.c_str()); rmdir(mntpoint.c_str()); unlink(fs_path.c_str()); umount(mntpoint_.c_str()); rmdir(mntpoint_.c_str()); unlink(fs_path_.c_str()); } std::string mntpoint; std::string fs_path; TemporaryDir tmpdir_; std::string mntpoint_; std::string fs_path_; }; TEST_F(FsTest, LowSpaceError) { auto limits = GetBigFileLimit(mntpoint_); ASSERT_GE(limits.first, 0); FiemapUniquePtr ptr; auto test_file = mntpoint_ + "/big_file"; auto status = FiemapWriter::Open(test_file, limits.first, &ptr); ASSERT_FALSE(status.is_ok()); ASSERT_EQ(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE); // Also test for EFBIG. status = FiemapWriter::Open(test_file, 16_TiB, &ptr); ASSERT_FALSE(status.is_ok()); ASSERT_NE(status.error_code(), FiemapStatus::ErrorCode::NO_SPACE); } bool DetermineBlockSize() { struct statfs s; if (statfs(gTestDir.c_str(), &s)) { Loading
fs_mgr/libsnapshot/snapshot_test.cpp +0 −86 Original line number Diff line number Diff line Loading @@ -2372,60 +2372,6 @@ TEST_F(SnapshotUpdateTest, Overflow) { << "FinishedSnapshotWrites should detect overflow of CoW device."; } // Get max file size and free space. std::pair<uint64_t, uint64_t> GetBigFileLimit() { struct statvfs fs; if (statvfs("/data", &fs) < 0) { PLOG(ERROR) << "statfs failed"; return {0, 0}; } auto fs_limit = static_cast<uint64_t>(fs.f_blocks) * (fs.f_bsize - 1); auto fs_free = static_cast<uint64_t>(fs.f_bfree) * fs.f_bsize; LOG(INFO) << "Big file limit: " << fs_limit << ", free space: " << fs_free; return {fs_limit, fs_free}; } TEST_F(SnapshotUpdateTest, LowSpace) { // To make the low space test more reliable, we force a large cow estimate. // However legacy VAB ignores the COW estimate and uses InstallOperations // to compute the exact size required for dm-snapshot. It's difficult to // make this work reliably (we'd need to somehow fake an extremely large // super partition, and we don't have that level of dependency injection). // // For now, just skip this test on legacy VAB. if (!snapuserd_required_) { GTEST_SKIP() << "Skipping test on legacy VAB"; } auto fs = GetBigFileLimit(); ASSERT_NE(fs.first, 0); constexpr uint64_t partition_size = 10_MiB; SetSize(sys_, partition_size); SetSize(vnd_, partition_size); SetSize(prd_, partition_size); sys_->set_estimate_cow_size(fs.first); vnd_->set_estimate_cow_size(fs.first); prd_->set_estimate_cow_size(fs.first); AddOperationForPartitions(); // Execute the update. ASSERT_TRUE(sm->BeginUpdate()); auto res = sm->CreateUpdateSnapshots(manifest_); ASSERT_FALSE(res); ASSERT_EQ(Return::ErrorCode::NO_SPACE, res.error_code()); // It's hard to predict exactly how much free space is needed, since /data // is writable and the test is not the only process running. Divide by two // as a rough lower bound, and adjust this in the future as necessary. auto expected_delta = fs.first - fs.second; ASSERT_GE(res.required_size(), expected_delta / 2); } TEST_F(SnapshotUpdateTest, AddPartition) { group_->add_partition_names("dlkm"); Loading Loading @@ -2796,38 +2742,6 @@ INSTANTIATE_TEST_SUITE_P(Snapshot, FlashAfterUpdateTest, Combine(Values(0, 1), B "Merge"s; }); class ImageManagerTest : public SnapshotTest { protected: void SetUp() override { SKIP_IF_NON_VIRTUAL_AB(); SnapshotTest::SetUp(); } void TearDown() override { RETURN_IF_NON_VIRTUAL_AB(); CleanUp(); SnapshotTest::TearDown(); } void CleanUp() { if (!image_manager_) { return; } EXPECT_TRUE(!image_manager_->BackingImageExists(kImageName) || image_manager_->DeleteBackingImage(kImageName)); } static constexpr const char* kImageName = "my_image"; }; TEST_F(ImageManagerTest, CreateImageNoSpace) { auto fs = GetBigFileLimit(); ASSERT_NE(fs.first, 0); auto res = image_manager_->CreateBackingImage(kImageName, fs.first, IImageManager::CREATE_IMAGE_DEFAULT); ASSERT_FALSE(res); ASSERT_EQ(res.error_code(), FiemapStatus::ErrorCode::NO_SPACE) << res.string(); } bool Mkdir(const std::string& path) { if (mkdir(path.c_str(), 0700) && errno != EEXIST) { std::cerr << "Could not mkdir " << path << ": " << strerror(errno) << std::endl; Loading
fs_mgr/libstorage_literals/storage_literals/storage_literals.h +6 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ using B = Size<0>; using KiB = Size<10>; using MiB = Size<20>; using GiB = Size<30>; using TiB = Size<40>; constexpr B operator""_B(unsigned long long v) { // NOLINT return B{v}; Loading @@ -54,6 +55,10 @@ constexpr GiB operator""_GiB(unsigned long long v) { // NOLINT return GiB{v}; } constexpr TiB operator""_TiB(unsigned long long v) { // NOLINT return TiB{v}; } template <typename Dest, typename Src> constexpr Dest size_cast(Src src) { if (Src::power < Dest::power) { Loading @@ -69,6 +74,7 @@ static_assert(1_B == 1); static_assert(1_KiB == 1 << 10); static_assert(1_MiB == 1 << 20); static_assert(1_GiB == 1 << 30); static_assert(1_TiB == 1ULL << 40); static_assert(size_cast<KiB>(1_B).count() == 0); static_assert(size_cast<KiB>(1024_B).count() == 1); static_assert(size_cast<KiB>(1_MiB).count() == 1024); Loading