Skip to content

Commit

Permalink
🐛 fstab discovery improvements (#5207)
Browse files Browse the repository at this point in the history
* feat: VolumeMounter interface & mock

* check: empty device name

* feat: add PARTUUID support

* feat: more reliable fstab structure matching

* unit test: mountWithFstab

* fix: handle subvolumes

* lint

* feat: handle pre-mounted rood part

* fix: unknown platforms

* prevent fsConn from overwrite

* cleanup: use local CmdRunner instead of exposing CmdRunner at volumeMounter
  • Loading branch information
slntopp authored Feb 17, 2025
1 parent e706d5f commit 0d668b1
Show file tree
Hide file tree
Showing 10 changed files with 595 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ providers/mock_plugin_interface.go
providers/mock_schema.go
providers-sdk/*/testutils/mockprovider/resources/*.resources.json
!providers/core/resources/*.resources.json
providers/os/connection/snapshot/mock_volumemounter.go
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -730,7 +730,7 @@ race/go:
go test -race go.mondoo.com/cnquery/v11/explorer/scan

test/generate: prep/tools/mockgen
go generate ./providers
go generate ./providers/...

test/go: cnquery/generate test/generate test/go/plain

Expand Down
20 changes: 11 additions & 9 deletions providers/os/connection/device/device_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package device
import (
"errors"
"runtime"
"slices"
"strings"

"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -129,19 +130,14 @@ func NewDeviceConnection(connId uint32, conf *inventory.Config, asset *inventory

res.partitions[block.MountPoint] = block

if asset.Platform != nil {
log.Debug().Msg("device connection> asset already detected, skipping")
continue
}

if skipAssetDetection {
log.Debug().Msg("device connection> skipping asset detection as requested")
continue
}

if fsConn, err := tryDetectAsset(connId, block, conf, asset); err != nil {
log.Error().Err(err).Msg("partition did not return an asset, continuing")
} else {
} else if fsConn != nil {
res.FileSystemConnection = fsConn
}
}
Expand Down Expand Up @@ -252,8 +248,6 @@ func tryDetectAsset(connId uint32, partition *snapshot.PartitionInfo, conf *inve
return nil, errors.New("device connection> no platform detected")
}

log.Debug().Str("scan_dir", partition.MountPoint).Msg("device connection> detected platform from device")
asset.Platform = p
if asset.Name == "" && fingerprint != nil {
asset.Name = fingerprint.Name
}
Expand All @@ -265,5 +259,13 @@ func tryDetectAsset(connId uint32, partition *snapshot.PartitionInfo, conf *inve

asset.Id = conf.Type

return fsConn, nil
// volumes and partitions without a system on them would return an "unknown" platform
// we don't want to overwrite the platform if a "proper" one was detected already
// as well as we want to always have some platform to the asset
if asset.Platform == nil || !slices.Contains([]string{"", "unknown"}, p.Name) {
asset.Platform = p
return fsConn, nil
}

return nil, nil
}
82 changes: 60 additions & 22 deletions providers/os/connection/device/linux/device_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const (
)

type LinuxDeviceManager struct {
volumeMounter *snapshot.VolumeMounter
volumeMounter snapshot.VolumeMounter
cmdRunner *snapshot.LocalCommandRunner
opts map[string]string
}

Expand All @@ -46,6 +47,7 @@ func NewLinuxDeviceManager(shell []string, opts map[string]string) (*LinuxDevice

return &LinuxDeviceManager{
volumeMounter: snapshot.NewVolumeMounter(shell),
cmdRunner: &snapshot.LocalCommandRunner{Shell: shell},
opts: opts,
}, nil
}
Expand Down Expand Up @@ -161,7 +163,7 @@ func (d *LinuxDeviceManager) hintFSTypes(partitions []*snapshot.PartitionInfo) (
}

func (d *LinuxDeviceManager) attemptFindFstab(dir string) ([]resources.FstabEntry, error) {
cmd, err := d.volumeMounter.CmdRunner.RunCommand(fmt.Sprintf("find %s -type f -wholename '*/etc/fstab'", dir))
cmd, err := d.cmdRunner.RunCommand(fmt.Sprintf("find %s -type f -wholename '*/etc/fstab'", dir))
if err != nil {
log.Error().Err(err).Msg("error searching for fstab")
return nil, nil
Expand Down Expand Up @@ -281,46 +283,75 @@ func (d *LinuxDeviceManager) mountWithFstab(partitions []*snapshot.PartitionInfo
return pathDepth(entries[i].Mountpoint) < pathDepth(entries[j].Mountpoint)
})

rootScanDir := ""
for _, entry := range entries {
for i := range partitions {
if !cmpPartition2Fstab(partitions[i], entry) {
partition := partitions[i]
mustAppend := false
if !cmpPartition2Fstab(partition, entry) {
continue
}

if partitions[i].MountPoint == "" {
partitions[i].MountOptions = entry.Options
log.Debug().Str("device", partitions[i].Name).
Strs("options", partitions[i].MountOptions).
Msg("mounting partition")
mnt, err := d.Mount(partitions[i])
if err != nil {
log.Error().Err(err).Str("device", partitions[i].Name).Msg("unable to mount partition")
return partitions, err
log.Debug().
Str("device", partition.Name).
Str("guest-mountpoint", entry.Mountpoint).
Str("host-mountpouint", partition.MountPoint).
Msg("partition matches fstab entry")

// if the partition is already mounted
if partition.MountPoint != "" {
mountedWithFstab := strings.HasPrefix(partition.MountPoint, rootScanDir)
// mounted without fstab consideration, unmount it
if rootScanDir == "" || !mountedWithFstab {
log.Debug().Str("device", partition.Name).Msg("partition already mounted")
// if the partition is mounted and is the root, we can keep it mounted
if entry.Mountpoint == "/" {
rootScanDir = partition.MountPoint
continue
}
if err := d.volumeMounter.UmountP(partition); err != nil {
log.Error().Err(err).Str("device", partition.Name).Msg("unable to unmount partition")
continue
}
partition.MountPoint = ""
} else if mountedWithFstab { // mounted with fstab, duplicate the partition (probably a subvolume)
partitionCopy := *partition
partition = &partitionCopy
mustAppend = true
}
partitions[i].MountPoint = mnt

break
}

// if partitions is already mounted, but there is an entry in fstab, we should register it and mount as subvolume
partition := ptr.To(*partitions[i]) // copy the partition
var scanDir *string
if rootScanDir != "" {
scanDir = ptr.To(path.Join(rootScanDir, entry.Mountpoint))
}
partition.MountOptions = entry.Options

log.Debug().Str("device", partition.Name).
Strs("options", partition.MountOptions).
Str("scan-dir", path.Join(partition.MountPoint, entry.Mountpoint)).
Any("scan-dir", scanDir).
Msg("mounting partition as subvolume")
mnt, err := d.volumeMounter.MountP(&snapshot.MountPartitionDto{
PartitionInfo: partition,
ScanDir: ptr.To(path.Join(partition.MountPoint, entry.Mountpoint)),
ScanDir: scanDir,
})
if err != nil {
log.Error().Err(err).Str("device", partition.Name).Msg("unable to mount partition")
return partitions, err
}

partition.MountPoint = mnt
partitions = append(partitions, partition)
if entry.Mountpoint == "/" {
rootScanDir = mnt
}

break
if mustAppend {
partitions = append(partitions, partition)
} else {
partitions[i] = partition
}

break // partition matched, no need to check the rest
}
}
return partitions, nil
Expand Down Expand Up @@ -348,6 +379,8 @@ func cmpPartition2Fstab(partition *snapshot.PartitionInfo, entry resources.Fstab
return partition.Uuid == parts[1]
case "LABEL":
return partition.Label == parts[1]
case "PARTUUID":
return partition.PartUuid == parts[1]
default:
log.Warn().Str("device", entry.Device).Msg("couldn't identify fstab device")
return false
Expand Down Expand Up @@ -443,7 +476,12 @@ func (c *LinuxDeviceManager) identifyDeviceViaLun(lun int) ([]snapshot.BlockDevi
}

func (c *LinuxDeviceManager) identifyViaDeviceName(deviceName string, mountAll bool, includeMounted bool) ([]*snapshot.PartitionInfo, error) {
blockDevices, err := c.volumeMounter.CmdRunner.GetBlockDevices()
if deviceName == "" {
log.Warn().Msg("can't identify partition via device name, no device name provided")
return []*snapshot.PartitionInfo{}, nil
}

blockDevices, err := c.cmdRunner.GetBlockDevices()
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 0d668b1

Please sign in to comment.