host/AzureRecoveryLib/linux/SourceFilesystemTree.cpp (451 lines of code) (raw):

/* +------------------------------------------------------------------------------------+ Copyright(c) Microsoft Corp. 2015 +------------------------------------------------------------------------------------+ File : SourceFilesystemTree.cpp Description : SourceFilesystemTree class implementation History : 6-11-2018 (Venu Sivanadham) - Created +------------------------------------------------------------------------------------+ */ #include "../config/RecoveryConfig.h" #include "../common/utils.h" #include "LinuxUtils.h" #include <boost/foreach.hpp> namespace AzureRecovery { SourceFilesystemTree SourceFilesystemTree::s_sft_obj; /* Method : SourceFilesystemTree::SourceFilesystemTree SourceFilesystemTree::~SourceFilesystemTree Description : SourceFilesystemTree constructor & destructor Parameters : None Return Code : None */ SourceFilesystemTree::SourceFilesystemTree() :m_bInitialized(false) { } SourceFilesystemTree::~SourceFilesystemTree() { } void SourceFilesystemTree::LoadFromConfig() { TRACE_FUNC_BEGIN; if (s_sft_obj.m_bInitialized) { // If its already initialized, then clear old entries. s_sft_obj.fs_tree_entries.clear(); s_sft_obj.m_bInitialized = false; } // root is always a seperate partition and // etc as seperate partition is not supported. // So /:etc/fstab is built-in entry. fs_tree_entry root_entry; root_entry.mountpoint = "/"; root_entry.files_to_detect.push_back("etc/fstab"); root_entry.is_seperate_partition = true; s_sft_obj.fs_tree_entries.push_back(root_entry); std::string settings = AzureRecoveryConfig::Instance() .GetFilesToDetectSystemPartitions(); if (boost::trim_copy(settings).empty()) settings = "/boot:grub/menu.lst,grub/grub.conf,grub2/grub.cfg,grub/grub.cfg;/boot/efi:;/var:;/var/log:;/usr:;/usr/local:"; std::vector<std::string> fs_entry_settings; boost::split(fs_entry_settings, settings, boost::is_any_of(";"), boost::token_compress_on); BOOST_FOREACH(const std::string& fs_entry_setting, fs_entry_settings) { std::vector<std::string> partition_files; boost::split(partition_files, fs_entry_setting, boost::is_any_of(":"), boost::token_compress_on); if (partition_files.size() != 2) { TRACE_WARNING("Invalid SFT setting [%s] found.", fs_entry_setting.c_str()); continue; } fs_tree_entry entry; entry.mountpoint = boost::trim_copy(partition_files[0]); if (boost::equals(entry.mountpoint, "/")) { TRACE_WARNING("root(/) and its detection files are default,\ it can not be controlled with config settings.\n"); continue; } std::string files = boost::trim_copy(partition_files[1]); if (!files.empty()) { boost::split(entry.files_to_detect, files, boost::is_any_of(","), boost::token_compress_on); } s_sft_obj.fs_tree_entries.push_back(entry); } s_sft_obj.m_bInitialized = true; TRACE_FUNC_END; } bool SourceFilesystemTree::DetectPartition( const std::string &mountpoint, std::string& detected_partion, const volume_details& volume) { TRACE_FUNC_BEGIN; bool bPartitoinFound = false; std::vector<fs_tree_entry>::iterator i_fs_entry = fs_tree_entries.begin(); for(;i_fs_entry != fs_tree_entries.end(); i_fs_entry++) { // If no detection file is present or its already detected // then skip and move to next entry. if (i_fs_entry->files_to_detect.size() == 0 || i_fs_entry->is_discovered) { TRACE_INFO("Skipping [%s] from detection as it has %s.\n", i_fs_entry->mountpoint.c_str(), i_fs_entry->is_discovered ? "already detected" : "no detection files"); continue; } TRACE_INFO("Detecting %s.\n", i_fs_entry->mountpoint.c_str()); if (VerifyFiles(mountpoint, i_fs_entry->files_to_detect, false)) { // TODO: Multi OS is not supported. If same partition // is detection second time then throw error. TRACE_INFO("Detection file found in source volume/partition: %s.\n", volume.name.c_str()); bPartitoinFound = true; detected_partion = i_fs_entry->mountpoint; i_fs_entry->is_discovered = true; i_fs_entry->is_seperate_partition = true; i_fs_entry->vol_info = volume; break; } else if (boost::iequals(i_fs_entry->mountpoint, "/") && boost::iequals(volume.fstype, "btrfs")) { // If its btrfs and we are looking for root (/) partition // then mount each subvolume of the btrfs filesystem and search // for detection files as the root volume might not be default always. TRACE_INFO("Searching btrfs subvolumes for root.\n"); bool is_mounted = true; std::vector<std::string> subvol_ids; // Fetch the subvolume Ids of the btrfs volume. // Ignoring return code as the search logic will fail // eventually if the desired subvolumes are not listed. GetBtrfsSubVolumeIds(mountpoint, subvol_ids); BOOST_FOREACH(const std::string& subvol_id, subvol_ids) { // Unmount already mounted volume and mount the current subvolume. if (is_mounted && UnMountPartition(mountpoint, true) != 0) { TRACE_ERROR("Could not unmount the btrfs volume.\n"); break; } std::string device = "UUID=" + volume.uuid; std::string opts = "-o subvolid=" + subvol_id; if (MountPartition(device, mountpoint, volume.fstype, opts) != 0) { TRACE_WARNING("Could not mount the the btrfs subvolume with Id %s.\n", subvol_id.c_str()); is_mounted = false; // Continue with next volumes. continue; } else { is_mounted = true; } TRACE_INFO("Searching in subolume %s.\n", subvol_id.c_str()); if (VerifyFiles(mountpoint, i_fs_entry->files_to_detect, false)) { TRACE_INFO("Detection file found in source subvolume of %s.\n", volume.name.c_str()); bPartitoinFound = true; detected_partion = i_fs_entry->mountpoint; i_fs_entry->is_discovered = true; i_fs_entry->is_seperate_partition = true; i_fs_entry->vol_info = volume; break; } } // If root partition is found then break the loop. if (bPartitoinFound) break; } else { TRACE_INFO("Detection files not found in source volume/partition: %s.\n", volume.name.c_str()); } } TRACE_FUNC_END; return bPartitoinFound; } void SourceFilesystemTree::UpdateSrcFstabDetails( const std::vector<fstab_entry>& fstab_entries) { TRACE_FUNC_BEGIN; BOOST_ASSERT(!fstab_entries.empty()); std::vector<fs_tree_entry>::iterator i_fs_entry = fs_tree_entries.begin(); for (; i_fs_entry != fs_tree_entries.end(); i_fs_entry++) { // Find corresponding fstab entry for the pre-defined system mountpoint. BOOST_FOREACH(const fstab_entry& src_fstab_entry, fstab_entries) { if (i_fs_entry->IsMountpointMatching(src_fstab_entry.mountpoint)) { TRACE_INFO("fstab entry found for %s.\n", i_fs_entry->mountpoint.c_str()); i_fs_entry->src_fstab_entry = src_fstab_entry; // fstab entries will conform which system mountpoints // are really seperate partitions on source VM. Also exclude // the entry if its a btrfs subvolume. if (src_fstab_entry.IsBtrfsSubvolume()) { TRACE_INFO("Its a btrfs subvolume %s.\n", i_fs_entry->mountpoint.c_str()); } else { i_fs_entry->is_seperate_partition = true; } break; } } // If its a seperate partition and standard device then // let detection logic identify it. Rest of them are either // not a seperate patitions or has uniqu identity such as: // UUID,LABEL,lvm or a network-path so they can be marked // as is_discovered = true. if (i_fs_entry->is_seperate_partition && i_fs_entry->src_fstab_entry.IsStandardDeviceName()) { TRACE_INFO("%s has standard device name in fstab, not marking it as discovered. Device Name: %s.\n", i_fs_entry->mountpoint.c_str(), i_fs_entry->src_fstab_entry.device.c_str()); // By default is_discovered = false, not assigning false again. // Also, don't want to overwrite if it was discovered already by // detection logic. } else { i_fs_entry->is_discovered = true; TRACE_INFO("%s has unique device name in fstab (or) not a seperate partition, marking it as discovered. Device Name: %s.\n", i_fs_entry->mountpoint.c_str(), i_fs_entry->src_fstab_entry.device.c_str()); } } TRACE_FUNC_END; } void SourceFilesystemTree::UpdateFstabSubvolDetails( const std::vector<fstab_entry>& fstab_entries) { TRACE_FUNC_BEGIN; BOOST_ASSERT(!fstab_entries.empty()); std::vector<fs_tree_entry>::iterator i_fs_entry = fs_tree_entries.begin(); for (; i_fs_entry != fs_tree_entries.end(); i_fs_entry++) { // Check if its a btrfs filesystem. if (!i_fs_entry->is_seperate_partition || !i_fs_entry->is_discovered || !i_fs_entry->src_fstab_entry.IsBtrfsVolume()) { continue; } TRACE_INFO("Finding subvolumes for the btrfs volume: %s\n", i_fs_entry->src_fstab_entry.mountpoint.c_str()); // Find subvolumes for the btrfs filesystem. BOOST_FOREACH(const fstab_entry& src_fstab_entry, fstab_entries) { // Ignore if it is not a btrfs subvolume or // same as current fs_tree_entry fstab_entry. if (!src_fstab_entry.IsBtrfsSubvolume() || boost::iequals( i_fs_entry->src_fstab_entry.mountpoint, src_fstab_entry.mountpoint)) { continue; } // Current fstab_entry should match with either // sft fstab_entry or volume info. if (i_fs_entry->src_fstab_entry.IsDeviceMatching( src_fstab_entry.device) || (i_fs_entry->vol_info.IsValid() && (i_fs_entry->vol_info == src_fstab_entry))) { TRACE_INFO("[%s] is subvolume of [%s].\n", src_fstab_entry.mountpoint.c_str(), i_fs_entry->src_fstab_entry.mountpoint.c_str()); i_fs_entry->subvol_fstab_entries[src_fstab_entry.mountpoint] = src_fstab_entry; } else { TRACE_INFO("[%s] is not subvolume of [%s].\n", src_fstab_entry.mountpoint.c_str(), i_fs_entry->src_fstab_entry.mountpoint.c_str()); } } } TRACE_FUNC_END; } bool SourceFilesystemTree::ShouldContinueDetection() const { BOOST_FOREACH(const fs_tree_entry& entry, fs_tree_entries) if (!entry.is_discovered && !entry.files_to_detect.empty()) return true; return false; } void SourceFilesystemTree::GetRealSystemPartitions( std::vector<fs_tree_entry>& sft_entries) const { BOOST_FOREACH(const fs_tree_entry& entry, fs_tree_entries) { if (entry.is_seperate_partition) sft_entries.push_back(entry); } } bool SourceFilesystemTree::IsDiscoveryComplete() const { BOOST_FOREACH(const fs_tree_entry& entry, fs_tree_entries) { if (entry.is_seperate_partition && !entry.is_discovered) return false; } return true; } void SourceFilesystemTree::GetUndiscoveredSrcSysMountpoints( std::vector<std::string> &mountpoints) const { mountpoints.clear(); BOOST_FOREACH(const fs_tree_entry& entry, fs_tree_entries) { if (entry.is_seperate_partition && !entry.is_discovered) mountpoints.push_back(entry.mountpoint); } } // This function uses standard device name heuristics to // find corresponding volume details of source fstab entry. // It only works on standard parition fstab entry. bool SourceFilesystemTree::FindVolForFstabEntry( const fstab_entry& entry, volume_details &vol_details) const { TRACE_FUNC_BEGIN; bool bFound = false; do { // This logic works only on standard partition fstab entries. if (!entry.IsStandardDeviceName()) break; std::string src_disk_name = entry.GetDiskName(); std::map<std::string, std::string>::const_iterator i_tgt_disk = src_target_disks.find(src_disk_name); // Stop processing if partition's disk name not found in the map. if (i_tgt_disk == src_target_disks.end()) break; std::string tgt_disk_name = i_tgt_disk->second; // If fstab_entry refers to disk itself then // get and return its target disk details. if (entry.IsDeviceMatching(src_disk_name)) { TRACE_INFO("The fstab entry refers to disk device. Mapping: [%s] => [%s].\n", entry.device.c_str(), tgt_disk_name.c_str()); bFound = BlockDeviceDetails::Instance().GetDisk( tgt_disk_name, vol_details); break; } // fstab_entry refes to disk partition, form its corresponding // target partition name using disk mapping. std::string part_num = boost::erase_first_copy( entry.device, src_disk_name); std::string tgt_disk_part = tgt_disk_name + part_num; TRACE("Source to target partition mapping: [%s] => [%s].\n", entry.device.c_str(), tgt_disk_part.c_str()); std::vector<volume_details> disk_partitions; BlockDeviceDetails::Instance().GetDiskPartitions( tgt_disk_name, disk_partitions); BOOST_FOREACH(const volume_details &vol, disk_partitions) { if (boost::iequals(vol.name, tgt_disk_part)) { vol_details = vol; TRACE_INFO("Successfully mapped undiscovered partition.\n\ FsTab entry: %s.\nMapped partition :%s.\n", entry.ToString().c_str(), vol_details.ToString().c_str()); bFound = true; break; } if (!bFound) { // LVM Nomenclature ({vgname}: Volume Group Name, {lvname}: Logical Volume Name) // Device Manager generated name: /dev/mapper/{vgname}-{lvname} // LVM2 generated symbolic name structure: /dev/{vgname}/{lvname} // vol.name reperesents fstab volume name // tgt_disk_part represents the name of partition on the hydration VM. size_t vn_mapPos = vol.name.find("mapper"); size_t tgtDiskPart_mapPos = tgt_disk_part.find("mapper"); // Check for different naming conventions. if (vn_mapPos != std::string::npos && tgtDiskPart_mapPos == std::string::npos) { // fstab entry name is of Device Manager generated format while vol.name is not. std::string vnModifiedName = "/dev/" + (vol.name.substr(vn_mapPos + 7)).replace ((vol.name.substr(vn_mapPos + 7)).find("-"), 1, "/"); TRACE_INFO("Modified vol name: %s", vnModifiedName.c_str()); bFound = boost::iequals(vnModifiedName, tgt_disk_part); } else if (vn_mapPos == std::string::npos && tgtDiskPart_mapPos != std::string::npos) { // vol.name is of Device Manager generated format while tgt_disk_part is not. std::string tgtDiskModifiedName = "/dev/" + (tgt_disk_part.substr(tgtDiskPart_mapPos + 7)).replace ((tgt_disk_part.substr(tgtDiskPart_mapPos + 7)).find("-"), 1, "/"); TRACE_INFO("Modified target disk name: %s", tgtDiskModifiedName.c_str()); bFound = boost::iequals(vol.name, tgtDiskModifiedName); } if (bFound) { break; } } } } while (false); TRACE_FUNC_END; return bFound; } void SourceFilesystemTree::DetectUndiscoveredStandardPartitions() { TRACE_FUNC_BEGIN; // We will use source => target (disk name on hydration vm) name // mapping to identifiy undiscovered system partitions with // standard device names. // Refresh the disk mapping with discovered partitions. RefreshSourceToTargetDiskMap(); // If there is no mapping found then exit the function. if (src_target_disks.empty()) return; std::vector<fs_tree_entry>::iterator i_entry = fs_tree_entries.begin(); for (; i_entry != fs_tree_entries.end(); i_entry++) { // If the entry is not a seperate partition or // it is already discovered or if its not a // standard partition namethen move to next entry. if (!i_entry->is_seperate_partition || i_entry->is_discovered || !i_entry->src_fstab_entry.IsStandardDeviceName()) continue; if (FindVolForFstabEntry(i_entry->src_fstab_entry, i_entry->vol_info)) { i_entry->is_discovered = true; TRACE_INFO("Successfully detected %s.\n", i_entry->ToString().c_str()); } } TRACE_FUNC_END; } void SourceFilesystemTree::RefreshSourceToTargetDiskMap() { TRACE_FUNC_BEGIN; BOOST_FOREACH(const fs_tree_entry& i_fs_entry, fs_tree_entries) { if (i_fs_entry.is_discovered && i_fs_entry.is_seperate_partition && i_fs_entry.vol_info.IsStandardDeviceName() && i_fs_entry.src_fstab_entry.IsStandardDeviceName()) { std::string src_disk = i_fs_entry.src_fstab_entry.GetDiskName(); std::string tgt_disk = i_fs_entry.vol_info.GetDiskName(); TRACE_INFO("Detected source to target disk mapping: %s => %s\n", src_disk.c_str(), tgt_disk.c_str()); if (src_target_disks.find(src_disk) != src_target_disks.end()) TRACE_INFO("Mapping already exist for %s.\n", src_disk.c_str()); else src_target_disks[src_disk] = tgt_disk; } else { TRACE_INFO("File System Entry details %s",i_fs_entry.ToString().c_str()); } } TRACE_FUNC_END; } void _fs_tree_entry::MountSubvolumes() const { TRACE_FUNC_BEGIN; BOOST_FOREACH(const t_pair_subvol &subvol, subvol_fstab_entries) { std::string mountopts = "-o " + subvol.second.mountopt; std::string mountpnt = SMS_AZURE_CHROOT + subvol.second.mountpoint; std::string fstype = subvol.second.fstype; std::string device_name; if (subvol.second.IsStandardDeviceName()) { if (vol_info.IsValid()) { // As per IsValid check, either UUID or LABEL should be valid value. if (!vol_info.uuid.empty()) device_name = "UUID=" + vol_info.uuid; else if (!vol_info.label.empty()) device_name = "LABEL=" + vol_info.label; } else { TRACE_WARNING( "Could not find device to mount for the subvolume: [%s]\n.", subvol.second.ToString().c_str()); continue; } } else { // Use fstab_entry device for mount command. device_name = subvol.second.device; } TRACE_INFO("Mounting subvolume %s, device: %s fstype: %s optins: %s\n", mountpnt.c_str(), device_name.c_str(), fstype.c_str(), mountopts.c_str()); BOOST_ASSERT(!device_name.empty()); MountPartition(device_name, mountpnt, fstype, mountopts); } TRACE_FUNC_END; } }