From 44fd6ccc851567619a5651c7cc0ec38745b20c17 Mon Sep 17 00:00:00 2001 From: Dominik Eisenberg <64131471+Dominik-esb@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:47:06 +0200 Subject: [PATCH] mi: add support for uint16 array (UINT16A) properties (#2490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: EisenbergD Co-authored-by: Jan-Otto Kröpke Signed-off-by: EisenbergD Signed-off-by: Jan-Otto Kröpke --- internal/mi/instance.go | 13 +++- internal/mi/mi_test.go | 134 +++++++++++++++++++++++++++++++++++++++ internal/mi/operation.go | 11 ++++ internal/mi/session.go | 6 ++ internal/mi/value.go | 18 ++++++ 5 files changed, 179 insertions(+), 3 deletions(-) diff --git a/internal/mi/instance.go b/internal/mi/instance.go index 3018bd2a..a3e81a93 100644 --- a/internal/mi/instance.go +++ b/internal/mi/instance.go @@ -98,8 +98,14 @@ func (instance *Instance) GetElement(elementName string) (*Element, error) { return nil, fmt.Errorf("failed to convert element name %s to UTF-16: %w", elementName, err) } + // MI_Value is a union sized to its largest member. On 64-bit MI_Datetime is + // 36 bytes and the union rounds up to 40 bytes for 8-byte alignment, so a + // full MI_Value must be provided; the previous single-word buffer let the + // provider write past it and corrupt the stack for array or datetime + // elements. Word 0 holds the scalar/pointer/array-data; word 1 holds the + // array element count for array types. var ( - value uintptr + valueBuf [5]uint64 valueType ValueType ) @@ -107,7 +113,7 @@ func (instance *Instance) GetElement(elementName string) (*Element, error) { instance.ft.GetElement, uintptr(unsafe.Pointer(instance)), uintptr(unsafe.Pointer(elementNameUTF16)), - uintptr(unsafe.Pointer(&value)), + uintptr(unsafe.Pointer(&valueBuf)), uintptr(unsafe.Pointer(&valueType)), 0, 0, @@ -118,7 +124,8 @@ func (instance *Instance) GetElement(elementName string) (*Element, error) { } return &Element{ - value: value, + value: uintptr(valueBuf[0]), + arrayLen: uint32(valueBuf[1]), valueType: valueType, }, nil } diff --git a/internal/mi/mi_test.go b/internal/mi/mi_test.go index 0facd390..d33ebf55 100644 --- a/internal/mi/mi_test.go +++ b/internal/mi/mi_test.go @@ -31,6 +31,18 @@ type win32Process struct { Name string `mi:"Name"` } +// win32DiskDrive is used to exercise UINT16A (uint16[]) unmarshalling. +// Win32_DiskDrive.Capabilities is a reliably-populated uint16[] on any host. +type win32DiskDrive struct { + Capabilities []uint16 `mi:"Capabilities"` +} + +// win32DiskDriveWrongType maps the uint16[] Capabilities property onto an +// incompatible Go type to exercise the UINT16A type guard. +type win32DiskDriveWrongType struct { + Capabilities []string `mi:"Capabilities"` +} + func Test_MI_Application_Initialize(t *testing.T) { application, err := mi.ApplicationInitialize() require.NoError(t, err) @@ -332,3 +344,125 @@ func Test_MI_QueryTimeout(t *testing.T) { err = application.Close() require.NoError(t, err) } + +func Test_MI_Query_Uint16Array(t *testing.T) { + application, err := mi.ApplicationInitialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + session, err := application.NewSession(nil) + require.NoError(t, err) + require.NotEmpty(t, session) + + operation, err := session.QueryInstances( + mi.OperationFlagsStandardRTTI, + nil, + mi.NamespaceRootCIMv2, + mi.QueryDialectWQL, + "SELECT Capabilities FROM Win32_DiskDrive", + ) + require.NoError(t, err) + require.NotEmpty(t, operation) + + found := false + + for { + instance, moreResults, err := operation.GetInstance() + require.NoError(t, err) + + if instance == nil { + break + } + + element, err := instance.GetElement("Capabilities") + require.NoError(t, err) + require.NotEmpty(t, element) + + value, err := element.GetValue() + require.NoError(t, err) + + capabilities, ok := value.([]uint16) + require.True(t, ok, "Capabilities should unmarshal to []uint16") + + if len(capabilities) > 0 { + found = true + } + + if !moreResults { + break + } + } + + require.True(t, found, "expected at least one disk with a non-empty uint16[] Capabilities") + + err = operation.Close() + require.NoError(t, err) + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_QueryUnmarshal_Uint16Array(t *testing.T) { + application, err := mi.ApplicationInitialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + session, err := application.NewSession(nil) + require.NoError(t, err) + require.NotEmpty(t, session) + + query, err := mi.NewQuery("SELECT Capabilities FROM Win32_DiskDrive") + require.NoError(t, err) + + var disks []win32DiskDrive + + err = session.Query(&disks, mi.NamespaceRootCIMv2, query, -1) + require.NoError(t, err) + require.NotEmpty(t, disks) + + found := false + + for _, disk := range disks { + if len(disk.Capabilities) > 0 { + found = true + + break + } + } + + require.True(t, found, "expected at least one disk with a non-empty uint16[] Capabilities") + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_QueryUnmarshal_Uint16Array_WrongType(t *testing.T) { + application, err := mi.ApplicationInitialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + session, err := application.NewSession(nil) + require.NoError(t, err) + require.NotEmpty(t, session) + + query, err := mi.NewQuery("SELECT Capabilities FROM Win32_DiskDrive") + require.NoError(t, err) + + var disks []win32DiskDriveWrongType + + // Unmarshalling a uint16[] into a []string field must error, not panic. + err = session.Query(&disks, mi.NamespaceRootCIMv2, query, -1) + require.Error(t, err) + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} diff --git a/internal/mi/operation.go b/internal/mi/operation.go index 6f958ac8..f23bd9e2 100644 --- a/internal/mi/operation.go +++ b/internal/mi/operation.go @@ -106,6 +106,17 @@ func (o *Operation) Close() error { return ErrNotInitialized } + moreResults := true + + var err error + + for moreResults { + _, moreResults, err = o.GetInstance() + if err != nil { + break + } + } + r0, _, _ := syscall.SyscallN(o.ft.Close, uintptr(unsafe.Pointer(o))) if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { diff --git a/internal/mi/session.go b/internal/mi/session.go index 9588301d..7bb4f46a 100644 --- a/internal/mi/session.go +++ b/internal/mi/session.go @@ -293,6 +293,12 @@ func (s *Session) QueryUnmarshal(dst any, field.SetString(stringValue) case ValueTypeREAL32, ValueTypeREAL64: field.SetFloat(float64(element.value)) + case ValueTypeUINT16A: + if field.Type() != reflect.TypeFor[[]uint16]() { + return fmt.Errorf("cannot unmarshal UINT16A into field of type %s, expected []uint16", field.Type()) + } + + field.Set(reflect.ValueOf(element.getUint16Array())) default: return fmt.Errorf("unsupported value type: %d", element.valueType) } diff --git a/internal/mi/value.go b/internal/mi/value.go index 6960f73e..faa91d0f 100644 --- a/internal/mi/value.go +++ b/internal/mi/value.go @@ -66,6 +66,7 @@ const ( type Element struct { value uintptr + arrayLen uint32 valueType ValueType } @@ -122,7 +123,24 @@ func (e *Element) GetValue() (any, error) { } return strArray, nil + case ValueTypeUINT16A: + return e.getUint16Array(), nil default: return nil, fmt.Errorf("unsupported value type: %d", e.valueType) } } + +// getUint16Array reads a UINT16A element into a Go []uint16. The element's +// value holds the pointer to the MI_Uint16 array and arrayLen its length. +func (e *Element) getUint16Array() []uint16 { + if e.value == 0 || e.arrayLen == 0 { + return nil + } + + src := unsafe.Slice((*uint16)(unsafe.Pointer(e.value)), e.arrayLen) + + out := make([]uint16, e.arrayLen) + copy(out, src) + + return out +}