Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 68 additions & 4 deletions pkg/snclient/check_drivesize.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package snclient

import (
"context"
"errors"
"fmt"
"maps"
"sort"
Expand Down Expand Up @@ -151,6 +152,7 @@ func (l *CheckDrivesize) Build() *CheckData {
}
}

//nolint:funlen // no need to split the function, it is simple as is
func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData, _ []Argument) (*CheckResult, error) {
enabled, _, _ := snc.config.Section("/modules").GetBool("CheckDisk")
if !enabled {
Expand All @@ -164,14 +166,49 @@ func (l *CheckDrivesize) Check(ctx context.Context, snc *Agent, check *CheckData
}
requiredDisks := map[string]map[string]string{}
drives, err := l.getRequiredDisks(l.drives, false)
if err != nil {
return nil, err
var notFoundErr *PartitionNotFoundError
var notMountedErr *PartitionNotMountedError
var partitionDiscoveryErr *PartitionDiscoveryError

// if empty-state is overridden with the filter, emptyStateSet is true
// only handle the errors and return nil if its not overridden
if !check.emptyStateSet {
switch {
case errors.As(err, &notFoundErr):
log.Debugf("check_drivesize, drive is not found : %s, stopping check", notFoundErr.Error())
// do not return (nil,err), finalize the check wihtout entries to check.listData
// we want the empty-state override to work if its specified
case errors.As(err, &notMountedErr):
// no special handling of mount errors
log.Debugf("check_drivesize, mounting error : %s, stopping check", notMountedErr.Error())

return nil, err
case errors.As(err, &partitionDiscoveryErr):
// no special handling of discovery errors
log.Debugf("check_drivesize partition discovery error : %s, stopping check", partitionDiscoveryErr.Error())

return nil, err
case err != nil:
log.Debugf("check_drivesize error of unspecialized type : %s", err.Error())

return nil, err
default:
break
}
} else {
log.Debugf("check_drivesize, ignoring errors relating to drive discovery due to empty-state being set.")
}
maps.Copy(requiredDisks, drives)

// when checking for folders and their mountpoints, set parentFallback to true
folders, err := l.getRequiredDisks(l.folders, true)
if err != nil {
return nil, err
// ignore errors if emptyState set is true, just like the drives
if !check.emptyStateSet {
if err != nil {
return nil, err
}
} else {
log.Debugf("check_drivesize, ignoring errors relating to directory discovery due to empty-state being set.")
}
maps.Copy(requiredDisks, folders)

Expand Down Expand Up @@ -417,3 +454,30 @@ func (l *CheckDrivesize) getFlagNames(drive map[string]string) []string {

return flags
}

// have to define the error variables here, this file builds on all platforms
type PartitionNotFoundError struct {
Path string
err error
}

func (e *PartitionNotFoundError) Error() string {
return fmt.Sprintf("partition not found for path: %s , error: %s", e.Path, e.err.Error())
}

type PartitionNotMountedError struct {
Path string
err error
}

func (e *PartitionNotMountedError) Error() string {
return fmt.Sprintf("partition not mounted for path: %s , error: %s", e.Path, e.err.Error())
}

type PartitionDiscoveryError struct {
err error
}

func (e *PartitionDiscoveryError) Error() string {
return fmt.Sprintf("error on disk.partitions call: %s", e.err.Error())
}
28 changes: 17 additions & 11 deletions pkg/snclient/check_drivesize_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Check folder, no matter if its a mountpoint itself or not:
`
}

// if parentFallback is true, try to find a parent folder that is a mountpoint. If its false, only the exact matches are checked for mountpoints.
func (l *CheckDrivesize) getRequiredDisks(drives []string, parentFallback bool) (requiredDisks map[string]map[string]string, err error) {
// create map of required disks/volumes with "drive_or_id" as primary key
requiredDisks = map[string]map[string]string{}
Expand All @@ -58,6 +59,7 @@ func (l *CheckDrivesize) getRequiredDisks(drives []string, parentFallback bool)
return requiredDisks, nil
}

// setDisks fills the requiredDisks map with all available disks/partitions
func (l *CheckDrivesize) setDisks(requiredDisks map[string]map[string]string) (err error) {
partitions, err := disk.Partitions(true)
if err != nil {
Expand All @@ -80,13 +82,13 @@ func (l *CheckDrivesize) setDisks(requiredDisks map[string]map[string]string) (e
return nil
}

func (l *CheckDrivesize) setCustomPath(drive string, requiredDisks map[string]map[string]string, parentFallback bool) (err error) {
func (l *CheckDrivesize) setCustomPath(path string, requiredDisks map[string]map[string]string, parentFallback bool) (err error) {
// make sure path exists
_, err = os.Stat(drive)
_, err = os.Stat(path)
if err != nil && os.IsNotExist(err) {
log.Debugf("%s: %s", drive, err.Error())
log.Debugf("%s: %s", path, err.Error())

return fmt.Errorf("failed to find disk partition: %s", err.Error())
return &PartitionNotFoundError{Path: path, err: err}
}

// try to find closest matching mount
Expand All @@ -99,29 +101,33 @@ func (l *CheckDrivesize) setCustomPath(drive string, requiredDisks map[string]ma
var match *map[string]string
for i := range availMounts {
vol := availMounts[i]
if parentFallback && vol["drive"] != "" && strings.HasPrefix(drive, vol["drive"]) {
if parentFallback && vol["drive"] != "" && strings.HasPrefix(path, vol["drive"]) {
// try to find the longest matching parent path, that is a mountpoint
if match == nil || len((*match)["drive"]) < len(vol["drive"]) {
match = &vol
}
}
// direct match, no need to search further
if drive == vol["drive"] {
if path == vol["drive"] {
match = &vol

break
}
}

if match != nil {
requiredDisks[drive] = utils.CloneStringMap(*match)
requiredDisks[drive]["drive"] = drive
requiredDisks[path] = utils.CloneStringMap(*match)
requiredDisks[path]["drive"] = path

return nil
}

// add anyway to generate an error later with more default values filled in
entry := l.driveEntry(drive)
entry["_error"] = fmt.Sprintf("%s not mounted", drive)
requiredDisks[drive] = entry
entry := l.driveEntry(path)
entry["_error"] = (&PartitionNotMountedError{
Path: path, err: fmt.Errorf("path :%s does exist, but could not match it to a drive. its likely that the partition is not mounted", path),
}).Error()
requiredDisks[path] = entry

return nil
}
Expand Down
34 changes: 30 additions & 4 deletions pkg/snclient/check_drivesize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ func TestCheckDrivesize(t *testing.T) {
assert.Contains(t, string(res.BuildPluginOutput()), "/ free", "output matches")
assert.Contains(t, string(res.BuildPluginOutput()), ";0;;0;100", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=k"})
assert.Equalf(t, CheckExitUnknown, res.State, "state unknown")
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - failed to find disk partition", "output matches")

// must not work, folder is not a mountpoint
tmpFolder := t.TempDir()
res = snc.RunCheck("check_drivesize", []string{"warn=inodes>100%", "crit=inodes>100%", "drive=" + tmpFolder})
Expand All @@ -55,3 +51,33 @@ func TestCheckDrivesize(t *testing.T) {

StopTestAgent(t, snc)
}

func TestNonexistingDrive(t *testing.T) {
snc := StartTestAgent(t, "")

res := snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999"})
assert.Equalf(t, CheckExitUnknown, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=ok"})
assert.Equalf(t, CheckExitOK, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "OK - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=warn"})
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=warning"})
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=1"})
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=/dev/sdxyz999", "empty-state=crit"})
assert.Equalf(t, CheckExitCritical, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "CRITICAL - No drives found", "output matches")

StopTestAgent(t, snc)
}
7 changes: 5 additions & 2 deletions pkg/snclient/check_drivesize_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ func (l *CheckDrivesize) getRequiredDisks(drives []string, parentFallback bool)
case "all-volumes":
l.setVolumes(requiredDisks)
default:
// drives are like block devices c: -> /dev/sda
// partition is written into the disk in a partition table. it exists independently of volumes -> /dev/sdb3
// volumes are accessible storage areas where a filesystem is made onto a partition. it can be mounted and have files -> / , /tmp/ , /home
// "c" or "c:"" will use the drive c
// "c:\" will use the volume
// "c:\path" will use the best matching volume
Expand Down Expand Up @@ -318,7 +321,7 @@ func (l *CheckDrivesize) setDisks(requiredDisks map[string]map[string]string) (e
// "This drive is locked by BitLocker Drive Encryption. You must unlock this drive from Control Panel"
// but there can still be valid elements in partitions,
// so abort here only if partitions is empty.
return fmt.Errorf("disk partitions failed: %s", err.Error())
return &PartitionDiscoveryError{err: err}
}
for _, partition := range partitions {
drive := strings.TrimSuffix(partition.Device, "\\") + "\\"
Expand Down Expand Up @@ -443,7 +446,7 @@ func (l *CheckDrivesize) setCustomPath(drive string, requiredDisks map[string]ma
if err != nil && os.IsNotExist(err) {
log.Debugf("%s: %s", drive, err.Error())

return fmt.Errorf("failed to find disk partition: %s", err.Error())
return &PartitionNotFoundError{Path: drive, err: err}
}

// try to find closes matching volume
Expand Down
30 changes: 26 additions & 4 deletions pkg/snclient/check_drivesize_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@ func TestCheckDrivesize(t *testing.T) {
assert.Contains(t, string(res.BuildPluginOutput()), "C:\\ free", "output matches")
assert.Contains(t, string(res.BuildPluginOutput()), ";0;;0;100", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=k"})
assert.Equalf(t, CheckExitUnknown, res.State, "state unknown")
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - failed to find disk partition", "output matches")

res = snc.RunCheck("check_drivesize", []string{
"warning=used > 99",
"crit=used > 99.5",
Expand Down Expand Up @@ -68,3 +64,29 @@ func TestCheckDrivesize(t *testing.T) {

StopTestAgent(t, snc)
}

func TestNonexistingDrive(t *testing.T) {
snc := StartTestAgent(t, "")

res := snc.RunCheck("check_drivesize", []string{"drive=X"})
assert.Equalf(t, CheckExitUnknown, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "UNKNOWN - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=X:", "empty-state=warn"})
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=X:\\", "empty-state=warn"})
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=X", "empty-state=warn"})
assert.Equalf(t, CheckExitWarning, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "WARNING - No drives found", "output matches")

res = snc.RunCheck("check_drivesize", []string{"drive=X", "empty-state=crit"})
assert.Equalf(t, CheckExitCritical, res.State, "state OK")
assert.Contains(t, string(res.BuildPluginOutput()), "CRITICAL - No drives found", "output matches")

StopTestAgent(t, snc)
}