blob: 4b0d30f0b401b024ce445c5280ac36a1a7e9643f [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "thinpool_migrator/stateful_metadata.h"
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <brillo/syslog_logging.h>
namespace thinpool_migrator {
namespace {
constexpr uint64_t kSectorSize = 512;
constexpr uint64_t kPhysicalExtentSize = 4 * 1024 * 1024;
constexpr uint64_t kStartingPhysicalExtentAddress = 1 * 1024 * 1024;
constexpr char kCreationHost[] = "localhost";
constexpr char kThinpoolLogicalVolume[] = "thinpool";
constexpr char kUnencryptedStatefulLogicalVolume[] = "unencrypted";
constexpr char kThinpoolDataVolume[] = "thinpool_tdata";
constexpr char kThinpoolMetadataVolume[] = "thinpool_tmeta";
constexpr char kThinpoolSpareMetaVolume[] = "lvol0_pmspare";
constexpr char kHiddenLogicalVolumeFlags[] = "\"READ\", \"WRITE\"";
constexpr char kVisibleLogicalVolumeFlags[] =
"\"READ\", \"WRITE\", \"VISIBLE\"";
constexpr char kVolumeGroupFlags[] = "\"READ\", \"WRITE\", \"RESIZEABLE\"";
constexpr char kStatusTemplate[] = "[%s]";
constexpr char kPhysicalVolumeStatus[] = "\"ALLOCATABLE\"";
constexpr char kPhysicalVolumeId[] = "pv0";
constexpr uint64_t kThinpoolChunkSize = 128;
constexpr uint64_t kExtentSize = 8192;
constexpr uint64_t kPEToChunkFactor = 64;
// Stateful header size in chunks (64k).
constexpr uint64_t kStatefulHeaderSize = 16;
} // namespace
StatefulMetadata::StatefulMetadata(const base::FilePath& stateful_device,
uint64_t device_size)
: stateful_device_(stateful_device),
device_size_(device_size),
pvuuid_(GenerateLvmDeviceId()),
volume_group_name_(GenerateVolumeGroupName()),
total_extent_count_((device_size_ - kStartingPhysicalExtentAddress) /
kPhysicalExtentSize),
// The metadata volume is allocated 1% of the extent count.
thinpool_metadata_volume_extent_count_(total_extent_count_ / 100),
// The thinpool extent count is what remains after two metadata
// partitions.
thinpool_extent_count_(total_extent_count_ -
2 * thinpool_metadata_volume_extent_count_),
// The thinpool data volume count is the same as the thinpool extent
// count.
thinpool_data_volume_extent_count_(thinpool_extent_count_),
// The unencrypted logical volume is given 95% of the extents of the
// thinpool.
unencrypted_stateful_extent_count_(95 * thinpool_extent_count_ / 100),
// The new stateful partition header resides in the last extent of the
// unencrypted logical volume.
new_stateful_header_location_(unencrypted_stateful_extent_count_ - 1) {}
PhysicalVolumeMetadata StatefulMetadata::GeneratePhysicalVolumeMetadata()
const {
return {.id = pvuuid_,
.device = stateful_device_.value(),
.status = base::StringPrintf(kStatusTemplate, kPhysicalVolumeStatus),
.flags = base::StringPrintf(kStatusTemplate, ""),
.dev_size = device_size_ / kSectorSize,
.pe_start = kStartingPhysicalExtentAddress / kSectorSize,
.pe_count = total_extent_count_};
}
// The first 1M of the partition contains LVM2 metadata, followed by the
// physical extents. Given the size of the unencrypted stateful logical
// volume in extents, the filesystem on the stateful partition needs to be
// resized down to be able to write the header to end of the logical
// volume.
uint64_t StatefulMetadata::GetResizedFilesystemSize() const {
return (GetUnencryptedStatefulExtentCount() - 1) * kPhysicalExtentSize +
kStartingPhysicalExtentAddress;
}
// The thinpool's metadata partition starts at the end of thinpool's data
// partition.
uint64_t StatefulMetadata::GetThinpoolMetadataOffset() const {
return GetThinpoolExtentCount() * kPhysicalExtentSize +
kStartingPhysicalExtentAddress;
}
uint64_t StatefulMetadata::GetThinpoolMetadataSize() const {
return GetThinpoolMetadataExtentCount() * kPhysicalExtentSize;
}
std::vector<LogicalVolumeMetadata>
StatefulMetadata::GenerateLogicalVolumeMetadata() const {
std::vector<LogicalVolumeMetadata> lv_metadata;
// Add logical volumes in order of creation.
// Actual metadata partition.
LogicalVolumeMetadata tpool_metadata = {
.name = kThinpoolMetadataVolume,
.id = GenerateLvmDeviceId(),
.status = base::StringPrintf(kStatusTemplate, kHiddenLogicalVolumeFlags),
.flags = base::StringPrintf(kStatusTemplate, ""),
.creation_time = base::Time::Now().ToTimeT(),
.creation_host = kCreationHost,
.segments = {{.start_extent = 0,
.extent_count = thinpool_metadata_volume_extent_count_,
.type = "striped",
.stripe.stripes = {{kPhysicalVolumeId,
thinpool_data_volume_extent_count_}}}}};
// Thinpool data volume.
LogicalVolumeMetadata tpool_data = {
.name = kThinpoolDataVolume,
.id = GenerateLvmDeviceId(),
.status = base::StringPrintf(kStatusTemplate, kHiddenLogicalVolumeFlags),
.flags = base::StringPrintf(kStatusTemplate, ""),
.creation_time = base::Time::Now().ToTimeT(),
.creation_host = kCreationHost,
.segments = {{.start_extent = 0,
.extent_count = thinpool_data_volume_extent_count_,
.type = "striped",
.stripe.stripes = {{kPhysicalVolumeId, 0}}}}};
// Spare metadata partition.
LogicalVolumeMetadata tpool_spare = {
.name = kThinpoolSpareMetaVolume,
.id = GenerateLvmDeviceId(),
.status = base::StringPrintf(kStatusTemplate, kHiddenLogicalVolumeFlags),
.flags = base::StringPrintf(kStatusTemplate, ""),
.creation_time = base::Time::Now().ToTimeT(),
.creation_host = kCreationHost,
.segments = {
{.start_extent = 0,
.extent_count = thinpool_metadata_volume_extent_count_,
.type = "striped",
.stripe.stripes = {{kPhysicalVolumeId,
thinpool_data_volume_extent_count_ +
thinpool_metadata_volume_extent_count_}}}}};
// Thinpool.
LogicalVolumeMetadata thinpool = {
.name = kThinpoolLogicalVolume,
.id = GenerateLvmDeviceId(),
.status = base::StringPrintf(kStatusTemplate, kVisibleLogicalVolumeFlags),
.flags = base::StringPrintf(kStatusTemplate, ""),
.creation_time = base::Time::Now().ToTimeT(),
.creation_host = kCreationHost,
.segments = {{.start_extent = 0,
.extent_count = thinpool_extent_count_,
.type = "thin-pool",
.thinpool.metadata = kThinpoolMetadataVolume,
.thinpool.pool = kThinpoolDataVolume,
.thinpool.transaction_id = 2,
.thinpool.chunk_size = kThinpoolChunkSize,
.thinpool.discards = "passdown",
.thinpool.zero_new_blocks = 0}}};
// Unenecrypted stateful.
LogicalVolumeMetadata unencrypted_lv = {
.name = kUnencryptedStatefulLogicalVolume,
.id = GenerateLvmDeviceId(),
.status = base::StringPrintf(kStatusTemplate, kVisibleLogicalVolumeFlags),
.flags = base::StringPrintf(kStatusTemplate, ""),
.creation_time = base::Time::Now().ToTimeT(),
.creation_host = kCreationHost,
.segments = {{.start_extent = 0,
.extent_count = unencrypted_stateful_extent_count_,
.type = "thin",
.thin.thin_pool = kThinpoolLogicalVolume,
.thin.transaction_id = 0,
.thin.device_id = 1}}};
return {thinpool, unencrypted_lv, tpool_spare, tpool_metadata, tpool_data};
}
VolumeGroupMetadata StatefulMetadata::GenerateVolumeGroupMetadata() const {
return {.name = volume_group_name_,
.id = GenerateLvmDeviceId(),
.seqno = 0,
.format = "lvm2",
.status = base::StringPrintf(kStatusTemplate, kVolumeGroupFlags),
.flags = base::StringPrintf(kStatusTemplate, ""),
.extent_size = kExtentSize,
.max_lv = 0,
.max_pv = 1,
.metadata_copies = 0,
.creation_time = base::Time::Now().ToTimeT(),
.pv_metadata = {GeneratePhysicalVolumeMetadata()},
.lv_metadata = GenerateLogicalVolumeMetadata()};
}
bool StatefulMetadata::DumpVolumeGroupConfiguration(
const base::FilePath& path) const {
if (path.empty()) {
LOG(INFO) << GenerateVolumeGroupMetadata().ToString();
return true;
}
return base::WriteFile(path, GenerateVolumeGroupMetadata().ToString());
}
bool StatefulMetadata::DumpThinpoolMetadataMappings(
const base::FilePath& path) const {
if (path.empty()) {
LOG(INFO) << GenerateThinpoolSuperblockMetadata().ToString();
return true;
}
return base::WriteFile(path, GenerateThinpoolSuperblockMetadata().ToString());
}
ThinpoolSuperblockMetadata
StatefulMetadata::GenerateThinpoolSuperblockMetadata() const {
return {
.uuid = "",
.time = 0,
.transaction = 2,
.version = 2,
.data_block_size = kThinpoolChunkSize,
.nr_data_blocks = unencrypted_stateful_extent_count_ * kPEToChunkFactor,
.device_mappings =
{
// Unencrypted stateful logical volume. This mapping sets up the
// volume as a combination of two mappings.
// 1) The 1M stateful header that was copied at the end of the
// filesystem is now the first 1M of the new volume.
// 2) The rest of the stateful filesystem comprises the remaining
// volume.
{
.device_id = 1,
.mapped_blocks =
unencrypted_stateful_extent_count_ * kPEToChunkFactor,
.transaction = 0,
.creation_time = 0,
.snap_time = 0,
.mappings =
{
// First mapping: relocated stateful header.
// Maps 16 64k blocks from the new stateful
// header location to the beginning of the new
// stateful logical volume.
{
.type = "range",
.mapping.range =
{
.origin_begin = 0,
.data_begin =
new_stateful_header_location_ *
kPEToChunkFactor,
.length = kStatefulHeaderSize,
},
.time = 0,
},
// Second mapping: maps stateful filesystem size -
// 16 64k blocks from the beginning of the
// thinpool-data logical volume.
{
.type = "range",
.mapping.range =
{
.origin_begin = kStatefulHeaderSize,
.data_begin = 0,
.length = new_stateful_header_location_ *
kPEToChunkFactor,
},
.time = 0,
},
},
},
},
};
}
} // namespace thinpool_migrator