diff --git a/pkg/snclient/check_drivesize.go b/pkg/snclient/check_drivesize.go index 03f97c80..277f973d 100644 --- a/pkg/snclient/check_drivesize.go +++ b/pkg/snclient/check_drivesize.go @@ -2,6 +2,7 @@ package snclient import ( "context" + "errors" "fmt" "maps" "sort" @@ -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 { @@ -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, ¬FoundErr): + 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, ¬MountedErr): + // 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) @@ -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()) +} diff --git a/pkg/snclient/check_drivesize_other.go b/pkg/snclient/check_drivesize_other.go index 1ada75dd..6e65317d 100644 --- a/pkg/snclient/check_drivesize_other.go +++ b/pkg/snclient/check_drivesize_other.go @@ -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{} @@ -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 { @@ -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 @@ -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 } diff --git a/pkg/snclient/check_drivesize_test.go b/pkg/snclient/check_drivesize_test.go index a198fadd..4d03de95 100644 --- a/pkg/snclient/check_drivesize_test.go +++ b/pkg/snclient/check_drivesize_test.go @@ -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}) @@ -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) +} diff --git a/pkg/snclient/check_drivesize_windows.go b/pkg/snclient/check_drivesize_windows.go index dbba5d92..6ac16ef3 100644 --- a/pkg/snclient/check_drivesize_windows.go +++ b/pkg/snclient/check_drivesize_windows.go @@ -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 @@ -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, "\\") + "\\" @@ -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 diff --git a/pkg/snclient/check_drivesize_windows_test.go b/pkg/snclient/check_drivesize_windows_test.go index 967896ad..bfb9f214 100644 --- a/pkg/snclient/check_drivesize_windows_test.go +++ b/pkg/snclient/check_drivesize_windows_test.go @@ -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", @@ -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) +}