Signed-off-by: Justin Terry (VM) <juterry@microsoft.com>
| ... | ... |
@@ -1,6 +1,6 @@ |
| 1 | 1 |
github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 |
| 2 | 2 |
github.com/Microsoft/hcsshim 672e52e9209d1e53718c1b6a7d68cc9272654ab5 |
| 3 |
-github.com/Microsoft/go-winio 3fe4fa31662f6ede2353d913e93907b8e096e0b6 |
|
| 3 |
+github.com/Microsoft/go-winio 6c72808b55902eae4c5943626030429ff20f3b63 # v0.4.14 |
|
| 4 | 4 |
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a |
| 5 | 5 |
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git |
| 6 | 6 |
github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a |
| ... | ... |
@@ -111,7 +111,13 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
|
| 111 | 111 |
} |
| 112 | 112 |
|
| 113 | 113 |
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
| 114 |
- return makeWin32File(h) |
|
| 114 |
+ // If we return the result of makeWin32File directly, it can result in an |
|
| 115 |
+ // interface-wrapped nil, rather than a nil interface value. |
|
| 116 |
+ f, err := makeWin32File(h) |
|
| 117 |
+ if err != nil {
|
|
| 118 |
+ return nil, err |
|
| 119 |
+ } |
|
| 120 |
+ return f, nil |
|
| 115 | 121 |
} |
| 116 | 122 |
|
| 117 | 123 |
// closeHandle closes the resources associated with a Win32 handle |
| ... | ... |
@@ -271,6 +277,10 @@ func (f *win32File) Flush() error {
|
| 271 | 271 |
return syscall.FlushFileBuffers(f.handle) |
| 272 | 272 |
} |
| 273 | 273 |
|
| 274 |
+func (f *win32File) Fd() uintptr {
|
|
| 275 |
+ return uintptr(f.handle) |
|
| 276 |
+} |
|
| 277 |
+ |
|
| 274 | 278 |
func (d *deadlineHandler) set(deadline time.Time) error {
|
| 275 | 279 |
d.setLock.Lock() |
| 276 | 280 |
defer d.setLock.Unlock() |
| ... | ... |
@@ -46,7 +46,7 @@ func (addr *HvsockAddr) String() string {
|
| 46 | 46 |
func VsockServiceID(port uint32) guid.GUID {
|
| 47 | 47 |
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
|
| 48 | 48 |
g.Data1 = port |
| 49 |
- return *g |
|
| 49 |
+ return g |
|
| 50 | 50 |
} |
| 51 | 51 |
|
| 52 | 52 |
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
| ... | ... |
@@ -3,6 +3,7 @@ package etw |
| 3 | 3 |
import ( |
| 4 | 4 |
"bytes" |
| 5 | 5 |
"encoding/binary" |
| 6 |
+ "syscall" |
|
| 6 | 7 |
) |
| 7 | 8 |
|
| 8 | 9 |
// eventData maintains a buffer which builds up the data for an ETW event. It |
| ... | ... |
@@ -63,3 +64,8 @@ func (ed *eventData) writeUint32(value uint32) {
|
| 63 | 63 |
func (ed *eventData) writeUint64(value uint64) {
|
| 64 | 64 |
binary.Write(&ed.buffer, binary.LittleEndian, value) |
| 65 | 65 |
} |
| 66 |
+ |
|
| 67 |
+// writeFiletime appends a FILETIME to the buffer. |
|
| 68 |
+func (ed *eventData) writeFiletime(value syscall.Filetime) {
|
|
| 69 |
+ binary.Write(&ed.buffer, binary.LittleEndian, value) |
|
| 70 |
+} |
| ... | ... |
@@ -6,8 +6,8 @@ import ( |
| 6 | 6 |
|
| 7 | 7 |
type eventOptions struct {
|
| 8 | 8 |
descriptor *eventDescriptor |
| 9 |
- activityID *guid.GUID |
|
| 10 |
- relatedActivityID *guid.GUID |
|
| 9 |
+ activityID guid.GUID |
|
| 10 |
+ relatedActivityID guid.GUID |
|
| 11 | 11 |
tags uint32 |
| 12 | 12 |
} |
| 13 | 13 |
|
| ... | ... |
@@ -59,14 +59,14 @@ func WithTags(newTags uint32) EventOpt {
|
| 59 | 59 |
} |
| 60 | 60 |
|
| 61 | 61 |
// WithActivityID specifies the activity ID of the event to be written. |
| 62 |
-func WithActivityID(activityID *guid.GUID) EventOpt {
|
|
| 62 |
+func WithActivityID(activityID guid.GUID) EventOpt {
|
|
| 63 | 63 |
return func(options *eventOptions) {
|
| 64 | 64 |
options.activityID = activityID |
| 65 | 65 |
} |
| 66 | 66 |
} |
| 67 | 67 |
|
| 68 | 68 |
// WithRelatedActivityID specifies the parent activity ID of the event to be written. |
| 69 |
-func WithRelatedActivityID(activityID *guid.GUID) EventOpt {
|
|
| 69 |
+func WithRelatedActivityID(activityID guid.GUID) EventOpt {
|
|
| 70 | 70 |
return func(options *eventOptions) {
|
| 71 | 71 |
options.relatedActivityID = activityID |
| 72 | 72 |
} |
| ... | ... |
@@ -4,6 +4,8 @@ import ( |
| 4 | 4 |
"fmt" |
| 5 | 5 |
"math" |
| 6 | 6 |
"reflect" |
| 7 |
+ "syscall" |
|
| 8 |
+ "time" |
|
| 7 | 9 |
"unsafe" |
| 8 | 10 |
) |
| 9 | 11 |
|
| ... | ... |
@@ -380,6 +382,14 @@ func Struct(name string, opts ...FieldOpt) FieldOpt {
|
| 380 | 380 |
} |
| 381 | 381 |
} |
| 382 | 382 |
|
| 383 |
+// Time adds a time to the event. |
|
| 384 |
+func Time(name string, value time.Time) FieldOpt {
|
|
| 385 |
+ return func(em *eventMetadata, ed *eventData) {
|
|
| 386 |
+ em.writeField(name, inTypeFileTime, outTypeDateTimeUTC, 0) |
|
| 387 |
+ ed.writeFiletime(syscall.NsecToFiletime(value.UTC().UnixNano())) |
|
| 388 |
+ } |
|
| 389 |
+} |
|
| 390 |
+ |
|
| 383 | 391 |
// Currently, we support logging basic builtin types (int, string, etc), slices |
| 384 | 392 |
// of basic builtin types, error, types derived from the basic types (e.g. "type |
| 385 | 393 |
// foo int"), and structs (recursively logging their fields). We do not support |
| ... | ... |
@@ -454,6 +464,8 @@ func SmartField(name string, v interface{}) FieldOpt {
|
| 454 | 454 |
return Float64Array(name, v) |
| 455 | 455 |
case error: |
| 456 | 456 |
return StringField(name, v.Error()) |
| 457 |
+ case time.Time: |
|
| 458 |
+ return Time(name, v) |
|
| 457 | 459 |
default: |
| 458 | 460 |
switch rv := reflect.ValueOf(v); rv.Kind() {
|
| 459 | 461 |
case reflect.Bool: |
| ... | ... |
@@ -15,7 +15,7 @@ import ( |
| 15 | 15 |
// provider ID to be manually specified. This is most useful when there is an |
| 16 | 16 |
// existing provider ID that must be used to conform to existing diagnostic |
| 17 | 17 |
// infrastructure. |
| 18 |
-func NewProviderWithID(name string, id *guid.GUID, callback EnableCallback) (provider *Provider, err error) {
|
|
| 18 |
+func NewProviderWithID(name string, id guid.GUID, callback EnableCallback) (provider *Provider, err error) {
|
|
| 19 | 19 |
providerCallbackOnce.Do(func() {
|
| 20 | 20 |
globalProviderCallback = windows.NewCallback(providerCallbackAdapter) |
| 21 | 21 |
}) |
| ... | ... |
@@ -29,7 +29,7 @@ func NewProviderWithID(name string, id *guid.GUID, callback EnableCallback) (pro |
| 29 | 29 |
provider.ID = id |
| 30 | 30 |
provider.callback = callback |
| 31 | 31 |
|
| 32 |
- if err := eventRegister((*windows.GUID)(provider.ID), globalProviderCallback, uintptr(provider.index), &provider.handle); err != nil {
|
|
| 32 |
+ if err := eventRegister((*windows.GUID)(&provider.ID), globalProviderCallback, uintptr(provider.index), &provider.handle); err != nil {
|
|
| 33 | 33 |
return nil, err |
| 34 | 34 |
} |
| 35 | 35 |
|
| ... | ... |
@@ -7,6 +7,6 @@ import ( |
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 | 9 |
// NewProviderWithID returns a nil provider on unsupported platforms. |
| 10 |
-func NewProviderWithID(name string, id *guid.GUID, callback EnableCallback) (provider *Provider, err error) {
|
|
| 10 |
+func NewProviderWithID(name string, id guid.GUID, callback EnableCallback) (provider *Provider, err error) {
|
|
| 11 | 11 |
return nil, nil |
| 12 | 12 |
} |
| ... | ... |
@@ -14,7 +14,7 @@ import ( |
| 14 | 14 |
// name and ID (GUID), which should always have a 1:1 mapping to each other |
| 15 | 15 |
// (e.g. don't use multiple provider names with the same ID, or vice versa). |
| 16 | 16 |
type Provider struct {
|
| 17 |
- ID *guid.GUID |
|
| 17 |
+ ID guid.GUID |
|
| 18 | 18 |
handle providerHandle |
| 19 | 19 |
metadata []byte |
| 20 | 20 |
callback EnableCallback |
| ... | ... |
@@ -61,9 +61,9 @@ const ( |
| 61 | 61 |
|
| 62 | 62 |
// EnableCallback is the form of the callback function that receives provider |
| 63 | 63 |
// enable/disable notifications from ETW. |
| 64 |
-type EnableCallback func(*guid.GUID, ProviderState, Level, uint64, uint64, uintptr) |
|
| 64 |
+type EnableCallback func(guid.GUID, ProviderState, Level, uint64, uint64, uintptr) |
|
| 65 | 65 |
|
| 66 |
-func providerCallback(sourceID *guid.GUID, state ProviderState, level Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr, i uintptr) {
|
|
| 66 |
+func providerCallback(sourceID guid.GUID, state ProviderState, level Level, matchAnyKeyword uint64, matchAllKeyword uint64, filterData uintptr, i uintptr) {
|
|
| 67 | 67 |
provider := providers.getProvider(uint(i)) |
| 68 | 68 |
|
| 69 | 69 |
switch state {
|
| ... | ... |
@@ -86,7 +86,7 @@ func providerCallback(sourceID *guid.GUID, state ProviderState, level Level, mat |
| 86 | 86 |
// different size, it has only pointer-sized arguments, which are then cast to |
| 87 | 87 |
// the appropriate types when calling providerCallback. |
| 88 | 88 |
func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr, matchAnyKeyword uintptr, matchAllKeyword uintptr, filterData uintptr, i uintptr) uintptr {
|
| 89 |
- providerCallback(sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i) |
|
| 89 |
+ providerCallback(*sourceID, ProviderState(state), Level(level), uint64(matchAnyKeyword), uint64(matchAllKeyword), filterData, i) |
|
| 90 | 90 |
return 0 |
| 91 | 91 |
} |
| 92 | 92 |
|
| ... | ... |
@@ -94,26 +94,27 @@ func providerCallbackAdapter(sourceID *guid.GUID, state uintptr, level uintptr, |
| 94 | 94 |
// uses the same algorithm as used by .NET's EventSource class, which is based |
| 95 | 95 |
// on RFC 4122. More information on the algorithm can be found here: |
| 96 | 96 |
// https://blogs.msdn.microsoft.com/dcook/2015/09/08/etw-provider-names-and-guids/ |
| 97 |
-// The algorithm is roughly: |
|
| 98 |
-// Hash = Sha1(namespace + arg.ToUpper().ToUtf16be()) |
|
| 99 |
-// Guid = Hash[0..15], with Hash[7] tweaked according to RFC 4122 |
|
| 100 |
-func providerIDFromName(name string) *guid.GUID {
|
|
| 97 |
+// |
|
| 98 |
+// The algorithm is roughly the RFC 4122 algorithm for a V5 UUID, but differs in |
|
| 99 |
+// the following ways: |
|
| 100 |
+// - The input name is first upper-cased, UTF16-encoded, and converted to |
|
| 101 |
+// big-endian. |
|
| 102 |
+// - No variant is set on the result UUID. |
|
| 103 |
+// - The result UUID is treated as being in little-endian format, rather than |
|
| 104 |
+// big-endian. |
|
| 105 |
+func providerIDFromName(name string) guid.GUID {
|
|
| 101 | 106 |
buffer := sha1.New() |
| 102 |
- |
|
| 103 |
- namespace := []byte{0x48, 0x2C, 0x2D, 0xB2, 0xC3, 0x90, 0x47, 0xC8, 0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}
|
|
| 104 |
- buffer.Write(namespace) |
|
| 105 |
- |
|
| 107 |
+ namespace := guid.GUID{0x482C2DB2, 0xC390, 0x47C8, [8]byte{0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB}}
|
|
| 108 |
+ namespaceBytes := namespace.ToArray() |
|
| 109 |
+ buffer.Write(namespaceBytes[:]) |
|
| 106 | 110 |
binary.Write(buffer, binary.BigEndian, utf16.Encode([]rune(strings.ToUpper(name)))) |
| 107 | 111 |
|
| 108 | 112 |
sum := buffer.Sum(nil) |
| 109 | 113 |
sum[7] = (sum[7] & 0xf) | 0x50 |
| 110 | 114 |
|
| 111 |
- return &guid.GUID{
|
|
| 112 |
- Data1: binary.LittleEndian.Uint32(sum[0:4]), |
|
| 113 |
- Data2: binary.LittleEndian.Uint16(sum[4:6]), |
|
| 114 |
- Data3: binary.LittleEndian.Uint16(sum[6:8]), |
|
| 115 |
- Data4: [8]byte{sum[8], sum[9], sum[10], sum[11], sum[12], sum[13], sum[14], sum[15]},
|
|
| 116 |
- } |
|
| 115 |
+ a := [16]byte{}
|
|
| 116 |
+ copy(a[:], sum) |
|
| 117 |
+ return guid.FromWindowsArray(a) |
|
| 117 | 118 |
} |
| 118 | 119 |
|
| 119 | 120 |
// NewProvider creates and registers a new ETW provider. The provider ID is |
| ... | ... |
@@ -219,8 +220,8 @@ func (provider *Provider) WriteEvent(name string, eventOpts []EventOpt, fieldOpt |
| 219 | 219 |
// the ETW infrastructure. |
| 220 | 220 |
func (provider *Provider) writeEventRaw( |
| 221 | 221 |
descriptor *eventDescriptor, |
| 222 |
- activityID *guid.GUID, |
|
| 223 |
- relatedActivityID *guid.GUID, |
|
| 222 |
+ activityID guid.GUID, |
|
| 223 |
+ relatedActivityID guid.GUID, |
|
| 224 | 224 |
metadataBlobs [][]byte, |
| 225 | 225 |
dataBlobs [][]byte) error {
|
| 226 | 226 |
|
| ... | ... |
@@ -235,5 +236,5 @@ func (provider *Provider) writeEventRaw( |
| 235 | 235 |
dataDescriptors = append(dataDescriptors, newEventDataDescriptor(eventDataDescriptorTypeUserData, blob)) |
| 236 | 236 |
} |
| 237 | 237 |
|
| 238 |
- return eventWriteTransfer(provider.handle, descriptor, (*windows.GUID)(activityID), (*windows.GUID)(relatedActivityID), dataDescriptorCount, &dataDescriptors[0]) |
|
| 238 |
+ return eventWriteTransfer(provider.handle, descriptor, (*windows.GUID)(&activityID), (*windows.GUID)(&relatedActivityID), dataDescriptorCount, &dataDescriptors[0]) |
|
| 239 | 239 |
} |
| ... | ... |
@@ -1,6 +1,8 @@ |
| 1 | 1 |
package etwlogrus |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "sort" |
|
| 5 |
+ |
|
| 4 | 6 |
"github.com/Microsoft/go-winio/pkg/etw" |
| 5 | 7 |
"github.com/sirupsen/logrus" |
| 6 | 8 |
) |
| ... | ... |
@@ -31,15 +33,7 @@ func NewHookFromProvider(provider *etw.Provider) (*Hook, error) {
|
| 31 | 31 |
// Levels returns the set of levels that this hook wants to receive log entries |
| 32 | 32 |
// for. |
| 33 | 33 |
func (h *Hook) Levels() []logrus.Level {
|
| 34 |
- return []logrus.Level{
|
|
| 35 |
- logrus.TraceLevel, |
|
| 36 |
- logrus.DebugLevel, |
|
| 37 |
- logrus.InfoLevel, |
|
| 38 |
- logrus.WarnLevel, |
|
| 39 |
- logrus.ErrorLevel, |
|
| 40 |
- logrus.FatalLevel, |
|
| 41 |
- logrus.PanicLevel, |
|
| 42 |
- } |
|
| 34 |
+ return logrus.AllLevels |
|
| 43 | 35 |
} |
| 44 | 36 |
|
| 45 | 37 |
var logrusToETWLevelMap = map[logrus.Level]etw.Level{
|
| ... | ... |
@@ -62,19 +56,42 @@ func (h *Hook) Fire(e *logrus.Entry) error {
|
| 62 | 62 |
return nil |
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 |
- // Reserve extra space for the message field. |
|
| 66 |
- fields := make([]etw.FieldOpt, 0, len(e.Data)+1) |
|
| 65 |
+ // Sort the fields by name so they are consistent in each instance |
|
| 66 |
+ // of an event. Otherwise, the fields don't line up in WPA. |
|
| 67 |
+ names := make([]string, 0, len(e.Data)) |
|
| 68 |
+ hasError := false |
|
| 69 |
+ for k := range e.Data {
|
|
| 70 |
+ if k == logrus.ErrorKey {
|
|
| 71 |
+ // Always put the error last because it is optional in some events. |
|
| 72 |
+ hasError = true |
|
| 73 |
+ } else {
|
|
| 74 |
+ names = append(names, k) |
|
| 75 |
+ } |
|
| 76 |
+ } |
|
| 77 |
+ sort.Strings(names) |
|
| 67 | 78 |
|
| 79 |
+ // Reserve extra space for the message and time fields. |
|
| 80 |
+ fields := make([]etw.FieldOpt, 0, len(e.Data)+2) |
|
| 68 | 81 |
fields = append(fields, etw.StringField("Message", e.Message))
|
| 69 |
- |
|
| 70 |
- for k, v := range e.Data {
|
|
| 71 |
- fields = append(fields, etw.SmartField(k, v)) |
|
| 82 |
+ fields = append(fields, etw.Time("Time", e.Time))
|
|
| 83 |
+ for _, k := range names {
|
|
| 84 |
+ fields = append(fields, etw.SmartField(k, e.Data[k])) |
|
| 85 |
+ } |
|
| 86 |
+ if hasError {
|
|
| 87 |
+ fields = append(fields, etw.SmartField(logrus.ErrorKey, e.Data[logrus.ErrorKey])) |
|
| 72 | 88 |
} |
| 73 | 89 |
|
| 74 |
- return h.provider.WriteEvent( |
|
| 90 |
+ // Firing an ETW event is essentially best effort, as the event write can |
|
| 91 |
+ // fail for reasons completely out of the control of the event writer (such |
|
| 92 |
+ // as a session listening for the event having no available space in its |
|
| 93 |
+ // buffers). Therefore, we don't return the error from WriteEvent, as it is |
|
| 94 |
+ // just noise in many cases. |
|
| 95 |
+ h.provider.WriteEvent( |
|
| 75 | 96 |
"LogrusEntry", |
| 76 | 97 |
etw.WithEventOpts(etw.WithLevel(level)), |
| 77 | 98 |
fields) |
| 99 |
+ |
|
| 100 |
+ return nil |
|
| 78 | 101 |
} |
| 79 | 102 |
|
| 80 | 103 |
// Close cleans up the hook and closes the ETW provider. If the provder was |
| ... | ... |
@@ -1,19 +1,43 @@ |
| 1 |
+// Package guid provides a GUID type. The backing structure for a GUID is |
|
| 2 |
+// identical to that used by the golang.org/x/sys/windows GUID type. |
|
| 3 |
+// There are two main binary encodings used for a GUID, the big-endian encoding, |
|
| 4 |
+// and the Windows (mixed-endian) encoding. See here for details: |
|
| 5 |
+// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding |
|
| 1 | 6 |
package guid |
| 2 | 7 |
|
| 3 | 8 |
import ( |
| 4 | 9 |
"crypto/rand" |
| 10 |
+ "crypto/sha1" |
|
| 11 |
+ "encoding" |
|
| 5 | 12 |
"encoding/binary" |
| 6 |
- "encoding/json" |
|
| 7 | 13 |
"fmt" |
| 8 | 14 |
"strconv" |
| 9 |
- "strings" |
|
| 10 | 15 |
|
| 11 |
- "github.com/pkg/errors" |
|
| 12 | 16 |
"golang.org/x/sys/windows" |
| 13 | 17 |
) |
| 14 | 18 |
|
| 15 |
-var _ = (json.Marshaler)(&GUID{})
|
|
| 16 |
-var _ = (json.Unmarshaler)(&GUID{})
|
|
| 19 |
+// Variant specifies which GUID variant (or "type") of the GUID. It determines |
|
| 20 |
+// how the entirety of the rest of the GUID is interpreted. |
|
| 21 |
+type Variant uint8 |
|
| 22 |
+ |
|
| 23 |
+// The variants specified by RFC 4122. |
|
| 24 |
+const ( |
|
| 25 |
+ // VariantUnknown specifies a GUID variant which does not conform to one of |
|
| 26 |
+ // the variant encodings specified in RFC 4122. |
|
| 27 |
+ VariantUnknown Variant = iota |
|
| 28 |
+ VariantNCS |
|
| 29 |
+ VariantRFC4122 |
|
| 30 |
+ VariantMicrosoft |
|
| 31 |
+ VariantFuture |
|
| 32 |
+) |
|
| 33 |
+ |
|
| 34 |
+// Version specifies how the bits in the GUID were generated. For instance, a |
|
| 35 |
+// version 4 GUID is randomly generated, and a version 5 is generated from the |
|
| 36 |
+// hash of an input string. |
|
| 37 |
+type Version uint8 |
|
| 38 |
+ |
|
| 39 |
+var _ = (encoding.TextMarshaler)(GUID{})
|
|
| 40 |
+var _ = (encoding.TextUnmarshaler)(&GUID{})
|
|
| 17 | 41 |
|
| 18 | 42 |
// GUID represents a GUID/UUID. It has the same structure as |
| 19 | 43 |
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting |
| ... | ... |
@@ -23,24 +47,83 @@ var _ = (json.Unmarshaler)(&GUID{})
|
| 23 | 23 |
type GUID windows.GUID |
| 24 | 24 |
|
| 25 | 25 |
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122. |
| 26 |
-func NewV4() (*GUID, error) {
|
|
| 26 |
+func NewV4() (GUID, error) {
|
|
| 27 | 27 |
var b [16]byte |
| 28 | 28 |
if _, err := rand.Read(b[:]); err != nil {
|
| 29 |
- return nil, err |
|
| 29 |
+ return GUID{}, err
|
|
| 30 | 30 |
} |
| 31 | 31 |
|
| 32 |
+ g := FromArray(b) |
|
| 33 |
+ g.setVersion(4) // Version 4 means randomly generated. |
|
| 34 |
+ g.setVariant(VariantRFC4122) |
|
| 35 |
+ |
|
| 36 |
+ return g, nil |
|
| 37 |
+} |
|
| 38 |
+ |
|
| 39 |
+// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing) |
|
| 40 |
+// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name, |
|
| 41 |
+// and the sample code treats it as a series of bytes, so we do the same here. |
|
| 42 |
+// |
|
| 43 |
+// Some implementations, such as those found on Windows, treat the name as a |
|
| 44 |
+// big-endian UTF16 stream of bytes. If that is desired, the string can be |
|
| 45 |
+// encoded as such before being passed to this function. |
|
| 46 |
+func NewV5(namespace GUID, name []byte) (GUID, error) {
|
|
| 47 |
+ b := sha1.New() |
|
| 48 |
+ namespaceBytes := namespace.ToArray() |
|
| 49 |
+ b.Write(namespaceBytes[:]) |
|
| 50 |
+ b.Write(name) |
|
| 51 |
+ |
|
| 52 |
+ a := [16]byte{}
|
|
| 53 |
+ copy(a[:], b.Sum(nil)) |
|
| 54 |
+ |
|
| 55 |
+ g := FromArray(a) |
|
| 56 |
+ g.setVersion(5) // Version 5 means generated from a string. |
|
| 57 |
+ g.setVariant(VariantRFC4122) |
|
| 58 |
+ |
|
| 59 |
+ return g, nil |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+func fromArray(b [16]byte, order binary.ByteOrder) GUID {
|
|
| 32 | 63 |
var g GUID |
| 33 |
- g.Data1 = binary.LittleEndian.Uint32(b[0:4]) |
|
| 34 |
- g.Data2 = binary.LittleEndian.Uint16(b[4:6]) |
|
| 35 |
- g.Data3 = binary.LittleEndian.Uint16(b[6:8]) |
|
| 64 |
+ g.Data1 = order.Uint32(b[0:4]) |
|
| 65 |
+ g.Data2 = order.Uint16(b[4:6]) |
|
| 66 |
+ g.Data3 = order.Uint16(b[6:8]) |
|
| 36 | 67 |
copy(g.Data4[:], b[8:16]) |
| 68 |
+ return g |
|
| 69 |
+} |
|
| 70 |
+ |
|
| 71 |
+func (g GUID) toArray(order binary.ByteOrder) [16]byte {
|
|
| 72 |
+ b := [16]byte{}
|
|
| 73 |
+ order.PutUint32(b[0:4], g.Data1) |
|
| 74 |
+ order.PutUint16(b[4:6], g.Data2) |
|
| 75 |
+ order.PutUint16(b[6:8], g.Data3) |
|
| 76 |
+ copy(b[8:16], g.Data4[:]) |
|
| 77 |
+ return b |
|
| 78 |
+} |
|
| 37 | 79 |
|
| 38 |
- g.Data3 = (g.Data3 & 0x0fff) | 0x4000 // Version 4 (randomly generated) |
|
| 39 |
- g.Data4[0] = (g.Data4[0] & 0x3f) | 0x80 // RFC4122 variant |
|
| 40 |
- return &g, nil |
|
| 80 |
+// FromArray constructs a GUID from a big-endian encoding array of 16 bytes. |
|
| 81 |
+func FromArray(b [16]byte) GUID {
|
|
| 82 |
+ return fromArray(b, binary.BigEndian) |
|
| 41 | 83 |
} |
| 42 | 84 |
|
| 43 |
-func (g *GUID) String() string {
|
|
| 85 |
+// ToArray returns an array of 16 bytes representing the GUID in big-endian |
|
| 86 |
+// encoding. |
|
| 87 |
+func (g GUID) ToArray() [16]byte {
|
|
| 88 |
+ return g.toArray(binary.BigEndian) |
|
| 89 |
+} |
|
| 90 |
+ |
|
| 91 |
+// FromWindowsArray constructs a GUID from a Windows encoding array of bytes. |
|
| 92 |
+func FromWindowsArray(b [16]byte) GUID {
|
|
| 93 |
+ return fromArray(b, binary.LittleEndian) |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows |
|
| 97 |
+// encoding. |
|
| 98 |
+func (g GUID) ToWindowsArray() [16]byte {
|
|
| 99 |
+ return g.toArray(binary.LittleEndian) |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 102 |
+func (g GUID) String() string {
|
|
| 44 | 103 |
return fmt.Sprintf( |
| 45 | 104 |
"%08x-%04x-%04x-%04x-%012x", |
| 46 | 105 |
g.Data1, |
| ... | ... |
@@ -53,58 +136,100 @@ func (g *GUID) String() string {
|
| 53 | 53 |
// FromString parses a string containing a GUID and returns the GUID. The only |
| 54 | 54 |
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
| 55 | 55 |
// format. |
| 56 |
-func FromString(s string) (*GUID, error) {
|
|
| 56 |
+func FromString(s string) (GUID, error) {
|
|
| 57 | 57 |
if len(s) != 36 {
|
| 58 |
- return nil, errors.New("invalid GUID format (length)")
|
|
| 58 |
+ return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
| 59 | 59 |
} |
| 60 | 60 |
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
| 61 |
- return nil, errors.New("invalid GUID format (dashes)")
|
|
| 61 |
+ return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
| 62 | 62 |
} |
| 63 | 63 |
|
| 64 | 64 |
var g GUID |
| 65 | 65 |
|
| 66 | 66 |
data1, err := strconv.ParseUint(s[0:8], 16, 32) |
| 67 | 67 |
if err != nil {
|
| 68 |
- return nil, errors.Wrap(err, "invalid GUID format (Data1)") |
|
| 68 |
+ return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
| 69 | 69 |
} |
| 70 | 70 |
g.Data1 = uint32(data1) |
| 71 | 71 |
|
| 72 | 72 |
data2, err := strconv.ParseUint(s[9:13], 16, 16) |
| 73 | 73 |
if err != nil {
|
| 74 |
- return nil, errors.Wrap(err, "invalid GUID format (Data2)") |
|
| 74 |
+ return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
| 75 | 75 |
} |
| 76 | 76 |
g.Data2 = uint16(data2) |
| 77 | 77 |
|
| 78 | 78 |
data3, err := strconv.ParseUint(s[14:18], 16, 16) |
| 79 | 79 |
if err != nil {
|
| 80 |
- return nil, errors.Wrap(err, "invalid GUID format (Data3)") |
|
| 80 |
+ return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
| 81 | 81 |
} |
| 82 | 82 |
g.Data3 = uint16(data3) |
| 83 | 83 |
|
| 84 | 84 |
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
| 85 | 85 |
v, err := strconv.ParseUint(s[x:x+2], 16, 8) |
| 86 | 86 |
if err != nil {
|
| 87 |
- return nil, errors.Wrap(err, "invalid GUID format (Data4)") |
|
| 87 |
+ return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
| 88 | 88 |
} |
| 89 | 89 |
g.Data4[i] = uint8(v) |
| 90 | 90 |
} |
| 91 | 91 |
|
| 92 |
- return &g, nil |
|
| 92 |
+ return g, nil |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+func (g *GUID) setVariant(v Variant) {
|
|
| 96 |
+ d := g.Data4[0] |
|
| 97 |
+ switch v {
|
|
| 98 |
+ case VariantNCS: |
|
| 99 |
+ d = (d & 0x7f) |
|
| 100 |
+ case VariantRFC4122: |
|
| 101 |
+ d = (d & 0x3f) | 0x80 |
|
| 102 |
+ case VariantMicrosoft: |
|
| 103 |
+ d = (d & 0x1f) | 0xc0 |
|
| 104 |
+ case VariantFuture: |
|
| 105 |
+ d = (d & 0x0f) | 0xe0 |
|
| 106 |
+ case VariantUnknown: |
|
| 107 |
+ fallthrough |
|
| 108 |
+ default: |
|
| 109 |
+ panic(fmt.Sprintf("invalid variant: %d", v))
|
|
| 110 |
+ } |
|
| 111 |
+ g.Data4[0] = d |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// Variant returns the GUID variant, as defined in RFC 4122. |
|
| 115 |
+func (g GUID) Variant() Variant {
|
|
| 116 |
+ b := g.Data4[0] |
|
| 117 |
+ if b&0x80 == 0 {
|
|
| 118 |
+ return VariantNCS |
|
| 119 |
+ } else if b&0xc0 == 0x80 {
|
|
| 120 |
+ return VariantRFC4122 |
|
| 121 |
+ } else if b&0xe0 == 0xc0 {
|
|
| 122 |
+ return VariantMicrosoft |
|
| 123 |
+ } else if b&0xe0 == 0xe0 {
|
|
| 124 |
+ return VariantFuture |
|
| 125 |
+ } |
|
| 126 |
+ return VariantUnknown |
|
| 127 |
+} |
|
| 128 |
+ |
|
| 129 |
+func (g *GUID) setVersion(v Version) {
|
|
| 130 |
+ g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12) |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 133 |
+// Version returns the GUID version, as defined in RFC 4122. |
|
| 134 |
+func (g GUID) Version() Version {
|
|
| 135 |
+ return Version((g.Data3 & 0xF000) >> 12) |
|
| 93 | 136 |
} |
| 94 | 137 |
|
| 95 |
-// MarshalJSON marshals the GUID to JSON representation and returns it as a |
|
| 96 |
-// slice of bytes. |
|
| 97 |
-func (g *GUID) MarshalJSON() ([]byte, error) {
|
|
| 98 |
- return json.Marshal(g.String()) |
|
| 138 |
+// MarshalText returns the textual representation of the GUID. |
|
| 139 |
+func (g GUID) MarshalText() ([]byte, error) {
|
|
| 140 |
+ return []byte(g.String()), nil |
|
| 99 | 141 |
} |
| 100 | 142 |
|
| 101 |
-// UnmarshalJSON unmarshals a GUID from JSON representation and sets itself to |
|
| 102 |
-// the unmarshaled GUID. |
|
| 103 |
-func (g *GUID) UnmarshalJSON(data []byte) error {
|
|
| 104 |
- g2, err := FromString(strings.Trim(string(data), "\"")) |
|
| 143 |
+// UnmarshalText takes the textual representation of a GUID, and unmarhals it |
|
| 144 |
+// into this GUID. |
|
| 145 |
+func (g *GUID) UnmarshalText(text []byte) error {
|
|
| 146 |
+ g2, err := FromString(string(text)) |
|
| 105 | 147 |
if err != nil {
|
| 106 | 148 |
return err |
| 107 | 149 |
} |
| 108 |
- *g = *g2 |
|
| 150 |
+ *g = g2 |
|
| 109 | 151 |
return nil |
| 110 | 152 |
} |
| ... | ... |
@@ -117,9 +117,13 @@ func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
|
| 117 | 117 |
return nil |
| 118 | 118 |
} |
| 119 | 119 |
|
| 120 |
-// DetachVhd detaches a VHD attached at the given path. |
|
| 120 |
+// DetachVhd detaches a mounted container layer vhd found at `path`. |
|
| 121 | 121 |
func DetachVhd(path string) error {
|
| 122 |
- handle, err := OpenVirtualDisk(path, VirtualDiskAccessDetach, OpenVirtualDiskFlagNone) |
|
| 122 |
+ handle, err := OpenVirtualDisk( |
|
| 123 |
+ path, |
|
| 124 |
+ VirtualDiskAccessNone, |
|
| 125 |
+ OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator) |
|
| 126 |
+ |
|
| 123 | 127 |
if err != nil {
|
| 124 | 128 |
return err |
| 125 | 129 |
} |
| ... | ... |
@@ -127,7 +131,7 @@ func DetachVhd(path string) error {
|
| 127 | 127 |
return detachVirtualDisk(handle, 0, 0) |
| 128 | 128 |
} |
| 129 | 129 |
|
| 130 |
-// OpenVirtuaDisk obtains a handle to a VHD opened with supplied access mask and flags. |
|
| 130 |
+// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags. |
|
| 131 | 131 |
func OpenVirtualDisk(path string, accessMask VirtualDiskAccessMask, flag VirtualDiskFlag) (syscall.Handle, error) {
|
| 132 | 132 |
var ( |
| 133 | 133 |
defaultType virtualStorageType |