Skip to content
Open
4 changes: 4 additions & 0 deletions docs/checks/commands/check_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Naemon Config

| Argument | Description |
| ---------------------------- | -------------------------------------------------------------------------------------- |
| add-disk-size | Add the disk size used on disk for each file and directory. On unix systems this is calculated from the allocated blocks, on windows an additional API call is required for each entry. This calculation may be expensive. Default: false |
| add-files-only-once | When following symlinks, same file can be added multiple times. Enable this to track added files and stop adding them twice. Files will be added using the first path they were encountered with. Default: false |
| calculate-subdirectory-sizes | For subdirectories that are found under the search paths, calculate the subdirectory sizes based on found files. This calculation may be expensive. Default: false |
| file | Alias for path |
Expand Down Expand Up @@ -89,13 +90,16 @@ these can be used in filters and thresholds (along with the default attributes):
| access | Unix timestamp of last access time |
| creation | Unix timestamp when file was created |
| size | File size in bytes |
| disksize | File size on disk in bytes |
| written | Unix timestamp when file was last written to |
| write | Alias for written |
| age | Seconds since file was last written |
| version | Windows exe/dll file version (windows only) |
| line_count | Number of lines in the files (text files) |
| total_bytes | Total size over all files in bytes |
| total_size | Total size over all files as human readable bytes |
| total_diskbytes | Total disk size over all files in bytes |
| total_disksize | Total disk size over all files as human readable bytes |
| md5_checksum | MD5 checksum of the file |
| sha1_checksum | SHA1 checksum of the file |
| sha256_checksum | SHA256 checksum of the file |
Expand Down
21 changes: 0 additions & 21 deletions pkg/snclient/check_drivesize_windows_vhdx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,27 +36,6 @@ func hasElevatedPrivileges() bool {
return token.IsElevated()
}

// resolveLongPath expands 8.3 short names (ex.: C:\Users\RUNNER~1) to their long form.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to pkg/snclient/path_utils_windows.go

// diskpart stores the vhd backing file path and the mount point as given, so later
// select/detach calls need to use the exact same path string.
func resolveLongPath(path string) (string, error) {
pathPtr, err := windows.UTF16PtrFromString(path)
if err != nil {
return "", fmt.Errorf("GetLongName error when creating UTF16 string pointer from path %w", err)
}
size, _ := windows.GetLongPathName(pathPtr, nil, 0)
if size == 0 {
return "", fmt.Errorf("GetLongPathName returned no size for %s", path)
}
buf := make([]uint16, size)
res, _ := windows.GetLongPathName(pathPtr, &buf[0], size)
if res == 0 {
return "", fmt.Errorf("GetLongPathName returned 0 for %s", path)
}

return windows.UTF16ToString(buf[:res]), nil
}

func execDiskpart(t *testing.T, script string) (output string, err error) {
t.Helper()

Expand Down
58 changes: 58 additions & 0 deletions pkg/snclient/check_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const (
CheckFilesDefaultFollowSymlinks = true
CheckFilesDefaultAddFilesOnlyOnce = false
CheckFilesFollowSymlinkMaxDepth = 50
CheckFilesDefaultAddDiskSize = false

// CheckFilesDefaultMaxFiles is the default value for max-files unless specified by the user. Can be increased up to CheckFilesMaxFilesLimit
CheckFilesDefaultMaxFiles = 10000
Expand All @@ -51,6 +52,7 @@ type CheckFiles struct {
calculateSubdirectorySizes bool // constructor NewCheckFiles sets this as false
followSymlinks bool
addFilesOnlyOnce bool
addDiskSize bool
maxFiles int64 // maximum number of files
}

Expand All @@ -62,6 +64,7 @@ func NewCheckFiles() CheckHandler {
calculateSubdirectorySizes: false,
followSymlinks: CheckFilesDefaultFollowSymlinks,
addFilesOnlyOnce: CheckFilesDefaultAddFilesOnlyOnce,
addDiskSize: CheckFilesDefaultAddDiskSize,
maxFiles: CheckFilesDefaultMaxFiles,
}
}
Expand All @@ -88,6 +91,8 @@ func (l *CheckFiles) Build() *CheckData {
" Enable this to track added files and stop adding them twice. Files will be added using the first path they were encountered with. Default: %t", CheckFilesDefaultAddFilesOnlyOnce)},
"calculate-subdirectory-sizes": {value: &l.calculateSubdirectorySizes, description: "For subdirectories that are found under the search paths, " +
"calculate the subdirectory sizes based on found files. This calculation may be expensive. Default: false"},
"add-disk-size": {value: &l.addDiskSize, description: "Add the disk size used on disk for each file and directory. On unix systems this is " +
"calculated from the allocated blocks, on windows an additional API call is required for each entry. This calculation may be expensive. Default: false"},
"max-files": {value: &l.maxFiles, description: fmt.Sprintf("Maximum number of files to process. Default: %d", CheckFilesDefaultMaxFiles)},
},
detailSyntax: "%(name)",
Expand All @@ -106,13 +111,16 @@ func (l *CheckFiles) Build() *CheckData {
{name: "access", description: "Unix timestamp of last access time", unit: UDate},
{name: "creation", description: "Unix timestamp when file was created", unit: UDate},
{name: "size", description: "File size in bytes", unit: UByte},
{name: "disksize", description: "File size on disk in bytes", unit: UByte},
{name: "written", description: "Unix timestamp when file was last written to", unit: UDate},
{name: "write", description: "Alias for written", unit: UDate},
{name: "age", description: "Seconds since file was last written", unit: UDuration},
{name: "version", description: "Windows exe/dll file version (windows only)"},
{name: "line_count", description: "Number of lines in the files (text files)"},
{name: "total_bytes", description: "Total size over all files in bytes", unit: UByte},
{name: "total_size", description: "Total size over all files as human readable bytes", unit: UByte},
{name: "total_diskbytes", description: "Total disk size over all files in bytes", unit: UByte},
{name: "total_disksize", description: "Total disk size over all files as human readable bytes", unit: UByte},
{name: "md5_checksum", description: "MD5 checksum of the file"},
{name: "sha1_checksum", description: "SHA1 checksum of the file"},
{name: "sha256_checksum", description: "SHA256 checksum of the file"},
Expand Down Expand Up @@ -518,6 +526,14 @@ func (l *CheckFiles) populateEntryDetails(check *CheckData, entry map[string]str
entry["write"] = fmt.Sprintf("%d", fileInfoSys.Mtime.Unix())
entry["written"] = fmt.Sprintf("%d", fileInfoSys.Mtime.Unix())

if l.addDiskSize {
if diskSize, diskErr := getFileDiskSize(fileInfo, path); diskErr != nil {
log.Debugf("could not get disk size for %s: %s", path, diskErr.Error())
} else {
entry["disksize"] = fmt.Sprintf("%d", diskSize)
}
}

needVersion := check.HasThreshold("version") || check.HasMacro("version")
if needVersion {
version, err := getFileVersion(path)
Expand Down Expand Up @@ -623,6 +639,34 @@ func (l *CheckFiles) addGeneralMetrics(check *CheckData) {
}
}

// only calculate the total disk size when the attribute has been populated for the entries
if l.addDiskSize {
totalDiskSize := uint64(0)
for _, data := range check.listData {
if data["type"] == "file" {
totalDiskSize += convert.UInt64(data["disksize"])
}
}

if len(check.listData) > 0 || check.emptySyntax == "" {
check.details["total_diskbytes"] = fmt.Sprintf("%d", totalDiskSize)
check.details["total_disksize"] = humanize.IBytesF(convert.UInt64(totalDiskSize), 2)
}

if check.HasThreshold("total_disksize") {
check.result.Metrics = append(check.result.Metrics,
&CheckMetric{
ThresholdName: "total_disksize",
Name: "total_disksize",
Value: totalDiskSize,
Unit: "B",
Warning: check.warnThreshold,
Critical: check.critThreshold,
Min: &Zero,
})
}
}

// files do not have a 'count' atrribute, so this wont collide like 'size' would. No need for 'totalCount'
if check.HasThreshold("count") {
check.result.Metrics = append(check.result.Metrics,
Expand Down Expand Up @@ -735,8 +779,10 @@ func (l *CheckFiles) addSubDirMetrics(check *CheckData) {
}
}

//nolint:funlen // the length is ok, function is not complex
func (l *CheckFiles) addFileMetrics(check *CheckData) {
needSize := check.HasThreshold("size")
needDiskSize := l.addDiskSize && check.HasThreshold("disksize")
needAge := check.HasThreshold("age")
needAccess := check.HasThreshold("access")
needWritten := check.HasThreshold("written")
Expand All @@ -755,6 +801,18 @@ func (l *CheckFiles) addFileMetrics(check *CheckData) {
Min: &Zero,
})
}
if needDiskSize {
check.result.Metrics = append(check.result.Metrics,
&CheckMetric{
ThresholdName: "disksize",
Name: data["filename"] + " " + "disksize",
Value: convert.UInt64(data["disksize"]),
Unit: "B",
Warning: check.warnThreshold,
Critical: check.critThreshold,
Min: &Zero,
})
}
if needAge {
check.result.Metrics = append(check.result.Metrics,
&CheckMetric{
Expand Down
15 changes: 15 additions & 0 deletions pkg/snclient/check_files_helper_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,18 @@ func getFileInode(fi fs.FileInfo) (uint64, bool) {

return stat.Ino, true
}

// POSIX st_blocks is always reported in 512 byte units, this is independent of the filesystem block size
const statBlockSizeBytes = 512

func getFileDiskSize(fileInfo fs.FileInfo, _ string) (uint64, error) {
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("type assertion for fileInfo.Sys() failed")
}
if stat.Blocks < 0 {
return 0, fmt.Errorf("invalid negative block count: %d", stat.Blocks)
}

return uint64(stat.Blocks) * statBlockSizeBytes, nil
}
15 changes: 15 additions & 0 deletions pkg/snclient/check_files_helper_osx_bsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,18 @@ func getFileInode(fi fs.FileInfo) (uint64, bool) {

return stat.Ino, true
}

// POSIX st_blocks is always reported in 512 byte units, this is independent of the filesystem block size
const statBlockSizeBytes = 512

func getFileDiskSize(fileInfo fs.FileInfo, _ string) (uint64, error) {
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("type assertion for fileInfo.Sys() failed")
}
if stat.Blocks < 0 {
return 0, fmt.Errorf("invalid negative block count: %d", stat.Blocks)
}

return uint64(stat.Blocks) * statBlockSizeBytes, nil
}
Loading
Loading