Skip to content

Commit

Permalink
add computecore apis
Browse files Browse the repository at this point in the history
Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
  • Loading branch information
helsaawy committed Apr 23, 2024
1 parent 99b4582 commit 470efa7
Show file tree
Hide file tree
Showing 31 changed files with 1,578 additions and 0 deletions.
63 changes: 63 additions & 0 deletions internal/computecore/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//go:build windows

package computecore

import "golang.org/x/sys/windows"

// Callback functions must be converted to a uintptr via [windows.NewCallback] before being
// passed to a syscall.
//
// Additionally, [windows.NewCallback] expects functions to return a uintptr result,
// so callbacks must be modified ahead of time.
//
// Create a dedicated type uintptr for each callback to ensure type safety.

type (
// Function type for the completion callback of an operation.
//
// typedef void (CALLBACK *HCS_OPERATION_COMPLETION)(
// _In_ HCS_OPERATION operation,
// _In_opt_ void* context
// );
HCSOperationCompletion func(op HCSOperation, hcsCtx HCSContext)

hcsOperationCompletionUintptr uintptr
)

func (f HCSOperationCompletion) asCallback() hcsOperationCompletionUintptr {
if f == nil {
return hcsOperationCompletionUintptr(0)
}
return hcsOperationCompletionUintptr(windows.NewCallback(
func(op HCSOperation, hcsCtx HCSContext) uintptr {
f(op, hcsCtx)
return 0
},
))
}

type (
// Function type for compute system event callbacks.
//
// typedef void (CALLBACK *HCS_EVENT_CALLBACK)(
// _In_ HCS_EVENT* event,
// _In_opt_ void* context
// );
HCSEventCallback func(event *HCSEvent, hcsCx HCSContext)

hcsEventCallbackUintptr uintptr
)

func (f HCSEventCallback) asCallback() hcsEventCallbackUintptr {
if f == nil {
return hcsEventCallbackUintptr(0)
}

return hcsEventCallbackUintptr(windows.NewCallback(
// NewCallback expects a function with one uintptr-sized result
func(event *HCSEvent, hcsCtx HCSContext) uintptr {
f(event, hcsCtx)
return 0
},
))
}
175 changes: 175 additions & 0 deletions internal/computecore/computecore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//go:build windows

package computecore

import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"

"go.opencensus.io/trace"
"golang.org/x/sys/windows"

hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/oc"
"github.com/Microsoft/hcsshim/internal/timeout"
)

// TODO: HcsSetComputeSystemCallback & HcsSetProcessCallback

type (
// Handle to a compute system.
HCSSystem windows.Handle

// Handle to a process running in a compute system.
HCSProcess windows.Handle
)

// HCSContext corresponds to a `void* context` parameter that allows for an arbitrary payload
// containing compute system-, process-, or operation-specific data to be passed to callbacks.
//
// It is not compatible with [context.Context].
type HCSContext uintptr

//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go

// HRESULT WINAPI
// HcsEnumerateComputeSystems(
// _In_opt_ PCWSTR query,
// _In_ HCS_OPERATION operation
// );
//
//sys hcsEnumerateComputeSystems(query string, operation HCSOperation) (hr error) = computecore.HcsEnumerateComputeSystems?

func EnumerateComputeSystems(ctx context.Context, op HCSOperation, query *hcsschema.SystemQuery) (properties []hcsschema.Properties, err error) {
ctx, cancel := context.WithTimeout(ctx, timeout.SyscallWatcher)
defer cancel()

ctx, span := oc.StartSpan(ctx, "computecore::HcsEnumerateComputeSystems", oc.WithClientSpanKind)
defer func() {
if len(properties) != 0 {
span.AddAttributes(trace.StringAttribute("properties", log.Format(ctx, properties)))
}
oc.SetSpanStatus(span, err)
span.End()
}()

q := ""
if query != nil {
q, err = encode(query)
if err != nil {
return nil, err
}
span.AddAttributes(trace.StringAttribute("query", q))
}

return runOperation[[]hcsschema.Properties](
ctx,
op,
func(_ context.Context, op HCSOperation) (err error) {
return hcsEnumerateComputeSystems(q, op)
},
)
}

// HRESULT WINAPI
// HcsSetComputeSystemCallback(
// _In_ HCS_SYSTEM computeSystem,
// _In_ HCS_EVENT_OPTIONS callbackOptions,
// _In_opt_ const void* context,
// _In_ HCS_EVENT_CALLBACK callback
// );
//
//sys hcsSetComputeSystemCallback(computeSystem HCSSystem, callbackOptions HCSEventOptions, context HCSContext, callback hcsEventCallbackUintptr) (hr error) = computecore.HcsSetComputeSystemCallback?

// SetCallback assigns a callback to handle events for the compute system.
func (s HCSSystem) SetCallback(ctx context.Context, options HCSEventOptions, hcsCtx HCSContext, callback HCSEventCallback) (err error) {
_, span := oc.StartSpan(ctx, "computecore::HcsSetComputeSystemCallback", oc.WithClientSpanKind)
defer func() {
oc.SetSpanStatus(span, err)
span.End()
}()
ptr := callback.asCallback()
span.AddAttributes(
trace.StringAttribute("options", options.String()),
trace.Int64Attribute("handle", int64(s)),
trace.Int64Attribute("context", int64(hcsCtx)),
trace.Int64Attribute("callback", int64(ptr)),
)

return hcsSetComputeSystemCallback(s, options, hcsCtx, ptr)
}

// HRESULT WINAPI
// HcsSetProcessCallback(
// _In_ HCS_PROCESS process,
// _In_ HCS_EVENT_OPTIONS callbackOptions,
// _In_ void* context,
// _In_ HCS_EVENT_CALLBACK callback
// );
//
//sys hcsSetProcessCallback(process HCSProcess, callbackOptions HCSEventOptions, context HCSContext, callback hcsEventCallbackUintptr) (hr error) = computecore.HcsSetProcessCallback?

// SetCallback assigns a callback to handle events for the compute system.
func (p HCSProcess) SetCallback(ctx context.Context, options HCSEventOptions, hcsCtx HCSContext, callback HCSEventCallback) (err error) {
_, span := oc.StartSpan(ctx, "computecore::HcsSetProcessCallback", oc.WithClientSpanKind)
defer func() {
oc.SetSpanStatus(span, err)
span.End()
}()
ptr := callback.asCallback()
span.AddAttributes(
trace.StringAttribute("options", options.String()),
trace.Int64Attribute("handle", int64(p)),
trace.Int64Attribute("context", int64(hcsCtx)),
trace.Int64Attribute("callback", int64(ptr)),
)

return hcsSetProcessCallback(p, options, hcsCtx, ptr)
}

func runOperation[T any](
ctx context.Context,
op HCSOperation,
f func(context.Context, HCSOperation) error,
) (v T, err error) {
if err := f(ctx, op); err != nil {
return v, err
}

result, err := op.WaitForOperationResult(ctx)
if err != nil {
// WaitForOperationResult should (attempt to) parse result as [hcsschema.ResultError]
// so just return results
return v, err
}
err = json.Unmarshal([]byte(result), &v)
return v, err
}

// bufferToString converts the `PWSTR` buffer to a string, and then frees the original buffer.
func bufferToString(buffer *uint16) string {
if buffer == nil {
return ""
}
return interop.ConvertAndFreeCoTaskMemString(buffer)
}

func encode(v any) (string, error) {
// TODO: pool of encoders/buffers
buf := &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
enc.SetIndent("", "")

if err := enc.Encode(v); err != nil {
return "", fmt.Errorf("json encoding: %w", err)
}

// encoder.Encode appends a newline to the end
return strings.TrimSpace(buf.String()), nil
}
19 changes: 19 additions & 0 deletions internal/computecore/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This package provides bindings and utility functions for dealing with ComputeCore.dll Win32 APIs.
//
// The HCS APIs allow using the operation as a future (and waiting or polling via
// [HcsWaitForOperationResult] or [HcsGetOperationResult], respectively), or setting an operation
// callback.
// However:
//
// 1. Futures do not match Go's async model (which instead relies on go routines).
// 2. An operation callback will error if the compute system has an event callback, which
// would prevent users from relying on the latter to be notified of compute systems events.
//
// For that reason, the APIs are called synchronously, and can be used asynchronously using a goroutine.
//
// See HCS [operation samples] for more information.
//
// [HcsWaitForOperationResult]: https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcswaitforoperationresult
// [HcsGetOperationResult]: https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsgetoperationresult
// [operation samples]: https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/operationcompletionsample
package computecore
63 changes: 63 additions & 0 deletions internal/computecore/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//go:build windows

package computecore

// typedef struct HCS_EVENT
// {
// HCS_EVENT_TYPE Type;
// PCWSTR EventData;
// HCS_OPERATION Operation;
// } HCS_EVENT;
//
// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcs_event
type HCSEvent struct {
Type HCSEventType
// eventData provides additional data for the event as a JSON document.
//
// the type of JSON document depends on the event type:
//
// HcsEventSystemExited github.com/Microsoft/hcsshim/internal/hcs/schema2.SystemExitStatus
// HcsEventSystemCrashInitiated github.com/Microsoft/hcsshim/internal/hcs/schema2.CrashReport
// HcsEventSystemCrashReport github.com/Microsoft/hcsshim/internal/hcs/schema2.CrashReport
// HcsEventProcessExited github.com/Microsoft/hcsshim/internal/hcs/schema2.ProcessStatus
EventData *uint16
// Handle to a completed operation, if Type is eventOperationCallback.
// This is only possible when HcsSetComputeSystemCallback has specified event option HcsEventOptionEnableOperationCallbacks.
Operation HCSOperation
}

//go:generate go run golang.org/x/tools/cmd/stringer -type=HCSEventType -trimprefix=Event event.go

// HCSEventType indicates the event type for callbacks registered by hcsSetComputeSystemCallback or hcsSetProcessCallback.
//
// See [documentation] for more info.
//
// [documentation]: https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcs_event_type
type HCSEventType int32

const (
EventInvalid = HCSEventType(0x00000000)
EventSystemExited = HCSEventType(0x00000001)
EventSystemCrashInitiated = HCSEventType(0x00000002)
EventSystemCrashReport = HCSEventType(0x00000003)
EventSystemRdpEnhancedModeStateChanged = HCSEventType(0x00000004)
EventSystemSiloJobCreated = HCSEventType(0x00000005)
EventSystemGuestConnectionClosed = HCSEventType(0x00000006)
EventProcessExited = HCSEventType(0x00010000)
EventOperationCallback = HCSEventType(0x01000000)
EventServiceDisconnect = HCSEventType(0x02000000)
)

//go:generate go run golang.org/x/tools/cmd/stringer -type=HCSEventOptions -trimprefix=EventOption event.go

// HCSEventOptions defines the options for an event callback registration, used in HcsSetComputeSystemCallback and HcsSetProcessCallback.
//
// See [documentation] for more info.
//
// [documentation]: https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcs_event_options
type HCSEventOptions int32

const (
EventOptionNone = HCSEventOptions(0x00000000)
EventOptionEnableOperationCallbacks = HCSEventOptions(0x00000001)
)
24 changes: 24 additions & 0 deletions internal/computecore/hcseventoptions_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions internal/computecore/hcseventtype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 470efa7

Please sign in to comment.