host/AzureRecoveryLib/linux/BlockDeviceDetails.h (346 lines of code) (raw):
/*
+------------------------------------------------------------------------------------+
Copyright(c) Microsoft Corp. 2015
+------------------------------------------------------------------------------------+
File : BlockDeviceDetails.h
Description : BlockDeviceDetails class is implemented as a singleton. On Init() call
the signleton object will be initialized by loading lsblk json output
to its data member. It will offers interface to access the parsed details.
History : 6-11-2018 (Venu Sivanadham) - Created
+------------------------------------------------------------------------------------+
*/
#ifndef BLOCK_DEVICE_DETAILS_H
#include <string>
#include <vector>
#include <sstream>
#include <map>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/regex.hpp>
namespace AzureRecovery
{
inline bool IsStandardDevice(
const std::string& device)
{
boost::regex std_dev_name_expr("/dev/x*[svh]d[a-z]+\\d*");
return boost::regex_match(
device,
std_dev_name_expr);
}
inline std::string GetDiskNameFromPartition(
const std::string& partition)
{
std::string disk;
if(IsStandardDevice(partition))
disk = boost::trim_right_copy_if(
partition,
boost::is_digit());
return disk;
}
inline bool IsDeviceIdsMatching(
const std::string& ldev,
const std::string& rdev)
{
return boost::iequals(
boost::erase_all_copy(boost::trim_copy(ldev), "\""),
boost::erase_all_copy(boost::trim_copy(rdev), "\""));
}
typedef struct _fstab_entry
{
std::string device;
std::string mountpoint;
std::string fstype;
std::string mountopt;
std::string dump;
std::string pass;
_fstab_entry()
{}
_fstab_entry(const _fstab_entry& rhs)
{
device = rhs.device;
mountpoint = rhs.mountpoint;
fstype = rhs.fstype;
mountopt = rhs.mountopt;
dump = rhs.dump;
pass = rhs.pass;
}
_fstab_entry& operator =(const _fstab_entry& rhs)
{
if(this != &rhs)
{
device = rhs.device;
mountpoint = rhs.mountpoint;
fstype = rhs.fstype;
mountopt = rhs.mountopt;
dump = rhs.dump;
pass = rhs.pass;
}
return *this;
}
std::string ToString() const
{
std::stringstream ssFstab;
ssFstab << "device:" << device << ", ";
ssFstab << "mountpoint:" << mountpoint << ", ";
ssFstab << "fstype:" << fstype << ", ";
ssFstab << "mountopt:" << mountopt << ", ";
ssFstab << "dump:" << dump << ", ";
ssFstab << "pass:" << pass;
return ssFstab.str();
}
static bool Fill(_fstab_entry &entry,
const std::string &line)
{
std::string _line = boost::trim_copy(line);
// Ignore the comment line stats with '#'.
if (boost::starts_with(_line, "#"))
return false;
std::vector<std::string> tokens;
boost::split(tokens,
_line, boost::is_space(),
boost::token_compress_on);
// Valid line should have 6 fields as per fstab man page.
if (tokens.size() != 6)
return false;
entry.device = tokens[0];
entry.mountpoint = tokens[1];
entry.fstype = tokens[2];
entry.mountopt = tokens[3];
entry.dump = tokens[4];
entry.pass = tokens[5];
return true;
}
std::string GetFstabLine() const
{
std::stringstream line;
line << device << " ";
line << mountpoint << " ";
line << fstype << " ";
line << mountopt << " ";
line << dump << " ";
line << pass;
return line.str();
}
std::string GetFstabLineWithOptions(const std::string& opts) const
{
std::stringstream line;
std::string mnt_opts = boost::trim_copy(opts);
line << device << " ";
line << mountpoint << " ";
line << fstype << " ";
if (!mnt_opts.empty() &&
!boost::icontains(mountopt, mnt_opts))
line << mountopt << "," << mnt_opts << " ";
else
line << mountopt << " ";
line << dump << " ";
line << pass;
return line.str();
}
bool IsStandardDeviceName() const
{
return IsStandardDevice(device);
}
bool IsNfsDevice() const
{
// List of network filesystems.
// TODO: Add missing network filesystem type.
boost::regex fs_expr("nfs|cifs|smbfs|vboxsf|sshfs");
return boost::regex_match(fstype, fs_expr);
}
bool IsPersistentLocalDevice() const
{
// Persistent local devices starts with /dev/ or UUID= or LABEL=
return boost::istarts_with(device, "/dev/") ||
boost::istarts_with(device, "UUID=") ||
boost::istarts_with(device, "LABEL=");
}
bool IsBtrfsSubvolume() const
{
// Check if its a btrfs volume and its options has "subvol" tag.
return IsBtrfsVolume() &&
boost::icontains(mountopt, "subvol=");
}
bool IsBtrfsVolume() const
{
return boost::iequals(fstype, "btrfs");
}
// Returns disk name if its a standard partition
// otherwise it returns empty string.
std::string GetDiskName() const
{
return GetDiskNameFromPartition(device);
}
bool IsDeviceMatching(const std::string & device_id) const
{
return IsDeviceIdsMatching(device, device_id);
}
}fstab_entry;
typedef struct _block_dev_details
{
std::string name;
std::string type;
std::string uuid;
std::string label;
std::string fstype;
std::string mountpoint;
std::string flags;
std::vector< _block_dev_details> children;
_block_dev_details()
{}
_block_dev_details(const _block_dev_details& rhs)
{
name = rhs.name;
type = rhs.type;
uuid = rhs.uuid;
label = rhs.label;
fstype = rhs.fstype;
mountpoint = rhs.mountpoint;
flags = rhs.flags;
children.assign(
rhs.children.begin(),
rhs.children.end());
}
_block_dev_details& operator =(const _block_dev_details& rhs)
{
if (this != &rhs)
{
name = rhs.name;
type = rhs.type;
uuid = rhs.uuid;
label = rhs.label;
fstype = rhs.fstype;
mountpoint = rhs.mountpoint;
flags = rhs.flags;
children.assign(
rhs.children.begin(),
rhs.children.end());
}
return *this;
}
bool operator ==(const fstab_entry& fe) const
{
bool bMatching = false;
std::string device_name = boost::erase_all_copy(
boost::trim_copy(fe.device),
"\"");
// The device name in fstab entry can be UUID, LABEL or LV.
if (boost::istarts_with(device_name, "UUID="))
bMatching = boost::iequals(device_name, "UUID=" + uuid);
else if (boost::istarts_with(device_name, "LABEL="))
bMatching = boost::iequals(device_name, "LABEL=" + label);
else
{
bMatching = boost::iequals(device_name, name);
if (!bMatching)
{
if (boost::istarts_with(device_name, "/dev/mapper") ||
boost::istarts_with(name, "/dev/mapper"))
{
// 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}
size_t dn_mapPos = device_name.find("mapper");
size_t feName_mapPos = name.find("mapper");
// Check for different naming conventions.
if (dn_mapPos != std::string::npos &&
feName_mapPos == std::string::npos &&
device_name.find("-") != std::string::npos)
{
// fstab entry name is of Device Manager generated format while device_name is not.
std::string dnModifiedName = "/dev/" + (device_name.substr(dn_mapPos + 7)).replace
((device_name.substr(dn_mapPos + 7)).find("-"), 1, "/");
bMatching = boost::iequals(dnModifiedName, name);
}
else if (device_name.find("mapper") == std::string::npos &&
name.find("mapper") != std::string::npos &&
name.find("-") != std::string::npos)
{
// fstab entry name is of Device Manager generated format while device_name is not.
std::string feModifiedName = "/dev/" + (name.substr(feName_mapPos + 7)).replace
((name.substr(feName_mapPos + 7)).find("-"), 1, "/");
bMatching = boost::iequals(device_name, feModifiedName);
}
}
else if (boost::istarts_with(device_name, "/dev/disk/by-uuid") ||
boost::istarts_with(name, "/dev/disk/by-uuid"))
{
// Device Manager generated name: /dev/disk/by-uuid/<guid>
// LVM2 generated symbolic name structure: UUID=<guid>
size_t dn_mapPos = device_name.find("disk/by-uuid");
size_t feName_mapPos = name.find("disk/by-uuid");
// Check for different naming conventions.
if (dn_mapPos != std::string::npos &&
feName_mapPos == std::string::npos)
{
// fstab entry name is of Device Manager generated format while device_name is not.
std::string dnModifiedName = "UUID=" + (device_name.substr(dn_mapPos + 13));
bMatching = boost::iequals(dnModifiedName, name);
}
else if (device_name.find("disk/by-uuid") == std::string::npos &&
name.find("disk/by-uuid") != std::string::npos)
{
// fstab entry name is of Device Manager generated format while device_name is not.
std::string feModifiedName = "UUID=" + (name.substr(feName_mapPos + 13));
bMatching = boost::iequals(device_name, feModifiedName);
}
}
else if (boost::istarts_with(device_name, "/dev/disk/by-label") ||
boost::istarts_with(name, "/dev/disk/by-label"))
{
// Device Manager generated name: /dev/disk/by-label/<label>
// LVM2 generated symbolic name structure: LABEL=<label>
size_t dn_mapPos = device_name.find("disk/by-label");
size_t feName_mapPos = name.find("disk/by-label");
// Check for different naming conventions.
if (dn_mapPos != std::string::npos &&
feName_mapPos == std::string::npos)
{
// fstab entry name is of Device Manager generated format while device_name is not.
std::string dnModifiedName = "LABEL=" + (device_name.substr(dn_mapPos + 14));
bMatching = boost::iequals(dnModifiedName, name);
}
else if (device_name.find("disk/by-label") == std::string::npos &&
name.find("disk/by-label") != std::string::npos)
{
// fstab entry name is of Device Manager generated format while device_name is not.
std::string feModifiedName = "LABEL=" + (name.substr(feName_mapPos + 14));
bMatching = boost::iequals(device_name, feModifiedName);
}
}
}
}
return bMatching;
}
std::string ToString() const
{
std::stringstream ssDev;
ssDev << "name:" << name << ", ";
ssDev << "uuid:" << uuid << ", ";
ssDev << "type:" << type << ", ";
ssDev << "label:" << label << ", ";
ssDev << "fstype:" << fstype << ", ";
ssDev << "partflags:" << flags << ", ";
ssDev << "mountpoint:" << mountpoint << ", ";
ssDev << "child-count:" << children.size();
return ssDev.str();
}
// Returns disk name if its a standard partition
// otherwise it returns empty string.
std::string GetDiskName() const
{
return GetDiskNameFromPartition(name);
}
// Returns true if its a standard partition otherwise false.
bool IsStandardDeviceName() const
{
return IsStandardDevice(name);
}
bool IsValid() const
{
return !name.empty() &&
(!uuid.empty() || !label.empty()) &&
!fstype.empty();
}
}block_dev_details, volume_details, disk_details;
typedef std::vector<_block_dev_details>::const_iterator block_dev_iter, volume_iter;
class BlockDeviceDetails
{
std::vector<block_dev_details> m_disk_devices;
bool m_bInitialized;
static BlockDeviceDetails s_blob_dev_details_obj;
BlockDeviceDetails();
~BlockDeviceDetails();
static void LoadBlobDevDetails();
// Helper functions gathering block device details.
static block_dev_details ReadBlockDeviceEntry(
boost::property_tree::ptree::value_type &disk);
static std::string GetStringValue(
boost::property_tree::ptree node,
const std::string &key,
bool optional = true);
void ReadChildEntry(
std::map<std::string, volume_details> &uuid_volumes,
const volume_details& vol) const;
public:
static const BlockDeviceDetails& Instance()
{
if (!s_blob_dev_details_obj.m_bInitialized)
{
LoadBlobDevDetails();
}
return s_blob_dev_details_obj;
}
//TODO: Move below function to private section after testing.
static void ParseJson(std::stringstream& jsonSteam);
void GetVolumeDetails(std::vector<volume_details> & volumes,
bool ignoreMoutedVols = false) const;
void GetVolumeDetails(std::map<std::string, volume_details> &uuid_volumes) const;
void GetDiskDetails(std::vector<disk_details> & disks) const;
bool GetDisk(const std::string& disk_name, disk_details& disk) const;
void GetDiskNames(std::vector<std::string> & disks) const;
void GetDiskPartitions(const std::string &disk_name,
std::vector<volume_details> &standard_partitions) const;
};
}
#endif //~BLOCK_DEVICE_DETAILS_H