mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-03-01 16:16:35 +00:00
logical_disk: Get Volume ID for NTFS Volume Mounts (#1752)
Signed-off-by: Jan-Otto Kröpke <mail@jkroepke.de>
This commit is contained in:
@@ -59,6 +59,7 @@ type Collector struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type volumeInfo struct {
|
type volumeInfo struct {
|
||||||
|
diskIDs string
|
||||||
filesystem string
|
filesystem string
|
||||||
serialNumber string
|
serialNumber string
|
||||||
label string
|
label string
|
||||||
@@ -288,9 +289,8 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
|
|||||||
// to the provided prometheus Metric channel.
|
// to the provided prometheus Metric channel.
|
||||||
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
diskID string
|
info volumeInfo
|
||||||
info volumeInfo
|
|
||||||
)
|
)
|
||||||
|
|
||||||
perfData, err := c.perfDataCollector.Collect()
|
perfData, err := c.perfDataCollector.Collect()
|
||||||
@@ -299,19 +299,10 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name, volume := range perfData {
|
for name, volume := range perfData {
|
||||||
if name == "_Total" ||
|
if c.config.VolumeExclude.MatchString(name) || !c.config.VolumeInclude.MatchString(name) {
|
||||||
c.config.VolumeExclude.MatchString(name) ||
|
|
||||||
!c.config.VolumeInclude.MatchString(name) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
diskID, err = getDiskIDByVolume(name)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warn("failed to get disk ID for "+name,
|
|
||||||
slog.Any("err", err),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err = getVolumeInfo(name)
|
info, err = getVolumeInfo(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Warn("failed to get volume information for "+name,
|
c.logger.Warn("failed to get volume information for "+name,
|
||||||
@@ -323,7 +314,7 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error {
|
|||||||
c.information,
|
c.information,
|
||||||
prometheus.GaugeValue,
|
prometheus.GaugeValue,
|
||||||
1,
|
1,
|
||||||
diskID,
|
info.diskIDs,
|
||||||
info.volumeType,
|
info.volumeType,
|
||||||
name,
|
name,
|
||||||
info.label,
|
info.label,
|
||||||
@@ -472,38 +463,60 @@ func getDriveType(driveType uint32) string {
|
|||||||
const diskExtentSize = 24
|
const diskExtentSize = 24
|
||||||
|
|
||||||
// getDiskIDByVolume returns the disk ID for a given volume.
|
// getDiskIDByVolume returns the disk ID for a given volume.
|
||||||
func getDiskIDByVolume(rootDrive string) (string, error) {
|
func getVolumeInfo(rootDrive string) (volumeInfo, error) {
|
||||||
// Open a volume handle to the Disk Root.
|
volumePath := rootDrive
|
||||||
var err error
|
|
||||||
|
|
||||||
var f windows.Handle
|
// If rootDrive is a NTFS directory, convert it to a volume GUID.
|
||||||
|
if strings.Contains(volumePath, `\`) {
|
||||||
|
volumePathName, err := windows.UTF16PtrFromString(volumePath + `\`)
|
||||||
|
if err != nil {
|
||||||
|
return volumeInfo{}, fmt.Errorf("could not convert rootDrive to volume path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeGUIDPtr := make([]uint16, 50)
|
||||||
|
if err := windows.GetVolumeNameForVolumeMountPoint(volumePathName, &volumeGUIDPtr[0], uint32(len(volumeGUIDPtr))); err != nil {
|
||||||
|
panic(fmt.Errorf("could not get volume GUID: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
volumePath = windows.UTF16ToString(volumeGUIDPtr)
|
||||||
|
|
||||||
|
// GetVolumeNameForVolumeMountPoint returns the volume GUID path as \\?\Volume{GUID}\
|
||||||
|
// According https://learn.microsoft.com/en-us/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol#remarks
|
||||||
|
// Win32 Drive Namespace is prefixed with \\.\, so we need to remove the \\?\ prefix.
|
||||||
|
volumePath, _ = strings.CutPrefix(volumePath, `\\?\`)
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/55710326/how-to-get-the-physical-device-that-a-volume-guid-path-belongs-to#comment98104360_55710326
|
||||||
|
// DeviceIoControl expects no trailing backslash in the volume GUID path.
|
||||||
|
volumePath = strings.TrimRight(volumePath, `\`)
|
||||||
|
}
|
||||||
|
|
||||||
|
volumePathPtr := windows.StringToUTF16Ptr(`\\.\` + volumePath)
|
||||||
|
|
||||||
// mode has to include FILE_SHARE permission to allow concurrent access to the disk.
|
// mode has to include FILE_SHARE permission to allow concurrent access to the disk.
|
||||||
// use 0 as access mode to avoid admin permission.
|
// use 0 as access mode to avoid admin permission.
|
||||||
mode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE)
|
mode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE)
|
||||||
|
attr := uint32(windows.FILE_ATTRIBUTE_READONLY)
|
||||||
|
|
||||||
f, err = windows.CreateFile(
|
volumeHandle, err := windows.CreateFile(volumePathPtr, 0, mode, nil, windows.OPEN_EXISTING, attr, 0)
|
||||||
windows.StringToUTF16Ptr(`\\.\`+rootDrive),
|
|
||||||
0, mode, nil, windows.OPEN_EXISTING, uint32(windows.FILE_ATTRIBUTE_READONLY), 0)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return volumeInfo{}, fmt.Errorf("could not open volume for %s: %w", rootDrive, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer windows.Close(f)
|
defer windows.Close(volumeHandle)
|
||||||
|
|
||||||
controlCode := uint32(5636096) // IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
|
controlCode := uint32(5636096) // IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS
|
||||||
volumeDiskExtents := make([]byte, 16*1024)
|
volumeDiskExtents := make([]byte, 16*1024)
|
||||||
|
|
||||||
var bytesReturned uint32
|
var bytesReturned uint32
|
||||||
|
|
||||||
err = windows.DeviceIoControl(f, controlCode, nil, 0, &volumeDiskExtents[0], uint32(len(volumeDiskExtents)), &bytesReturned, nil)
|
err = windows.DeviceIoControl(volumeHandle, controlCode, nil, 0, &volumeDiskExtents[0], uint32(len(volumeDiskExtents)), &bytesReturned, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("could not identify physical drive for %s: %w", rootDrive, err)
|
return volumeInfo{}, fmt.Errorf("could not identify physical drive for %s: %w", rootDrive, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
numDiskIDs := uint(binary.LittleEndian.Uint32(volumeDiskExtents))
|
numDiskIDs := uint(binary.LittleEndian.Uint32(volumeDiskExtents))
|
||||||
if numDiskIDs < 1 {
|
if numDiskIDs < 1 {
|
||||||
return "", fmt.Errorf("could not identify physical drive for %s: no disk IDs returned", rootDrive)
|
return volumeInfo{}, fmt.Errorf("could not identify physical drive for %s: no disk IDs returned", rootDrive)
|
||||||
}
|
}
|
||||||
|
|
||||||
diskIDs := make([]string, numDiskIDs)
|
diskIDs := make([]string, numDiskIDs)
|
||||||
@@ -515,34 +528,35 @@ func getDiskIDByVolume(rootDrive string) (string, error) {
|
|||||||
slices.Sort(diskIDs)
|
slices.Sort(diskIDs)
|
||||||
diskIDs = slices.Compact(diskIDs)
|
diskIDs = slices.Compact(diskIDs)
|
||||||
|
|
||||||
return strings.Join(diskIDs, ";"), nil
|
volumeInformationRootDrive := volumePath + `\`
|
||||||
}
|
|
||||||
|
|
||||||
func getVolumeInfo(rootDrive string) (volumeInfo, error) {
|
if strings.Contains(volumePath, `Volume`) {
|
||||||
if !strings.HasSuffix(rootDrive, ":") {
|
volumeInformationRootDrive = `\\?\` + volumeInformationRootDrive
|
||||||
return volumeInfo{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
volPath := windows.StringToUTF16Ptr(rootDrive + `\`)
|
volumeInformationRootDrivePtr := windows.StringToUTF16Ptr(volumeInformationRootDrive)
|
||||||
|
driveType := windows.GetDriveType(volumeInformationRootDrivePtr)
|
||||||
volBufLabel := make([]uint16, windows.MAX_PATH+1)
|
volBufLabel := make([]uint16, windows.MAX_PATH+1)
|
||||||
volSerialNum := uint32(0)
|
volSerialNum := uint32(0)
|
||||||
fsFlags := uint32(0)
|
fsFlags := uint32(0)
|
||||||
volBufType := make([]uint16, windows.MAX_PATH+1)
|
volBufType := make([]uint16, windows.MAX_PATH+1)
|
||||||
|
|
||||||
driveType := windows.GetDriveType(volPath)
|
err = windows.GetVolumeInformation(
|
||||||
|
volumeInformationRootDrivePtr,
|
||||||
err := windows.GetVolumeInformation(volPath, &volBufLabel[0], uint32(len(volBufLabel)),
|
&volBufLabel[0], uint32(len(volBufLabel)),
|
||||||
&volSerialNum, nil, &fsFlags, &volBufType[0], uint32(len(volBufType)))
|
&volSerialNum, nil, &fsFlags,
|
||||||
|
&volBufType[0], uint32(len(volBufType)),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if driveType != windows.DRIVE_CDROM && driveType != windows.DRIVE_REMOVABLE {
|
if driveType == windows.DRIVE_CDROM || driveType == windows.DRIVE_REMOVABLE {
|
||||||
return volumeInfo{}, err
|
return volumeInfo{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return volumeInfo{}, nil
|
return volumeInfo{}, fmt.Errorf("could not get volume information for %s: %w", volumeInformationRootDrive, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return volumeInfo{
|
return volumeInfo{
|
||||||
|
diskIDs: strings.Join(diskIDs, ";"),
|
||||||
volumeType: getDriveType(driveType),
|
volumeType: getDriveType(driveType),
|
||||||
label: windows.UTF16PtrToString(&volBufLabel[0]),
|
label: windows.UTF16PtrToString(&volBufLabel[0]),
|
||||||
filesystem: windows.UTF16PtrToString(&volBufType[0]),
|
filesystem: windows.UTF16PtrToString(&volBufType[0]),
|
||||||
|
|||||||
Reference in New Issue
Block a user