Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⭐️ device manager: multi partition support #4719

Merged
merged 10 commits into from
Oct 9, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,5 @@ func (c *AzureSnapshotConnection) Type() shared.ConnectionType {
}

func (c *AzureSnapshotConnection) Config() *inventory.Config {
return c.DeviceConnection.Conf
return c.DeviceConnection.Conf()
}
2 changes: 1 addition & 1 deletion providers/gcp/connection/gcpinstancesnapshot/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,5 +300,5 @@ func (c *GcpSnapshotConnection) Type() shared.ConnectionType {
}

func (c *GcpSnapshotConnection) Config() *inventory.Config {
return c.DeviceConnection.Conf
return c.DeviceConnection.Conf()
}
6 changes: 6 additions & 0 deletions providers/os/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,12 @@ var Config = plugin.Provider{
Desc: "The serial number of the block device that should be scanned. Supported only for Windows scanning. Do not use together with --device-name or --lun",
Option: plugin.FlagOption_Hidden,
},
{
Long: "mount-all-partitions",
Type: plugin.FlagType_String,
Desc: "Mount all partitions of the block device",
Option: plugin.FlagOption_Hidden,
},
{
Long: "platform-ids",
Type: plugin.FlagType_List,
Expand Down
137 changes: 88 additions & 49 deletions providers/os/connection/device/device_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type DeviceConnection struct {
plugin.Connection
asset *inventory.Asset
deviceManager DeviceManager

MountedDirs []string
}

func getDeviceManager(conf *inventory.Config) (DeviceManager, error) {
Expand All @@ -54,73 +56,106 @@ func NewDeviceConnection(connId uint32, conf *inventory.Config, asset *inventory
if err != nil {
return nil, err
}
if len(blocks) != 1 {
// FIXME: remove this when we start scanning multiple blocks
return nil, errors.New("internal>blocks size is not equal to 1")
if len(blocks) == 0 {
return nil, errors.New("internal> no blocks found")
}
block := blocks[0]
log.Debug().Str("name", block.Name).Str("type", block.FsType).Msg("identified partition for mounting")

res := &DeviceConnection{
Connection: plugin.NewConnection(connId, asset),
deviceManager: manager,
asset: asset,
}

scanDir, err := manager.Mount(block)
if err != nil {
log.Error().Err(err).Msg("unable to complete mount step")
res.Close()
return nil, err
}
if conf.Options == nil {
conf.Options = make(map[string]string)
}
for i := range blocks {
block := blocks[i]
log.Debug().Str("name", block.Name).Str("type", block.FsType).Msg("identified partition for mounting")

conf.Options["path"] = scanDir
// create and initialize fs provider
fsConn, err := fs.NewConnection(connId, &inventory.Config{
Path: scanDir,
PlatformId: conf.PlatformId,
Options: conf.Options,
Type: "fs",
Record: conf.Record,
}, asset)
if err != nil {
res.Close()
return nil, err
}
scanDir, err := manager.Mount(block)
if err != nil {
log.Error().Err(err).Msg("unable to complete mount step")
res.Close()
return nil, err
}
res.MountedDirs = append(res.MountedDirs, scanDir)

if conf.Options == nil {
slntopp marked this conversation as resolved.
Show resolved Hide resolved
conf.Options = make(map[string]string)
}

res.FileSystemConnection = fsConn
// create and initialize fs provider
conf.Options["path"] = scanDir
preslavgerchev marked this conversation as resolved.
Show resolved Hide resolved
fsConn, err := fs.NewConnection(connId, &inventory.Config{
Path: scanDir,
PlatformId: conf.PlatformId,
Options: conf.Options,
Type: "fs",
Record: conf.Record,
}, asset)
if err != nil {
res.Close()
return nil, err
}

// allow injecting platform ids into the device connection. we cannot always know the asset that's being scanned, e.g.
// if we can scan an azure VM's disk we should be able to inject the platform ids of the VM
if platformIDs, ok := conf.Options[PlatformIdInject]; ok {
platformIds := strings.Split(platformIDs, ",")
if len(platformIds) > 0 {
log.Debug().Strs("platform-ids", platformIds).Msg("device connection> injecting platform ids")
conf.PlatformId = platformIds[0]
asset.PlatformIds = append(asset.PlatformIds, platformIds...)
// allow injecting platform ids into the device connection. we cannot always know the asset that's being scanned, e.g.
// if we can scan an azure VM's disk we should be able to inject the platform ids of the VM
if platformIDs, ok := conf.Options[PlatformIdInject]; ok {
platformIds := strings.Split(platformIDs, ",")
if len(platformIds) > 0 {
log.Debug().Strs("platform-ids", platformIds).Msg("device connection> injecting platform ids")
conf.PlatformId = platformIds[0]
asset.PlatformIds = append(asset.PlatformIds, platformIds...)
}
}

if asset.Platform != nil {
log.Debug().Msg("device connection> platform already detected")

// Edge case: asset platform is provided from the inventory
if res.FileSystemConnection == nil {
res.FileSystemConnection = fsConn
}
continue
}

p, ok := detector.DetectOS(fsConn)
if !ok {
log.Debug().
Str("block", block.Name).
Msg("device connection> cannot detect os")
continue
}
asset.Platform = p
asset.IdDetector = []string{ids.IdDetector_Hostname}
res.FileSystemConnection = fsConn

fingerprint, p, err := id.IdentifyPlatform(res, &plugin.ConnectReq{}, asset.Platform, asset.IdDetector)
if err != nil {
log.Debug().Err(err).Msg("device connection> failed to identify platform from device")
asset.Platform = nil
}

if err == nil {
log.Debug().Str("scan_dir", scanDir).Msg("device connection> detected platform from device")
if asset.Name == "" {
asset.Name = fingerprint.Name
}
asset.PlatformIds = append(asset.PlatformIds, fingerprint.PlatformIDs...)
asset.IdDetector = fingerprint.ActiveIdDetectors
asset.Platform = p
asset.Id = conf.Type
}
}

p, ok := detector.DetectOS(fsConn)
if !ok {
if asset.Platform == nil {
res.Close()
return nil, errors.New("failed to detect OS")
}
asset.Platform = p
asset.IdDetector = []string{ids.IdDetector_Hostname}
fingerprint, p, err := id.IdentifyPlatform(res, &plugin.ConnectReq{}, asset.Platform, asset.IdDetector)
if err == nil {
if asset.Name == "" {
asset.Name = fingerprint.Name
}
asset.PlatformIds = append(asset.PlatformIds, fingerprint.PlatformIDs...)
asset.IdDetector = fingerprint.ActiveIdDetectors
asset.Platform = p
asset.Id = conf.Type

if res.FileSystemConnection == nil {
res.Close()
return nil, errors.New("failed to create fs connection")
}

return res, nil
}

Expand Down Expand Up @@ -166,3 +201,7 @@ func (p *DeviceConnection) FileSystem() afero.Fs {
func (p *DeviceConnection) FileInfo(path string) (shared.FileInfoDetails, error) {
return p.FileSystemConnection.FileInfo(path)
}

func (p *DeviceConnection) Conf() *inventory.Config {
return p.FileSystemConnection.Conf
}
28 changes: 21 additions & 7 deletions providers/os/connection/device/linux/device_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import (
)

const (
LunOption = "lun"
DeviceName = "device-name"
LunOption = "lun"
DeviceName = "device-name"
MountAllPartitions = "mount-all-partitions"
)

type LinuxDeviceManager struct {
Expand Down Expand Up @@ -52,11 +53,11 @@ func (d *LinuxDeviceManager) IdentifyMountTargets(opts map[string]string) ([]*sn
return []*snapshot.PartitionInfo{pi}, nil
}

pi, err := d.identifyViaDeviceName(opts[DeviceName])
partitions, err := d.identifyViaDeviceName(opts[DeviceName], opts[MountAllPartitions] == "true")
preslavgerchev marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
return []*snapshot.PartitionInfo{pi}, nil
return partitions, nil
}

func (d *LinuxDeviceManager) Mount(pi *snapshot.PartitionInfo) (string, error) {
Expand Down Expand Up @@ -123,7 +124,7 @@ func (c *LinuxDeviceManager) identifyViaLun(lun int) (*snapshot.PartitionInfo, e
return device.GetMountablePartition()
}

func (c *LinuxDeviceManager) identifyViaDeviceName(deviceName string) (*snapshot.PartitionInfo, error) {
func (c *LinuxDeviceManager) identifyViaDeviceName(deviceName string, mountAll bool) ([]*snapshot.PartitionInfo, error) {
blockDevices, err := c.volumeMounter.CmdRunner.GetBlockDevices()
if err != nil {
return nil, err
Expand All @@ -133,7 +134,11 @@ func (c *LinuxDeviceManager) identifyViaDeviceName(deviceName string) (*snapshot
// this is a best-guess approach
if deviceName == "" {
// TODO: we should rename/simplify this method
return blockDevices.GetUnnamedBlockEntry()
pi, err := blockDevices.GetUnnamedBlockEntry()
if err != nil {
return nil, err
}
return []*snapshot.PartitionInfo{pi}, nil
}

// if we have a specific device we're looking for we can just ask only for that
Expand All @@ -142,5 +147,14 @@ func (c *LinuxDeviceManager) identifyViaDeviceName(deviceName string) (*snapshot
return nil, err
}

return device.GetMountablePartition()
if mountAll {
log.Debug().Str("device", device.Name).Msg("mounting all partitions")
return device.GetMountablePartitions(true)
}

pi, err := device.GetMountablePartition()
if err != nil {
return nil, err
}
return []*snapshot.PartitionInfo{pi}, nil
}
64 changes: 51 additions & 13 deletions providers/os/connection/snapshot/blockdevices.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (blockEntries BlockDevices) GetMountablePartitionByDevice(device string) (*
}

// sort the candidates by size, so we can pick the largest one
sortPartitionsBySize(partitions)
sortBlockDevicesBySize(partitions)

// return the largest partition. we can extend this to be a parameter in the future
devFsName := "/dev/" + partitions[0].Name
Expand Down Expand Up @@ -151,31 +151,55 @@ func (blockEntries BlockDevices) FindDevice(name string) (BlockDevice, error) {
}

// Searches all the partitions in the device and finds one that can be mounted. It must be unmounted, non-boot partition
// If multiple partitions meet this criteria, the largest one is returned.
func (device BlockDevice) GetMountablePartition() (*PartitionInfo, error) {
func (device BlockDevice) GetMountablePartitions(includeBoot bool) ([]*PartitionInfo, error) {
slntopp marked this conversation as resolved.
Show resolved Hide resolved
log.Debug().Str("device", device.Name).Msg("get partitions for device")
partitions := []BlockDevice{}
for _, partition := range device.Children {

blockDevices := device.Children
// sort the candidates by size, so we can pick the largest one
sortBlockDevicesBySize(blockDevices)

filter := func(partition BlockDevice) bool {
return partition.IsNoBootVolumeAndUnmounted()
}
if includeBoot {
filter = func(partition BlockDevice) bool {
return !partition.IsMounted()
}
}

partitions := []*PartitionInfo{}
for _, partition := range blockDevices {
log.Debug().Str("name", partition.Name).Int("size", partition.Size).Msg("checking partition")
if partition.IsNoBootVolumeAndUnmounted() {
if partition.FsType == "" {
log.Debug().Str("name", partition.Name).Msg("skipping partition without filesystem type")
continue
}
if filter(partition) {
log.Debug().Str("name", partition.Name).Msg("found suitable partition")
partitions = append(partitions, partition)
devFsName := "/dev/" + partition.Name
partitions = append(partitions, &PartitionInfo{Name: devFsName, FsType: partition.FsType})
}
}

if len(partitions) == 0 {
return nil, fmt.Errorf("no suitable partitions found on device %s", device.Name)
}

// sort the candidates by size, so we can pick the largest one
sortPartitionsBySize(partitions)
return partitions, nil
}

// If multiple partitions meet this criteria, the largest one is returned.
func (device BlockDevice) GetMountablePartition() (*PartitionInfo, error) {
// return the largest partition. we can extend this to be a parameter in the future
devFsName := "/dev/" + partitions[0].Name
return &PartitionInfo{Name: devFsName, FsType: partitions[0].FsType}, nil
partitions, err := device.GetMountablePartitions(false)
if err != nil {
return nil, err
}

return partitions[0], nil
}

func sortPartitionsBySize(partitions []BlockDevice) {
func sortBlockDevicesBySize(partitions []BlockDevice) {
sort.Slice(partitions, func(i, j int) bool {
return partitions[i].Size > partitions[j].Size
})
Expand All @@ -197,6 +221,20 @@ func (blockEntries BlockDevices) GetUnnamedBlockEntry() (*PartitionInfo, error)
return nil, errors.New("target volume not found on instance")
}

func (blockEntries BlockDevices) GetDeviceWithUnmountedPartitions() (BlockDevice, error) {
log.Debug().Msg("get device with unmounted partitions")
for i := range blockEntries.BlockDevices {
d := blockEntries.BlockDevices[i]
log.Debug().Str("name", d.Name).Interface("children", d.Children).Interface("mountpoint", d.MountPoint).Msg("found block device")
if d.MountPoint != "" { // empty string means it is not mounted
continue
}

return d, nil
}
return BlockDevice{}, errors.New("target block device not found on instance")
}

func (blockEntries BlockDevices) GetUnmountedBlockEntry() (*PartitionInfo, error) {
log.Debug().Msg("get unmounted block entry")
for i := range blockEntries.BlockDevices {
Expand Down Expand Up @@ -224,7 +262,7 @@ func findVolume(children []BlockDevice) *PartitionInfo {
if len(candidates) == 0 {
return nil
}
sortPartitionsBySize(candidates)
sortBlockDevicesBySize(candidates)
return &PartitionInfo{Name: "/dev/" + candidates[0].Name, FsType: candidates[0].FsType}
}

Expand Down
Loading
Loading