From f8cf2f6146c3790f3459a981c8216991291bea45 Mon Sep 17 00:00:00 2001 From: Zhongcheng Lao Date: Sun, 16 Mar 2025 20:56:21 +0800 Subject: [PATCH] Use WMI to implement Disk API to reduce PowerShell overhead --- .github/workflows/windows.yml | 6 +- docs/IMPLEMENTATION.md | 11 + pkg/cim/disk.go | 127 +++++++++++ pkg/disk/hostapi/hostapi.go | 419 +++++++++++++++++++--------------- 4 files changed, 383 insertions(+), 180 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 939555b2..98b754f2 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -22,7 +22,11 @@ jobs: Install-WindowsFeature -name Hyper-V-PowerShell $env:CSI_PROXY_GH_ACTIONS="TRUE" - go test --timeout 20m -v ./integrationtests/... + # Note: checkptr is enabled by default with `-race` specified. + # Since Windows COM interop is called in integration tests and + # dereferencing pointer allocated by COM is not safe for Go, + # checkptr needs to be disabled for these tests to pass. + go test --timeout 20m -v -race -gcflags=all=-d=checkptr=0 ./integrationtests/... unit_tests: strategy: matrix: diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index e14f85f0..4c23a94a 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -135,6 +135,17 @@ can be used to retrieve a volume (`MSFT_Volume`) from a partition (`MSFT_Partiti collection, err := part.GetAssociated("MSFT_PartitionToVolume", "MSFT_Volume", "Volume", "Partition") ``` +### Disable checkptr + +COM APIs (like IDispatch.Invoke, Variant, BSTR, IUnknown**, etc.) frequently use: +* Double pointers (e.g., BSTR*, IUnknown**) +* Pointers passed as int64 or uintptr +* In-place modification via reference + +These patterns are common and valid in C/C++, but violate Go’s unsafe.Pointer rules when interpreted literally - even if they are functionally correct. + +Therefore, you should make sure `-gcflags=all=-d=checkptr=0` when calling `microsoft/wmi`. + ## Debug with PowerShell diff --git a/pkg/cim/disk.go b/pkg/cim/disk.go index a41241df..58c8f376 100644 --- a/pkg/cim/disk.go +++ b/pkg/cim/disk.go @@ -28,6 +28,14 @@ const ( ErrorCodeCreatePartitionAccessPathAlreadyInUse = 42002 ) +var ( + DiskSelectorListForDiskNumberAndLocation = []string{"Number", "Location"} + DiskSelectorListForPartitionStyle = []string{"PartitionStyle"} + DiskSelectorListForPathAndSerialNumber = []string{"Path", "SerialNumber"} + DiskSelectorListForIsOffline = []string{"IsOffline"} + DiskSelectorListForSize = []string{"Size"} +) + // QueryDiskByNumber retrieves disk information for a specific disk identified by its number. // // The equivalent WMI query is: @@ -52,6 +60,43 @@ func QueryDiskByNumber(diskNumber uint32, selectorList []string) (*storage.MSFT_ return disk, nil } +// ListDisks retrieves information about all available disks. +// +// The equivalent WMI query is: +// +// SELECT [selectors] FROM MSFT_Disk +// +// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk +// for the WMI class definition. +func ListDisks(selectorList []string) ([]*storage.MSFT_Disk, error) { + diskQuery := query.NewWmiQueryWithSelectList("MSFT_Disk", selectorList) + instances, err := QueryInstances(WMINamespaceStorage, diskQuery) + if IgnoreNotFound(err) != nil { + return nil, err + } + + var disks []*storage.MSFT_Disk + for _, instance := range instances { + disk, err := storage.NewMSFT_DiskEx1(instance) + if err != nil { + return nil, fmt.Errorf("failed to query disk %v. error: %v", instance, err) + } + + disks = append(disks, disk) + } + + return disks, nil +} + +// InitializeDisk initializes a RAW disk with a particular partition style. +// +// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/initialize-msft-disk +// for the WMI method definition. +func InitializeDisk(disk *storage.MSFT_Disk, partitionStyle int) (int, error) { + result, err := disk.InvokeMethodWithReturn("Initialize", int32(partitionStyle)) + return int(result), err +} + // RefreshDisk Refreshes the cached disk layout information. // // Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk-refresh @@ -61,3 +106,85 @@ func RefreshDisk(disk *storage.MSFT_Disk) (int, string, error) { result, err := disk.InvokeMethodWithReturn("Refresh", &status) return int(result), status, err } + +// CreatePartition creates a partition on a disk. +// +// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk +// for the WMI method definition. +func CreatePartition(disk *storage.MSFT_Disk, params ...interface{}) (int, error) { + result, err := disk.InvokeMethodWithReturn("CreatePartition", params...) + return int(result), err +} + +// SetDiskState takes a disk online or offline. +// +// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk-online and +// https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk-offline +// for the WMI method definition. +func SetDiskState(disk *storage.MSFT_Disk, online bool) (int, string, error) { + method := "Offline" + if online { + method = "Online" + } + + var status string + result, err := disk.InvokeMethodWithReturn(method, &status) + return int(result), status, err +} + +// RescanDisks rescans all changes by updating the internal cache of software objects (that is, Disks, Partitions, Volumes) +// for the storage setting. +// +// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-storagesetting-updatehoststoragecache +// for the WMI method definition. +func RescanDisks() (int, error) { + result, _, err := InvokeCimMethod(WMINamespaceStorage, "MSFT_StorageSetting", "UpdateHostStorageCache", nil) + return result, err +} + +// GetDiskNumber returns the number of a disk. +func GetDiskNumber(disk *storage.MSFT_Disk) (uint32, error) { + number, err := disk.GetProperty("Number") + if err != nil { + return 0, err + } + return uint32(number.(int32)), err +} + +// GetDiskLocation returns the location of a disk. +func GetDiskLocation(disk *storage.MSFT_Disk) (string, error) { + return disk.GetPropertyLocation() +} + +// GetDiskPartitionStyle returns the partition style of a disk. +func GetDiskPartitionStyle(disk *storage.MSFT_Disk) (int32, error) { + retValue, err := disk.GetProperty("PartitionStyle") + if err != nil { + return 0, err + } + return retValue.(int32), err +} + +// IsDiskOffline returns whether a disk is offline. +func IsDiskOffline(disk *storage.MSFT_Disk) (bool, error) { + return disk.GetPropertyIsOffline() +} + +// GetDiskSize returns the size of a disk. +func GetDiskSize(disk *storage.MSFT_Disk) (int64, error) { + sz, err := disk.GetProperty("Size") + if err != nil { + return -1, err + } + return strconv.ParseInt(sz.(string), 10, 64) +} + +// GetDiskPath returns the path of a disk. +func GetDiskPath(disk *storage.MSFT_Disk) (string, error) { + return disk.GetPropertyPath() +} + +// GetDiskSerialNumber returns the serial number of a disk. +func GetDiskSerialNumber(disk *storage.MSFT_Disk) (string, error) { + return disk.GetPropertySerialNumber() +} diff --git a/pkg/disk/hostapi/hostapi.go b/pkg/disk/hostapi/hostapi.go index 28d46f80..7046b2aa 100644 --- a/pkg/disk/hostapi/hostapi.go +++ b/pkg/disk/hostapi/hostapi.go @@ -2,15 +2,12 @@ package api import ( "encoding/hex" - "encoding/json" "fmt" - "regexp" - "strconv" "strings" "syscall" "unsafe" - "github.com/kubernetes-csi/csi-proxy/v2/pkg/utils" + "github.com/kubernetes-csi/csi-proxy/v2/pkg/cim" "k8s.io/klog/v2" ) @@ -63,113 +60,166 @@ func New() DiskAPI { // ListDiskLocations - constructs a map with the disk number as the key and the DiskLocation structure // as the value. The DiskLocation struct has various fields like the Adapter, Bus, Target and LUNID. -func (DiskAPI) ListDiskLocations() (map[uint32]DiskLocation, error) { - // sample response - // [{ - // "number": 0, - // "location": "PCI Slot 3 : Adapter 0 : Port 0 : Target 1 : LUN 0" - // }, ...] - cmd := "ConvertTo-Json @(Get-Disk | select Number, Location)" - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return nil, fmt.Errorf("failed to list disk location. cmd: %q, output: %q, err %v", cmd, string(out), err) - } +func (imp DiskAPI) ListDiskLocations() (map[uint32]DiskLocation, error) { + m := make(map[uint32]DiskLocation) + err := cim.WithCOMThread(func() error { + // "location": "PCI Slot 3 : Adapter 0 : Port 0 : Target 1 : LUN 0" + disks, err := cim.ListDisks(cim.DiskSelectorListForDiskNumberAndLocation) + if err != nil { + return fmt.Errorf("could not query disk locations") + } - var getDisk []map[string]interface{} - err = json.Unmarshal(out, &getDisk) - if err != nil { - return nil, err - } + for _, disk := range disks { + num, err := cim.GetDiskNumber(disk) + if err != nil { + return fmt.Errorf("failed to query disk number: %v, %w", disk, err) + } - m := make(map[uint32]DiskLocation) - for _, v := range getDisk { - str := v["Location"].(string) - num := v["Number"].(float64) - - found := false - s := strings.Split(str, ":") - if len(s) >= 5 { - var d DiskLocation - for _, item := range s { - item = strings.TrimSpace(item) - itemSplit := strings.Split(item, " ") - if len(itemSplit) == 2 { - found = true - switch strings.TrimSpace(itemSplit[0]) { - case "Adapter": - d.Adapter = strings.TrimSpace(itemSplit[1]) - case "Target": - d.Target = strings.TrimSpace(itemSplit[1]) - case "LUN": - d.LUNID = strings.TrimSpace(itemSplit[1]) - default: - klog.Warningf("Got unknown field : %s=%s", itemSplit[0], itemSplit[1]) + location, err := cim.GetDiskLocation(disk) + if err != nil { + return fmt.Errorf("failed to query disk location: %v, %w", disk, err) + } + + found := false + s := strings.Split(location, ":") + if len(s) >= 5 { + var d DiskLocation + for _, item := range s { + item = strings.TrimSpace(item) + itemSplit := strings.Split(item, " ") + if len(itemSplit) == 2 { + found = true + switch strings.TrimSpace(itemSplit[0]) { + case "Adapter": + d.Adapter = strings.TrimSpace(itemSplit[1]) + case "Target": + d.Target = strings.TrimSpace(itemSplit[1]) + case "LUN": + d.LUNID = strings.TrimSpace(itemSplit[1]) + default: + klog.Warningf("Got unknown field : %s=%s", itemSplit[0], itemSplit[1]) + } } } - } - if found { - m[uint32(num)] = d + if found { + m[num] = d + } } } - } - return m, nil + + return nil + }) + return m, err } -func (DiskAPI) Rescan() error { - cmd := "Update-HostStorageCache" - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return fmt.Errorf("error updating host storage cache output: %q, err: %v", string(out), err) - } - return nil +func (imp DiskAPI) Rescan() error { + return cim.WithCOMThread(func() error { + result, err := cim.RescanDisks() + if err != nil { + return fmt.Errorf("error updating host storage cache output. result: %d, err: %v", result, err) + } + return nil + }) } -func (DiskAPI) IsDiskInitialized(diskNumber uint32) (bool, error) { - cmd := fmt.Sprintf("Get-Disk -Number %d | Where partitionstyle -eq 'raw'", diskNumber) - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return false, fmt.Errorf("error checking initialized status of disk %d: %v, %v", diskNumber, out, err) - } - if len(out) == 0 { - // disks with raw initialization not detected - return true, nil - } - return false, nil +func (imp DiskAPI) IsDiskInitialized(diskNumber uint32) (bool, error) { + var partitionStyle int32 + err := cim.WithCOMThread(func() error { + disk, err := cim.QueryDiskByNumber(diskNumber, cim.DiskSelectorListForPartitionStyle) + if err != nil { + return fmt.Errorf("error checking initialized status of disk %d: %v", diskNumber, err) + } + + partitionStyle, err = cim.GetDiskPartitionStyle(disk) + if err != nil { + return fmt.Errorf("failed to query partition style of disk %d: %v", diskNumber, err) + } + + return nil + }) + return partitionStyle != cim.PartitionStyleUnknown, err } -func (DiskAPI) InitializeDisk(diskNumber uint32) error { - cmd := fmt.Sprintf("Initialize-Disk -Number %d -PartitionStyle GPT", diskNumber) - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return fmt.Errorf("error initializing disk %d: %v, %v", diskNumber, string(out), err) - } - return nil +func (imp DiskAPI) InitializeDisk(diskNumber uint32) error { + return cim.WithCOMThread(func() error { + disk, err := cim.QueryDiskByNumber(diskNumber, nil) + if err != nil { + return fmt.Errorf("failed to initializing disk %d. error: %w", diskNumber, err) + } + + result, err := cim.InitializeDisk(disk, cim.PartitionStyleGPT) + if result != 0 || err != nil { + return fmt.Errorf("failed to initializing disk %d: result %d, error: %w", diskNumber, result, err) + } + + return nil + }) } -func (DiskAPI) BasicPartitionsExist(diskNumber uint32) (bool, error) { - cmd := fmt.Sprintf("Get-Partition | Where DiskNumber -eq %d | Where Type -ne Reserved", diskNumber) - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return false, fmt.Errorf("error checking presence of partitions on disk %d: %v, %v", diskNumber, out, err) - } - if len(out) > 0 { - // disk has partitions in it - return true, nil - } - return false, nil +func (imp DiskAPI) BasicPartitionsExist(diskNumber uint32) (bool, error) { + var exist bool + err := cim.WithCOMThread(func() error { + partitions, err := cim.ListPartitionsWithFilters(nil, cim.FilterForPartitionOnDisk(diskNumber), cim.FilterForPartitionsOfTypeNormal()) + if cim.IgnoreNotFound(err) != nil { + return fmt.Errorf("error checking presence of partitions on disk %d:, %v", diskNumber, err) + } + + exist = len(partitions) > 0 + return nil + }) + return exist, err } -func (DiskAPI) CreateBasicPartition(diskNumber uint32) error { - cmd := fmt.Sprintf("New-Partition -DiskNumber %d -UseMaximumSize", diskNumber) - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return fmt.Errorf("error creating partition on disk %d: %v, %v", diskNumber, out, err) - } - return nil +func (imp DiskAPI) CreateBasicPartition(diskNumber uint32) error { + return cim.WithCOMThread(func() error { + disk, err := cim.QueryDiskByNumber(diskNumber, nil) + if err != nil { + return err + } + + result, err := cim.CreatePartition( + disk, + nil, // Size + true, // UseMaximumSize + nil, // Offset + nil, // Alignment + nil, // DriveLetter + false, // AssignDriveLetter + nil, // MbrType, + cim.GPTPartitionTypeBasicData, // GPT Type + false, // IsHidden + false, // IsActive, + ) + if (result != 0 && result != cim.ErrorCodeCreatePartitionAccessPathAlreadyInUse) || err != nil { + return fmt.Errorf("error creating partition on disk %d. result: %d, err: %v", diskNumber, result, err) + } + + result, _, err = cim.RefreshDisk(disk) + if result != 0 || err != nil { + return fmt.Errorf("error rescan disk (%d). result %d, error: %v", diskNumber, result, err) + } + + partitions, err := cim.ListPartitionsWithFilters(nil, cim.FilterForPartitionOnDisk(diskNumber), cim.FilterForPartitionsOfTypeNormal()) + if err != nil { + return fmt.Errorf("error query basic partition on disk %d:, %v", diskNumber, err) + } + + if len(partitions) == 0 { + return fmt.Errorf("failed to create basic partition on disk %d:, %v", diskNumber, err) + } + + partition := partitions[0] + result, status, err := cim.SetPartitionState(partition, true) + if result != 0 || err != nil { + return fmt.Errorf("error bring partition %v on disk %d online. result: %d, status %s, err: %v", partition, diskNumber, result, status, err) + } + + return nil + }) } -func (DiskAPI) GetDiskNumber(disk syscall.Handle) (uint32, error) { +func (imp DiskAPI) GetDiskNumber(disk syscall.Handle) (uint32, error) { var bytes uint32 devNum := StorageDeviceNumber{} buflen := uint32(unsafe.Sizeof(devNum.DeviceType)) + uint32(unsafe.Sizeof(devNum.DeviceNumber)) + uint32(unsafe.Sizeof(devNum.PartitionNumber)) @@ -179,7 +229,7 @@ func (DiskAPI) GetDiskNumber(disk syscall.Handle) (uint32, error) { return devNum.DeviceNumber, err } -func (DiskAPI) GetDiskPage83ID(disk syscall.Handle) (string, error) { +func (imp DiskAPI) GetDiskPage83ID(disk syscall.Handle) (string, error) { query := StoragePropertyQuery{} bufferSize := uint32(4 * 1024) @@ -221,31 +271,33 @@ func (DiskAPI) GetDiskPage83ID(disk syscall.Handle) (string, error) { } func (imp DiskAPI) GetDiskNumberWithID(page83ID string) (uint32, error) { - cmd := "ConvertTo-Json @(Get-Disk | Select Path)" - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return 0, fmt.Errorf("Could not query disk paths") - } - - outString := string(out) - disks := []Disk{} - err = json.Unmarshal([]byte(outString), &disks) - if err != nil { - return 0, err - } - - for i := range disks { - diskNumber, diskPage83ID, err := imp.GetDiskNumberAndPage83ID(disks[i].Path) + var diskNumberResult uint32 + err := cim.WithCOMThread(func() error { + disks, err := cim.ListDisks(cim.DiskSelectorListForPathAndSerialNumber) if err != nil { - return 0, err + return err } - if diskPage83ID == page83ID { - return diskNumber, nil + for _, disk := range disks { + path, err := cim.GetDiskPath(disk) + if err != nil { + return fmt.Errorf("failed to query disk path: %v, %w", disk, err) + } + + diskNumber, diskPage83ID, err := imp.GetDiskNumberAndPage83ID(path) + if err != nil { + return err + } + + if diskPage83ID == page83ID { + diskNumberResult = diskNumber + return nil + } } - } - return 0, fmt.Errorf("Could not find disk with Page83 ID %s", page83ID) + return fmt.Errorf("could not find disk with Page83 ID %s", page83ID) + }) + return diskNumberResult, err } func (imp DiskAPI) GetDiskNumberAndPage83ID(path string) (uint32, string, error) { @@ -271,90 +323,99 @@ func (imp DiskAPI) GetDiskNumberAndPage83ID(path string) (uint32, string, error) // ListDiskIDs - constructs a map with the disk number as the key and the DiskID structure // as the value. The DiskID struct has a field for the page83 ID. func (imp DiskAPI) ListDiskIDs() (map[uint32]DiskIDs, error) { - // sample response - // [ - // { - // "Path": "\\\\?\\scsi#disk\u0026ven_google\u0026prod_persistentdisk#4\u002621cb0360\u00260\u0026000100#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}", - // "SerialNumber": " " - // }, - // { - // "Path": "\\\\?\\scsi#disk\u0026ven_msft\u0026prod_virtual_disk#2\u00261f4adffe\u00260\u0026000001#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}", - // "SerialNumber": null - // }, ] - cmd := "ConvertTo-Json @(Get-Disk | Select Path, SerialNumber)" - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return nil, fmt.Errorf("Could not query disk paths: %v", err) - } - - outString := string(out) - disks := []Disk{} - err = json.Unmarshal([]byte(outString), &disks) - if err != nil { - return nil, err - } - m := make(map[uint32]DiskIDs) - - for i := range disks { - diskNumber, page83, err := imp.GetDiskNumberAndPage83ID(disks[i].Path) + err := cim.WithCOMThread(func() error { + disks, err := cim.ListDisks(cim.DiskSelectorListForPathAndSerialNumber) if err != nil { - return nil, err + return err } - m[diskNumber] = DiskIDs{ - Page83: page83, - SerialNumber: disks[i].SerialNumber, + for _, disk := range disks { + path, err := cim.GetDiskPath(disk) + if err != nil { + return fmt.Errorf("failed to query disk path: %v, %w", disk, err) + } + + sn, err := cim.GetDiskSerialNumber(disk) + if err != nil { + return fmt.Errorf("failed to query disk serial number: %v, %w", disk, err) + } + + diskNumber, page83, err := imp.GetDiskNumberAndPage83ID(path) + if err != nil { + return err + } + + m[diskNumber] = DiskIDs{ + Page83: page83, + SerialNumber: sn, + } } - } - return m, nil + return nil + }) + return m, err } func (imp DiskAPI) GetDiskStats(diskNumber uint32) (int64, error) { - cmd := fmt.Sprintf("(Get-Disk -Number %d).Size", diskNumber) - out, err := utils.RunPowershellCmd(cmd) - if err != nil || len(out) == 0 { - return -1, fmt.Errorf("error getting size of disk. cmd: %s, output: %s, error: %v", cmd, string(out), err) - } - - reg, err := regexp.Compile("[^0-9]+") - if err != nil { - return -1, fmt.Errorf("error compiling regex. err: %v", err) - } - diskSizeOutput := reg.ReplaceAllString(string(out), "") - - diskSize, err := strconv.ParseInt(diskSizeOutput, 10, 64) + // TODO: change to uint64 as it does not make sense to use int64 for size + size := int64(-1) + err := cim.WithCOMThread(func() error { + disk, err := cim.QueryDiskByNumber(diskNumber, cim.DiskSelectorListForSize) + if err != nil { + return err + } - if err != nil { - return -1, fmt.Errorf("error parsing size of disk. cmd: %s, output: %s, error: %v", cmd, diskSizeOutput, err) - } + size, err = cim.GetDiskSize(disk) + if err != nil { + return fmt.Errorf("failed to query size of disk %d. %v", diskNumber, err) + } - return diskSize, nil + return nil + }) + return size, err } func (imp DiskAPI) SetDiskState(diskNumber uint32, isOnline bool) error { - cmd := fmt.Sprintf("(Get-Disk -Number %d) | Set-Disk -IsOffline $%t", diskNumber, !isOnline) - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return fmt.Errorf("error setting disk attach state. cmd: %s, output: %s, error: %v", cmd, string(out), err) - } + return cim.WithCOMThread(func() error { + disk, err := cim.QueryDiskByNumber(diskNumber, cim.DiskSelectorListForIsOffline) + if err != nil { + return err + } + + isOffline, err := cim.IsDiskOffline(disk) + if err != nil { + return fmt.Errorf("error setting disk %d attach state. error: %v", diskNumber, err) + } + + if isOnline == !isOffline { + klog.V(2).Infof("Disk %d is already in the desired state", diskNumber) + return nil + } - return nil + result, _, err := cim.SetDiskState(disk, isOnline) + if result != 0 || err != nil { + return fmt.Errorf("setting disk %d attach state (isOnline: %v): result %d, error: %w", diskNumber, isOnline, result, err) + } + + return nil + }) } func (imp DiskAPI) GetDiskState(diskNumber uint32) (bool, error) { - cmd := fmt.Sprintf("(Get-Disk -Number %d) | Select-Object -ExpandProperty IsOffline", diskNumber) - out, err := utils.RunPowershellCmd(cmd) - if err != nil { - return false, fmt.Errorf("error getting disk state. cmd: %s, output: %s, error: %v", cmd, string(out), err) - } + var isOffline bool + err := cim.WithCOMThread(func() error { + disk, err := cim.QueryDiskByNumber(diskNumber, cim.DiskSelectorListForIsOffline) + if err != nil { + return err + } - sout := strings.TrimSpace(string(out)) - isOffline, err := strconv.ParseBool(sout) - if err != nil { - return false, fmt.Errorf("error parsing disk state. output: %s, error: %v", sout, err) - } + isOffline, err = cim.IsDiskOffline(disk) + if err != nil { + return fmt.Errorf("error parsing disk %d state. error: %v", diskNumber, err) + } - return !isOffline, nil + return nil + }) + return !isOffline, err }