Merge pull request #2284 from mtrmac/no-windows
RFC: Remove drivers/windows
This commit is contained in:
commit
f555b9f2dd
|
|
@ -1,4 +1,4 @@
|
||||||
//go:build !linux && !windows && !freebsd && !solaris && !darwin
|
//go:build !linux && !freebsd && !solaris && !darwin
|
||||||
|
|
||||||
package graphdriver
|
package graphdriver
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package graphdriver
|
|
||||||
|
|
||||||
// Slice of drivers that should be used in order
|
|
||||||
var Priority = []string{
|
|
||||||
"windowsfilter",
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFSMagic returns the filesystem id given the path.
|
|
||||||
func GetFSMagic(rootpath string) (FsMagic, error) {
|
|
||||||
// Note it is OK to return FsMagicUnsupported on Windows.
|
|
||||||
return FsMagicUnsupported, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package register
|
|
||||||
|
|
||||||
import (
|
|
||||||
// register the windows graph driver
|
|
||||||
_ "github.com/containers/storage/drivers/windows"
|
|
||||||
)
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
package windows
|
|
||||||
|
|
||||||
import jsoniter "github.com/json-iterator/go"
|
|
||||||
|
|
||||||
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,18 +0,0 @@
|
||||||
package windows
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestAddAceToSddlDacl(t *testing.T) {
|
|
||||||
cases := [][3]string{
|
|
||||||
{"D:", "(A;;;)", "D:(A;;;)"},
|
|
||||||
{"D:(A;;;)", "(A;;;)", "D:(A;;;)"},
|
|
||||||
{"O:D:(A;;;stuff)", "(A;;;new)", "O:D:(A;;;new)(A;;;stuff)"},
|
|
||||||
{"O:D:(D;;;no)(A;;;stuff)", "(A;;;new)", "O:D:(D;;;no)(A;;;new)(A;;;stuff)"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
if newSddl, worked := addAceToSddlDacl(c[0], c[1]); !worked || newSddl != c[2] {
|
|
||||||
t.Errorf("%s + %s == %s, expected %s (%v)", c[0], c[1], newSddl, c[2], worked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
go.mod
13
go.mod
|
|
@ -7,8 +7,6 @@ module github.com/containers/storage
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.5.0
|
github.com/BurntSushi/toml v1.5.0
|
||||||
github.com/Microsoft/go-winio v0.6.2
|
|
||||||
github.com/Microsoft/hcsshim v0.13.0
|
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3
|
github.com/containerd/stargz-snapshotter/estargz v0.16.3
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1
|
github.com/cyphar/filepath-securejoin v0.4.1
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
|
|
@ -35,22 +33,11 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/containerd/cgroups/v3 v3.0.5 // indirect
|
|
||||||
github.com/containerd/errdefs v0.3.0 // indirect
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
|
||||||
github.com/containerd/typeurl/v2 v2.2.0 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect
|
|
||||||
google.golang.org/grpc v1.69.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.35.2 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
133
go.sum
133
go.sum
|
|
@ -1,24 +1,7 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
|
||||||
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
|
|
||||||
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
|
||||||
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
|
|
||||||
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
|
|
||||||
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
|
|
||||||
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
|
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
|
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
|
||||||
github.com/containerd/typeurl/v2 v2.2.0 h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso=
|
|
||||||
github.com/containerd/typeurl/v2 v2.2.0/go.mod h1:8XOOxnyatxSWuG8OfsZXVnAF4iZfedjS/8UHSPJnX4g=
|
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
@ -26,47 +9,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=
|
github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=
|
||||||
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
|
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
|
@ -92,22 +44,14 @@ github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU
|
||||||
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
|
||||||
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
|
github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
|
||||||
|
|
@ -116,86 +60,11 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
|
||||||
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
|
||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
|
||||||
google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI=
|
|
||||||
google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
|
||||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
|
||||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
@ -203,5 +72,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
* text=auto eol=lf
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
.vscode/
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
# testing
|
|
||||||
testdata
|
|
||||||
|
|
||||||
# go workspaces
|
|
||||||
go.work
|
|
||||||
go.work.sum
|
|
||||||
|
|
@ -1,147 +0,0 @@
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
# style
|
|
||||||
- containedctx # struct contains a context
|
|
||||||
- dupl # duplicate code
|
|
||||||
- errname # erorrs are named correctly
|
|
||||||
- nolintlint # "//nolint" directives are properly explained
|
|
||||||
- revive # golint replacement
|
|
||||||
- unconvert # unnecessary conversions
|
|
||||||
- wastedassign
|
|
||||||
|
|
||||||
# bugs, performance, unused, etc ...
|
|
||||||
- contextcheck # function uses a non-inherited context
|
|
||||||
- errorlint # errors not wrapped for 1.13
|
|
||||||
- exhaustive # check exhaustiveness of enum switch statements
|
|
||||||
- gofmt # files are gofmt'ed
|
|
||||||
- gosec # security
|
|
||||||
- nilerr # returns nil even with non-nil error
|
|
||||||
- thelper # test helpers without t.Helper()
|
|
||||||
- unparam # unused function params
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude-dirs:
|
|
||||||
- pkg/etw/sample
|
|
||||||
|
|
||||||
exclude-rules:
|
|
||||||
# err is very often shadowed in nested scopes
|
|
||||||
- linters:
|
|
||||||
- govet
|
|
||||||
text: '^shadow: declaration of "err" shadows declaration'
|
|
||||||
|
|
||||||
# ignore long lines for skip autogen directives
|
|
||||||
- linters:
|
|
||||||
- revive
|
|
||||||
text: "^line-length-limit: "
|
|
||||||
source: "^//(go:generate|sys) "
|
|
||||||
|
|
||||||
#TODO: remove after upgrading to go1.18
|
|
||||||
# ignore comment spacing for nolint and sys directives
|
|
||||||
- linters:
|
|
||||||
- revive
|
|
||||||
text: "^comment-spacings: no space between comment delimiter and comment text"
|
|
||||||
source: "//(cspell:|nolint:|sys |todo)"
|
|
||||||
|
|
||||||
# not on go 1.18 yet, so no any
|
|
||||||
- linters:
|
|
||||||
- revive
|
|
||||||
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
|
|
||||||
|
|
||||||
# allow unjustified ignores of error checks in defer statements
|
|
||||||
- linters:
|
|
||||||
- nolintlint
|
|
||||||
text: "^directive `//nolint:errcheck` should provide explanation"
|
|
||||||
source: '^\s*defer '
|
|
||||||
|
|
||||||
# allow unjustified ignores of error lints for io.EOF
|
|
||||||
- linters:
|
|
||||||
- nolintlint
|
|
||||||
text: "^directive `//nolint:errorlint` should provide explanation"
|
|
||||||
source: '[=|!]= io.EOF'
|
|
||||||
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
exhaustive:
|
|
||||||
default-signifies-exhaustive: true
|
|
||||||
govet:
|
|
||||||
enable-all: true
|
|
||||||
disable:
|
|
||||||
# struct order is often for Win32 compat
|
|
||||||
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
|
||||||
- fieldalignment
|
|
||||||
nolintlint:
|
|
||||||
require-explanation: true
|
|
||||||
require-specific: true
|
|
||||||
revive:
|
|
||||||
# revive is more configurable than static check, so likely the preferred alternative to static-check
|
|
||||||
# (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997)
|
|
||||||
enable-all-rules:
|
|
||||||
true
|
|
||||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
|
||||||
rules:
|
|
||||||
# rules with required arguments
|
|
||||||
- name: argument-limit
|
|
||||||
disabled: true
|
|
||||||
- name: banned-characters
|
|
||||||
disabled: true
|
|
||||||
- name: cognitive-complexity
|
|
||||||
disabled: true
|
|
||||||
- name: cyclomatic
|
|
||||||
disabled: true
|
|
||||||
- name: file-header
|
|
||||||
disabled: true
|
|
||||||
- name: function-length
|
|
||||||
disabled: true
|
|
||||||
- name: function-result-limit
|
|
||||||
disabled: true
|
|
||||||
- name: max-public-structs
|
|
||||||
disabled: true
|
|
||||||
# geneally annoying rules
|
|
||||||
- name: add-constant # complains about any and all strings and integers
|
|
||||||
disabled: true
|
|
||||||
- name: confusing-naming # we frequently use "Foo()" and "foo()" together
|
|
||||||
disabled: true
|
|
||||||
- name: flag-parameter # excessive, and a common idiom we use
|
|
||||||
disabled: true
|
|
||||||
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
|
|
||||||
disabled: true
|
|
||||||
# general config
|
|
||||||
- name: line-length-limit
|
|
||||||
arguments:
|
|
||||||
- 140
|
|
||||||
- name: var-naming
|
|
||||||
arguments:
|
|
||||||
- []
|
|
||||||
- - CID
|
|
||||||
- CRI
|
|
||||||
- CTRD
|
|
||||||
- DACL
|
|
||||||
- DLL
|
|
||||||
- DOS
|
|
||||||
- ETW
|
|
||||||
- FSCTL
|
|
||||||
- GCS
|
|
||||||
- GMSA
|
|
||||||
- HCS
|
|
||||||
- HV
|
|
||||||
- IO
|
|
||||||
- LCOW
|
|
||||||
- LDAP
|
|
||||||
- LPAC
|
|
||||||
- LTSC
|
|
||||||
- MMIO
|
|
||||||
- NT
|
|
||||||
- OCI
|
|
||||||
- PMEM
|
|
||||||
- PWSH
|
|
||||||
- RX
|
|
||||||
- SACl
|
|
||||||
- SID
|
|
||||||
- SMB
|
|
||||||
- TX
|
|
||||||
- VHD
|
|
||||||
- VHDX
|
|
||||||
- VMID
|
|
||||||
- VPCI
|
|
||||||
- WCOW
|
|
||||||
- WIM
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
* @microsoft/containerplat
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Microsoft
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
# go-winio [](https://github.com/microsoft/go-winio/actions/workflows/ci.yml)
|
|
||||||
|
|
||||||
This repository contains utilities for efficiently performing Win32 IO operations in
|
|
||||||
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
|
||||||
for using named pipes as a net transport.
|
|
||||||
|
|
||||||
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
|
||||||
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
|
||||||
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
|
||||||
package.
|
|
||||||
|
|
||||||
Please see the LICENSE file for licensing information.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
This project welcomes contributions and suggestions.
|
|
||||||
Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that
|
|
||||||
you have the right to, and actually do, grant us the rights to use your contribution.
|
|
||||||
For details, visit [Microsoft CLA](https://cla.microsoft.com).
|
|
||||||
|
|
||||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to
|
|
||||||
provide a CLA and decorate the PR appropriately (e.g., label, comment).
|
|
||||||
Simply follow the instructions provided by the bot.
|
|
||||||
You will only need to do this once across all repos using our CLA.
|
|
||||||
|
|
||||||
Additionally, the pull request pipeline requires the following steps to be performed before
|
|
||||||
mergining.
|
|
||||||
|
|
||||||
### Code Sign-Off
|
|
||||||
|
|
||||||
We require that contributors sign their commits using [`git commit --signoff`][git-commit-s]
|
|
||||||
to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
|
||||||
|
|
||||||
A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s].
|
|
||||||
|
|
||||||
Please see [the developer certificate](https://developercertificate.org) for more info,
|
|
||||||
as well as to make sure that you can attest to the rules listed.
|
|
||||||
Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
|
|
||||||
|
|
||||||
### Linting
|
|
||||||
|
|
||||||
Code must pass a linting stage, which uses [`golangci-lint`][lint].
|
|
||||||
The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run
|
|
||||||
automatically with VSCode by adding the following to your workspace or folder settings:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"go.lintTool": "golangci-lint",
|
|
||||||
"go.lintOnSave": "package",
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional editor [integrations options are also available][lint-ide].
|
|
||||||
|
|
||||||
Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# use . or specify a path to only lint a package
|
|
||||||
# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0"
|
|
||||||
> golangci-lint run ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Go Generate
|
|
||||||
|
|
||||||
The pipeline checks that auto-generated code, via `go generate`, are up to date.
|
|
||||||
|
|
||||||
This can be done for the entire repo:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
> go generate ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
||||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
||||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
||||||
|
|
||||||
## Special Thanks
|
|
||||||
|
|
||||||
Thanks to [natefinch][natefinch] for the inspiration for this library.
|
|
||||||
See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation.
|
|
||||||
|
|
||||||
[lint]: https://golangci-lint.run/
|
|
||||||
[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration
|
|
||||||
[lint-install]: https://golangci-lint.run/usage/install/#local-installation
|
|
||||||
|
|
||||||
[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s
|
|
||||||
[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff
|
|
||||||
|
|
||||||
[natefinch]: https://github.com/natefinch
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
|
||||||
|
|
||||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
|
||||||
|
|
||||||
## Reporting Security Issues
|
|
||||||
|
|
||||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
||||||
|
|
||||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
|
||||||
|
|
||||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
|
||||||
|
|
||||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
|
||||||
|
|
||||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
|
||||||
|
|
||||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
|
||||||
* Full paths of source file(s) related to the manifestation of the issue
|
|
||||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
|
||||||
* Any special configuration required to reproduce the issue
|
|
||||||
* Step-by-step instructions to reproduce the issue
|
|
||||||
* Proof-of-concept or exploit code (if possible)
|
|
||||||
* Impact of the issue, including how an attacker might exploit the issue
|
|
||||||
|
|
||||||
This information will help us triage your report more quickly.
|
|
||||||
|
|
||||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
|
||||||
|
|
||||||
## Preferred Languages
|
|
||||||
|
|
||||||
We prefer all communications to be in English.
|
|
||||||
|
|
||||||
## Policy
|
|
||||||
|
|
||||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
|
||||||
|
|
||||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
||||||
|
|
@ -1,287 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"unicode/utf16"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/internal/fs"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
//sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
|
||||||
//sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
|
||||||
|
|
||||||
const (
|
|
||||||
BackupData = uint32(iota + 1)
|
|
||||||
BackupEaData
|
|
||||||
BackupSecurity
|
|
||||||
BackupAlternateData
|
|
||||||
BackupLink
|
|
||||||
BackupPropertyData
|
|
||||||
BackupObjectId //revive:disable-line:var-naming ID, not Id
|
|
||||||
BackupReparseData
|
|
||||||
BackupSparseBlock
|
|
||||||
BackupTxfsData
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
StreamSparseAttributes = uint32(8)
|
|
||||||
)
|
|
||||||
|
|
||||||
//nolint:revive // var-naming: ALL_CAPS
|
|
||||||
const (
|
|
||||||
WRITE_DAC = windows.WRITE_DAC
|
|
||||||
WRITE_OWNER = windows.WRITE_OWNER
|
|
||||||
ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY
|
|
||||||
)
|
|
||||||
|
|
||||||
// BackupHeader represents a backup stream of a file.
|
|
||||||
type BackupHeader struct {
|
|
||||||
//revive:disable-next-line:var-naming ID, not Id
|
|
||||||
Id uint32 // The backup stream ID
|
|
||||||
Attributes uint32 // Stream attributes
|
|
||||||
Size int64 // The size of the stream in bytes
|
|
||||||
Name string // The name of the stream (for BackupAlternateData only).
|
|
||||||
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
|
|
||||||
}
|
|
||||||
|
|
||||||
type win32StreamID struct {
|
|
||||||
StreamID uint32
|
|
||||||
Attributes uint32
|
|
||||||
Size uint64
|
|
||||||
NameSize uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
|
|
||||||
// of BackupHeader values.
|
|
||||||
type BackupStreamReader struct {
|
|
||||||
r io.Reader
|
|
||||||
bytesLeft int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
|
|
||||||
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
|
||||||
return &BackupStreamReader{r, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
|
|
||||||
// it was not completely read.
|
|
||||||
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
|
||||||
if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this
|
|
||||||
if s, ok := r.r.(io.Seeker); ok {
|
|
||||||
// Make sure Seek on io.SeekCurrent sometimes succeeds
|
|
||||||
// before trying the actual seek.
|
|
||||||
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
|
|
||||||
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r.bytesLeft = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(io.Discard, r); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var wsi win32StreamID
|
|
||||||
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hdr := &BackupHeader{
|
|
||||||
Id: wsi.StreamID,
|
|
||||||
Attributes: wsi.Attributes,
|
|
||||||
Size: int64(wsi.Size),
|
|
||||||
}
|
|
||||||
if wsi.NameSize != 0 {
|
|
||||||
name := make([]uint16, int(wsi.NameSize/2))
|
|
||||||
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hdr.Name = windows.UTF16ToString(name)
|
|
||||||
}
|
|
||||||
if wsi.StreamID == BackupSparseBlock {
|
|
||||||
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
hdr.Size -= 8
|
|
||||||
}
|
|
||||||
r.bytesLeft = hdr.Size
|
|
||||||
return hdr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads from the current backup stream.
|
|
||||||
func (r *BackupStreamReader) Read(b []byte) (int, error) {
|
|
||||||
if r.bytesLeft == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
if int64(len(b)) > r.bytesLeft {
|
|
||||||
b = b[:r.bytesLeft]
|
|
||||||
}
|
|
||||||
n, err := r.r.Read(b)
|
|
||||||
r.bytesLeft -= int64(n)
|
|
||||||
if err == io.EOF {
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
} else if r.bytesLeft == 0 && err == nil {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
|
|
||||||
type BackupStreamWriter struct {
|
|
||||||
w io.Writer
|
|
||||||
bytesLeft int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
|
|
||||||
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
|
|
||||||
return &BackupStreamWriter{w, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteHeader writes the next backup stream header and prepares for calls to Write().
|
|
||||||
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
|
|
||||||
if w.bytesLeft != 0 {
|
|
||||||
return fmt.Errorf("missing %d bytes", w.bytesLeft)
|
|
||||||
}
|
|
||||||
name := utf16.Encode([]rune(hdr.Name))
|
|
||||||
wsi := win32StreamID{
|
|
||||||
StreamID: hdr.Id,
|
|
||||||
Attributes: hdr.Attributes,
|
|
||||||
Size: uint64(hdr.Size),
|
|
||||||
NameSize: uint32(len(name) * 2),
|
|
||||||
}
|
|
||||||
if hdr.Id == BackupSparseBlock {
|
|
||||||
// Include space for the int64 block offset
|
|
||||||
wsi.Size += 8
|
|
||||||
}
|
|
||||||
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(name) != 0 {
|
|
||||||
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hdr.Id == BackupSparseBlock {
|
|
||||||
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.bytesLeft = hdr.Size
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes to the current backup stream.
|
|
||||||
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
|
|
||||||
if w.bytesLeft < int64(len(b)) {
|
|
||||||
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
|
|
||||||
}
|
|
||||||
n, err := w.w.Write(b)
|
|
||||||
w.bytesLeft -= int64(n)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
|
|
||||||
type BackupFileReader struct {
|
|
||||||
f *os.File
|
|
||||||
includeSecurity bool
|
|
||||||
ctx uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
|
|
||||||
// Read will attempt to read the security descriptor of the file.
|
|
||||||
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
|
||||||
r := &BackupFileReader{f, includeSecurity, 0}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
|
||||||
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
|
||||||
var bytesRead uint32
|
|
||||||
err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
|
||||||
if err != nil {
|
|
||||||
return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(r.f)
|
|
||||||
if bytesRead == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
return int(bytesRead), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close frees Win32 resources associated with the BackupFileReader. It does not close
|
|
||||||
// the underlying file.
|
|
||||||
func (r *BackupFileReader) Close() error {
|
|
||||||
if r.ctx != 0 {
|
|
||||||
_ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
|
||||||
runtime.KeepAlive(r.f)
|
|
||||||
r.ctx = 0
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
|
|
||||||
type BackupFileWriter struct {
|
|
||||||
f *os.File
|
|
||||||
includeSecurity bool
|
|
||||||
ctx uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
|
||||||
// Write() will attempt to restore the security descriptor from the stream.
|
|
||||||
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
|
||||||
w := &BackupFileWriter{f, includeSecurity, 0}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write restores a portion of the file using the provided backup stream.
|
|
||||||
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
|
||||||
var bytesWritten uint32
|
|
||||||
err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
|
||||||
if err != nil {
|
|
||||||
return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(w.f)
|
|
||||||
if int(bytesWritten) != len(b) {
|
|
||||||
return int(bytesWritten), errors.New("not all bytes could be written")
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close frees Win32 resources associated with the BackupFileWriter. It does not
|
|
||||||
// close the underlying file.
|
|
||||||
func (w *BackupFileWriter) Close() error {
|
|
||||||
if w.ctx != 0 {
|
|
||||||
_ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
|
||||||
runtime.KeepAlive(w.f)
|
|
||||||
w.ctx = 0
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
|
|
||||||
// or restore privileges have been acquired.
|
|
||||||
//
|
|
||||||
// If the file opened was a directory, it cannot be used with Readdir().
|
|
||||||
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
|
|
||||||
h, err := fs.CreateFile(path,
|
|
||||||
fs.AccessMask(access),
|
|
||||||
fs.FileShareMode(share),
|
|
||||||
nil,
|
|
||||||
fs.FileCreationDisposition(createmode),
|
|
||||||
fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
err = &os.PathError{Op: "open", Path: path, Err: err}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return os.NewFile(uintptr(h), path), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
// This file only exists to allow go get on non-Windows platforms.
|
|
||||||
|
|
||||||
package backuptar
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package backuptar
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Functions copied from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
|
|
||||||
// as we need to manage the LIBARCHIVE.creationtime PAXRecord manually.
|
|
||||||
// Idea taken from containerd which did the same thing.
|
|
||||||
|
|
||||||
// parsePAXTime takes a string of the form %d.%d as described in the PAX
|
|
||||||
// specification. Note that this implementation allows for negative timestamps,
|
|
||||||
// which is allowed for by the PAX specification, but not always portable.
|
|
||||||
func parsePAXTime(s string) (time.Time, error) {
|
|
||||||
const maxNanoSecondDigits = 9
|
|
||||||
|
|
||||||
// Split string into seconds and sub-seconds parts.
|
|
||||||
ss, sn := s, ""
|
|
||||||
if pos := strings.IndexByte(s, '.'); pos >= 0 {
|
|
||||||
ss, sn = s[:pos], s[pos+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the seconds.
|
|
||||||
secs, err := strconv.ParseInt(ss, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, tar.ErrHeader
|
|
||||||
}
|
|
||||||
if len(sn) == 0 {
|
|
||||||
return time.Unix(secs, 0), nil // No sub-second values
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the nanoseconds.
|
|
||||||
if strings.Trim(sn, "0123456789") != "" {
|
|
||||||
return time.Time{}, tar.ErrHeader
|
|
||||||
}
|
|
||||||
if len(sn) < maxNanoSecondDigits {
|
|
||||||
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
|
|
||||||
} else {
|
|
||||||
sn = sn[:maxNanoSecondDigits] // Right truncate
|
|
||||||
}
|
|
||||||
nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
|
|
||||||
if len(ss) > 0 && ss[0] == '-' {
|
|
||||||
return time.Unix(secs, -1*nsecs), nil // Negative correction
|
|
||||||
}
|
|
||||||
return time.Unix(secs, nsecs), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatPAXTime converts ts into a time of the form %d.%d as described in the
|
|
||||||
// PAX specification. This function is capable of negative timestamps.
|
|
||||||
func formatPAXTime(ts time.Time) (s string) {
|
|
||||||
secs, nsecs := ts.Unix(), ts.Nanosecond()
|
|
||||||
if nsecs == 0 {
|
|
||||||
return strconv.FormatInt(secs, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If seconds is negative, then perform correction.
|
|
||||||
sign := ""
|
|
||||||
if secs < 0 {
|
|
||||||
sign = "-" // Remember sign
|
|
||||||
secs = -(secs + 1) // Add a second to secs
|
|
||||||
nsecs = -(nsecs - 1e9) // Take that second away from nsecs
|
|
||||||
}
|
|
||||||
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
|
|
||||||
}
|
|
||||||
|
|
@ -1,508 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package backuptar
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
//nolint:deadcode,varcheck // keep unused constants for potential future use
|
|
||||||
const (
|
|
||||||
cISUID = 0004000 // Set uid
|
|
||||||
cISGID = 0002000 // Set gid
|
|
||||||
cISVTX = 0001000 // Save text (sticky bit)
|
|
||||||
cISDIR = 0040000 // Directory
|
|
||||||
cISFIFO = 0010000 // FIFO
|
|
||||||
cISREG = 0100000 // Regular file
|
|
||||||
cISLNK = 0120000 // Symbolic link
|
|
||||||
cISBLK = 0060000 // Block special file
|
|
||||||
cISCHR = 0020000 // Character special file
|
|
||||||
cISSOCK = 0140000 // Socket
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
hdrFileAttributes = "MSWINDOWS.fileattr"
|
|
||||||
hdrSecurityDescriptor = "MSWINDOWS.sd"
|
|
||||||
hdrRawSecurityDescriptor = "MSWINDOWS.rawsd"
|
|
||||||
hdrMountPoint = "MSWINDOWS.mountpoint"
|
|
||||||
hdrEaPrefix = "MSWINDOWS.xattr."
|
|
||||||
|
|
||||||
hdrCreationTime = "LIBARCHIVE.creationtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// zeroReader is an io.Reader that always returns 0s.
|
|
||||||
type zeroReader struct{}
|
|
||||||
|
|
||||||
func (zeroReader) Read(b []byte) (int, error) {
|
|
||||||
for i := range b {
|
|
||||||
b[i] = 0
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
|
|
||||||
curOffset := int64(0)
|
|
||||||
for {
|
|
||||||
bhdr, err := br.Next()
|
|
||||||
if err == io.EOF { //nolint:errorlint
|
|
||||||
err = io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if bhdr.Id != winio.BackupSparseBlock {
|
|
||||||
return fmt.Errorf("unexpected stream %d", bhdr.Id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't seek backwards, since we have already written that data to the tar.Writer.
|
|
||||||
if bhdr.Offset < curOffset {
|
|
||||||
return fmt.Errorf("cannot seek back from %d to %d", curOffset, bhdr.Offset)
|
|
||||||
}
|
|
||||||
// archive/tar does not support writing sparse files
|
|
||||||
// so just write zeroes to catch up to the current offset.
|
|
||||||
if _, err = io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil {
|
|
||||||
return fmt.Errorf("seek to offset %d: %w", bhdr.Offset, err)
|
|
||||||
}
|
|
||||||
if bhdr.Size == 0 {
|
|
||||||
// A sparse block with size = 0 is used to mark the end of the sparse blocks.
|
|
||||||
break
|
|
||||||
}
|
|
||||||
n, err := io.Copy(t, br)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n != bhdr.Size {
|
|
||||||
return fmt.Errorf("copied %d bytes instead of %d at offset %d", n, bhdr.Size, bhdr.Offset)
|
|
||||||
}
|
|
||||||
curOffset = bhdr.Offset + n
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasicInfoHeader creates a tar header from basic file information.
|
|
||||||
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
|
|
||||||
hdr := &tar.Header{
|
|
||||||
Format: tar.FormatPAX,
|
|
||||||
Name: filepath.ToSlash(name),
|
|
||||||
Size: size,
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
|
|
||||||
ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
|
|
||||||
AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
|
|
||||||
PAXRecords: make(map[string]string),
|
|
||||||
}
|
|
||||||
hdr.PAXRecords[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
|
|
||||||
hdr.PAXRecords[hdrCreationTime] = formatPAXTime(time.Unix(0, fileInfo.CreationTime.Nanoseconds()))
|
|
||||||
|
|
||||||
if (fileInfo.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY) != 0 {
|
|
||||||
hdr.Mode |= cISDIR
|
|
||||||
hdr.Size = 0
|
|
||||||
hdr.Typeflag = tar.TypeDir
|
|
||||||
}
|
|
||||||
return hdr
|
|
||||||
}
|
|
||||||
|
|
||||||
// SecurityDescriptorFromTarHeader reads the SDDL associated with the header of the current file
|
|
||||||
// from the tar header and returns the security descriptor into a byte slice.
|
|
||||||
func SecurityDescriptorFromTarHeader(hdr *tar.Header) ([]byte, error) {
|
|
||||||
if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
|
|
||||||
sd, err := base64.StdEncoding.DecodeString(sdraw)
|
|
||||||
if err != nil {
|
|
||||||
// Not returning sd as-is in the error-case, as base64.DecodeString
|
|
||||||
// may return partially decoded data (not nil or empty slice) in case
|
|
||||||
// of a failure: https://github.com/golang/go/blob/go1.17.7/src/encoding/base64/base64.go#L382-L387
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return sd, nil
|
|
||||||
}
|
|
||||||
// Maintaining old SDDL-based behavior for backward compatibility. All new
|
|
||||||
// tar headers written by this library will have raw binary for the security
|
|
||||||
// descriptor.
|
|
||||||
if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
|
|
||||||
return winio.SddlToSecurityDescriptor(sddl)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtendedAttributesFromTarHeader reads the EAs associated with the header of the
|
|
||||||
// current file from the tar header and returns it as a byte slice.
|
|
||||||
func ExtendedAttributesFromTarHeader(hdr *tar.Header) ([]byte, error) {
|
|
||||||
var eas []winio.ExtendedAttribute //nolint:prealloc // len(eas) <= len(hdr.PAXRecords); prealloc is wasteful
|
|
||||||
for k, v := range hdr.PAXRecords {
|
|
||||||
if !strings.HasPrefix(k, hdrEaPrefix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data, err := base64.StdEncoding.DecodeString(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
eas = append(eas, winio.ExtendedAttribute{
|
|
||||||
Name: k[len(hdrEaPrefix):],
|
|
||||||
Value: data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var eaData []byte
|
|
||||||
var err error
|
|
||||||
if len(eas) != 0 {
|
|
||||||
eaData, err = winio.EncodeExtendedAttributes(eas)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return eaData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeReparsePointFromTarHeader reads the ReparsePoint structure from the tar header
|
|
||||||
// and encodes it into a byte slice. The file for which this function is called must be a
|
|
||||||
// symlink.
|
|
||||||
func EncodeReparsePointFromTarHeader(hdr *tar.Header) []byte {
|
|
||||||
_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
|
|
||||||
rp := winio.ReparsePoint{
|
|
||||||
Target: filepath.FromSlash(hdr.Linkname),
|
|
||||||
IsMountPoint: isMountPoint,
|
|
||||||
}
|
|
||||||
return winio.EncodeReparsePoint(&rp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
|
|
||||||
//
|
|
||||||
// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
|
|
||||||
//
|
|
||||||
// The additional Win32 metadata is:
|
|
||||||
//
|
|
||||||
// - MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
|
|
||||||
// - MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
|
|
||||||
// - MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
|
|
||||||
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
|
|
||||||
name = filepath.ToSlash(name)
|
|
||||||
hdr := BasicInfoHeader(name, size, fileInfo)
|
|
||||||
|
|
||||||
// If r can be seeked, then this function is two-pass: pass 1 collects the
|
|
||||||
// tar header data, and pass 2 copies the data stream. If r cannot be
|
|
||||||
// seeked, then some header data (in particular EAs) will be silently lost.
|
|
||||||
var (
|
|
||||||
restartPos int64
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
sr, readTwice := r.(io.Seeker)
|
|
||||||
if readTwice {
|
|
||||||
if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil {
|
|
||||||
readTwice = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
br := winio.NewBackupStreamReader(r)
|
|
||||||
var dataHdr *winio.BackupHeader
|
|
||||||
for dataHdr == nil {
|
|
||||||
bhdr, err := br.Next()
|
|
||||||
if err == io.EOF { //nolint:errorlint
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch bhdr.Id {
|
|
||||||
case winio.BackupData:
|
|
||||||
hdr.Mode |= cISREG
|
|
||||||
if !readTwice {
|
|
||||||
dataHdr = bhdr
|
|
||||||
}
|
|
||||||
case winio.BackupSecurity:
|
|
||||||
sd, err := io.ReadAll(br)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
|
|
||||||
|
|
||||||
case winio.BackupReparseData:
|
|
||||||
hdr.Mode |= cISLNK
|
|
||||||
hdr.Typeflag = tar.TypeSymlink
|
|
||||||
reparseBuffer, _ := io.ReadAll(br)
|
|
||||||
rp, err := winio.DecodeReparsePoint(reparseBuffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if rp.IsMountPoint {
|
|
||||||
hdr.PAXRecords[hdrMountPoint] = "1"
|
|
||||||
}
|
|
||||||
hdr.Linkname = rp.Target
|
|
||||||
|
|
||||||
case winio.BackupEaData:
|
|
||||||
eab, err := io.ReadAll(br)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
eas, err := winio.DecodeExtendedAttributes(eab)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, ea := range eas {
|
|
||||||
// Use base64 encoding for the binary value. Note that there
|
|
||||||
// is no way to encode the EA's flags, since their use doesn't
|
|
||||||
// make any sense for persisted EAs.
|
|
||||||
hdr.PAXRecords[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
|
||||||
// ignore these streams
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.WriteHeader(hdr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if readTwice {
|
|
||||||
// Get back to the data stream.
|
|
||||||
if _, err = sr.Seek(restartPos, io.SeekStart); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for dataHdr == nil {
|
|
||||||
bhdr, err := br.Next()
|
|
||||||
if err == io.EOF { //nolint:errorlint
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if bhdr.Id == winio.BackupData {
|
|
||||||
dataHdr = bhdr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The logic for copying file contents is fairly complicated due to the need for handling sparse files,
|
|
||||||
// and the weird ways they are represented by BackupRead. A normal file will always either have a data stream
|
|
||||||
// with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also
|
|
||||||
// be represented using a series of sparse block streams following the data stream. Additionally, the way sparse
|
|
||||||
// files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described
|
|
||||||
// in the list at the bottom of this block comment.
|
|
||||||
//
|
|
||||||
// Sparse files can be represented in four different ways, based on the specifics of the file.
|
|
||||||
// - Size = 0:
|
|
||||||
// Previously: BackupRead yields no data stream and no sparse block streams.
|
|
||||||
// Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams.
|
|
||||||
// - Size > 0, no allocated ranges:
|
|
||||||
// BackupRead yields a data stream with size = 0. Following is a single sparse block stream with
|
|
||||||
// size = 0 and offset = <file size>.
|
|
||||||
// - Size > 0, one allocated range:
|
|
||||||
// BackupRead yields a data stream with size = <file size> containing the file contents. There are no
|
|
||||||
// sparse block streams. This is the case if you take a normal file with contents and simply set the
|
|
||||||
// sparse flag on it.
|
|
||||||
// - Size > 0, multiple allocated ranges:
|
|
||||||
// BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated
|
|
||||||
// range of the file containing the range contents. Finally there is a sparse block stream with
|
|
||||||
// size = 0 and offset = <file size>.
|
|
||||||
|
|
||||||
if dataHdr != nil { //nolint:nestif // todo: reduce nesting complexity
|
|
||||||
// A data stream was found. Copy the data.
|
|
||||||
// We assume that we will either have a data stream size > 0 XOR have sparse block streams.
|
|
||||||
if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 {
|
|
||||||
if size != dataHdr.Size {
|
|
||||||
return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
|
|
||||||
}
|
|
||||||
if _, err = io.Copy(t, br); err != nil {
|
|
||||||
return fmt.Errorf("%s: copying contents from data stream: %w", name, err)
|
|
||||||
}
|
|
||||||
} else if size > 0 {
|
|
||||||
// As of a recent OS change, BackupRead now returns a data stream for empty sparse files.
|
|
||||||
// These files have no sparse block streams, so skip the copySparse call if file size = 0.
|
|
||||||
if err = copySparse(t, br); err != nil {
|
|
||||||
return fmt.Errorf("%s: copying contents from sparse block stream: %w", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for streams after the data stream. The only ones we handle are alternate data streams.
|
|
||||||
// Other streams may have metadata that could be serialized, but the tar header has already
|
|
||||||
// been written. In practice, this means that we don't get EA or TXF metadata.
|
|
||||||
for {
|
|
||||||
bhdr, err := br.Next()
|
|
||||||
if err == io.EOF { //nolint:errorlint
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch bhdr.Id {
|
|
||||||
case winio.BackupAlternateData:
|
|
||||||
if (bhdr.Attributes & winio.StreamSparseAttributes) != 0 {
|
|
||||||
// Unsupported for now, since the size of the alternate stream is not present
|
|
||||||
// in the backup stream until after the data has been read.
|
|
||||||
return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name)
|
|
||||||
}
|
|
||||||
altName := strings.TrimSuffix(bhdr.Name, ":$DATA")
|
|
||||||
hdr = &tar.Header{
|
|
||||||
Format: hdr.Format,
|
|
||||||
Name: name + altName,
|
|
||||||
Mode: hdr.Mode,
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Size: bhdr.Size,
|
|
||||||
ModTime: hdr.ModTime,
|
|
||||||
AccessTime: hdr.AccessTime,
|
|
||||||
ChangeTime: hdr.ChangeTime,
|
|
||||||
}
|
|
||||||
err = t.WriteHeader(hdr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(t, br)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
|
||||||
// ignore these streams
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
|
|
||||||
// WriteTarFileFromBackupStream.
|
|
||||||
func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
|
|
||||||
name = hdr.Name
|
|
||||||
if hdr.Typeflag == tar.TypeReg {
|
|
||||||
size = hdr.Size
|
|
||||||
}
|
|
||||||
fileInfo = &winio.FileBasicInfo{
|
|
||||||
LastAccessTime: windows.NsecToFiletime(hdr.AccessTime.UnixNano()),
|
|
||||||
LastWriteTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
|
|
||||||
ChangeTime: windows.NsecToFiletime(hdr.ChangeTime.UnixNano()),
|
|
||||||
// Default to ModTime, we'll pull hdrCreationTime below if present
|
|
||||||
CreationTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
|
|
||||||
}
|
|
||||||
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
|
|
||||||
attr, err := strconv.ParseUint(attrStr, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, nil, err
|
|
||||||
}
|
|
||||||
fileInfo.FileAttributes = uint32(attr)
|
|
||||||
} else {
|
|
||||||
if hdr.Typeflag == tar.TypeDir {
|
|
||||||
fileInfo.FileAttributes |= windows.FILE_ATTRIBUTE_DIRECTORY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if creationTimeStr, ok := hdr.PAXRecords[hdrCreationTime]; ok {
|
|
||||||
creationTime, err := parsePAXTime(creationTimeStr)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, nil, err
|
|
||||||
}
|
|
||||||
fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano())
|
|
||||||
}
|
|
||||||
return name, size, fileInfo, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
|
|
||||||
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
|
|
||||||
// tar file that was not processed, or io.EOF is there are no more.
|
|
||||||
func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
|
|
||||||
bw := winio.NewBackupStreamWriter(w)
|
|
||||||
|
|
||||||
sd, err := SecurityDescriptorFromTarHeader(hdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(sd) != 0 {
|
|
||||||
bhdr := winio.BackupHeader{
|
|
||||||
Id: winio.BackupSecurity,
|
|
||||||
Size: int64(len(sd)),
|
|
||||||
}
|
|
||||||
err := bw.WriteHeader(&bhdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = bw.Write(sd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eadata, err := ExtendedAttributesFromTarHeader(hdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(eadata) != 0 {
|
|
||||||
bhdr := winio.BackupHeader{
|
|
||||||
Id: winio.BackupEaData,
|
|
||||||
Size: int64(len(eadata)),
|
|
||||||
}
|
|
||||||
err = bw.WriteHeader(&bhdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = bw.Write(eadata)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hdr.Typeflag == tar.TypeSymlink {
|
|
||||||
reparse := EncodeReparsePointFromTarHeader(hdr)
|
|
||||||
bhdr := winio.BackupHeader{
|
|
||||||
Id: winio.BackupReparseData,
|
|
||||||
Size: int64(len(reparse)),
|
|
||||||
}
|
|
||||||
err := bw.WriteHeader(&bhdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = bw.Write(reparse)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hdr.Typeflag == tar.TypeReg {
|
|
||||||
bhdr := winio.BackupHeader{
|
|
||||||
Id: winio.BackupData,
|
|
||||||
Size: hdr.Size,
|
|
||||||
}
|
|
||||||
err := bw.WriteHeader(&bhdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(bw, t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Copy all the alternate data streams and return the next non-ADS header.
|
|
||||||
for {
|
|
||||||
ahdr, err := t.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
|
|
||||||
return ahdr, nil
|
|
||||||
}
|
|
||||||
bhdr := winio.BackupHeader{
|
|
||||||
Id: winio.BackupAlternateData,
|
|
||||||
Size: ahdr.Size,
|
|
||||||
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
|
|
||||||
}
|
|
||||||
err = bw.WriteHeader(&bhdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(bw, t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
// This package provides utilities for efficiently performing Win32 IO operations in Go.
|
|
||||||
// Currently, this package is provides support for genreal IO and management of
|
|
||||||
// - named pipes
|
|
||||||
// - files
|
|
||||||
// - [Hyper-V sockets]
|
|
||||||
//
|
|
||||||
// This code is similar to Go's [net] package, and uses IO completion ports to avoid
|
|
||||||
// blocking IO on system threads, allowing Go to reuse the thread to schedule other goroutines.
|
|
||||||
//
|
|
||||||
// This limits support to Windows Vista and newer operating systems.
|
|
||||||
//
|
|
||||||
// Additionally, this package provides support for:
|
|
||||||
// - creating and managing GUIDs
|
|
||||||
// - writing to [ETW]
|
|
||||||
// - opening and manageing VHDs
|
|
||||||
// - parsing [Windows Image files]
|
|
||||||
// - auto-generating Win32 API code
|
|
||||||
//
|
|
||||||
// [Hyper-V sockets]: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service
|
|
||||||
// [ETW]: https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-
|
|
||||||
// [Windows Image files]: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/work-with-windows-images
|
|
||||||
package winio
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fileFullEaInformation struct {
|
|
||||||
NextEntryOffset uint32
|
|
||||||
Flags uint8
|
|
||||||
NameLength uint8
|
|
||||||
ValueLength uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
|
||||||
|
|
||||||
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
|
||||||
errEaNameTooLarge = errors.New("extended attribute name too large")
|
|
||||||
errEaValueTooLarge = errors.New("extended attribute value too large")
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExtendedAttribute represents a single Windows EA.
|
|
||||||
type ExtendedAttribute struct {
|
|
||||||
Name string
|
|
||||||
Value []byte
|
|
||||||
Flags uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
|
||||||
var info fileFullEaInformation
|
|
||||||
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
|
||||||
if err != nil {
|
|
||||||
err = errInvalidEaBuffer
|
|
||||||
return ea, nb, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nameOffset := fileFullEaInformationSize
|
|
||||||
nameLen := int(info.NameLength)
|
|
||||||
valueOffset := nameOffset + int(info.NameLength) + 1
|
|
||||||
valueLen := int(info.ValueLength)
|
|
||||||
nextOffset := int(info.NextEntryOffset)
|
|
||||||
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
|
||||||
err = errInvalidEaBuffer
|
|
||||||
return ea, nb, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
|
||||||
ea.Value = b[valueOffset : valueOffset+valueLen]
|
|
||||||
ea.Flags = info.Flags
|
|
||||||
if info.NextEntryOffset != 0 {
|
|
||||||
nb = b[info.NextEntryOffset:]
|
|
||||||
}
|
|
||||||
return ea, nb, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
|
||||||
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
|
||||||
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
|
||||||
for len(b) != 0 {
|
|
||||||
ea, nb, err := parseEa(b)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
eas = append(eas, ea)
|
|
||||||
b = nb
|
|
||||||
}
|
|
||||||
return eas, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
|
||||||
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
|
||||||
return errEaNameTooLarge
|
|
||||||
}
|
|
||||||
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
|
||||||
return errEaValueTooLarge
|
|
||||||
}
|
|
||||||
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
|
||||||
withPadding := (entrySize + 3) &^ 3
|
|
||||||
nextOffset := uint32(0)
|
|
||||||
if !last {
|
|
||||||
nextOffset = withPadding
|
|
||||||
}
|
|
||||||
info := fileFullEaInformation{
|
|
||||||
NextEntryOffset: nextOffset,
|
|
||||||
Flags: ea.Flags,
|
|
||||||
NameLength: uint8(len(ea.Name)),
|
|
||||||
ValueLength: uint16(len(ea.Value)),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := binary.Write(buf, binary.LittleEndian, &info)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = buf.Write([]byte(ea.Name))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = buf.WriteByte(0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = buf.Write(ea.Value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
|
||||||
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
|
||||||
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
for i := range eas {
|
|
||||||
last := false
|
|
||||||
if i == len(eas)-1 {
|
|
||||||
last = true
|
|
||||||
}
|
|
||||||
|
|
||||||
err := writeEa(&buf, &eas[i], last)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,320 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
//sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx
|
|
||||||
//sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort
|
|
||||||
//sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
|
||||||
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
|
||||||
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrFileClosed = errors.New("file has already been closed")
|
|
||||||
ErrTimeout = &timeoutError{}
|
|
||||||
)
|
|
||||||
|
|
||||||
type timeoutError struct{}
|
|
||||||
|
|
||||||
func (*timeoutError) Error() string { return "i/o timeout" }
|
|
||||||
func (*timeoutError) Timeout() bool { return true }
|
|
||||||
func (*timeoutError) Temporary() bool { return true }
|
|
||||||
|
|
||||||
type timeoutChan chan struct{}
|
|
||||||
|
|
||||||
var ioInitOnce sync.Once
|
|
||||||
var ioCompletionPort windows.Handle
|
|
||||||
|
|
||||||
// ioResult contains the result of an asynchronous IO operation.
|
|
||||||
type ioResult struct {
|
|
||||||
bytes uint32
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ioOperation represents an outstanding asynchronous Win32 IO.
|
|
||||||
type ioOperation struct {
|
|
||||||
o windows.Overlapped
|
|
||||||
ch chan ioResult
|
|
||||||
}
|
|
||||||
|
|
||||||
func initIO() {
|
|
||||||
h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
ioCompletionPort = h
|
|
||||||
go ioCompletionProcessor(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
|
||||||
// It takes ownership of this handle and will close it if it is garbage collected.
|
|
||||||
type win32File struct {
|
|
||||||
handle windows.Handle
|
|
||||||
wg sync.WaitGroup
|
|
||||||
wgLock sync.RWMutex
|
|
||||||
closing atomic.Bool
|
|
||||||
socket bool
|
|
||||||
readDeadline deadlineHandler
|
|
||||||
writeDeadline deadlineHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
type deadlineHandler struct {
|
|
||||||
setLock sync.Mutex
|
|
||||||
channel timeoutChan
|
|
||||||
channelLock sync.RWMutex
|
|
||||||
timer *time.Timer
|
|
||||||
timedout atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeWin32File makes a new win32File from an existing file handle.
|
|
||||||
func makeWin32File(h windows.Handle) (*win32File, error) {
|
|
||||||
f := &win32File{handle: h}
|
|
||||||
ioInitOnce.Do(initIO)
|
|
||||||
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.readDeadline.channel = make(timeoutChan)
|
|
||||||
f.writeDeadline.channel = make(timeoutChan)
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: use NewOpenFile instead.
|
|
||||||
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
|
||||||
return NewOpenFile(windows.Handle(h))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) {
|
|
||||||
// If we return the result of makeWin32File directly, it can result in an
|
|
||||||
// interface-wrapped nil, rather than a nil interface value.
|
|
||||||
f, err := makeWin32File(h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeHandle closes the resources associated with a Win32 handle.
|
|
||||||
func (f *win32File) closeHandle() {
|
|
||||||
f.wgLock.Lock()
|
|
||||||
// Atomically set that we are closing, releasing the resources only once.
|
|
||||||
if !f.closing.Swap(true) {
|
|
||||||
f.wgLock.Unlock()
|
|
||||||
// cancel all IO and wait for it to complete
|
|
||||||
_ = cancelIoEx(f.handle, nil)
|
|
||||||
f.wg.Wait()
|
|
||||||
// at this point, no new IO can start
|
|
||||||
windows.Close(f.handle)
|
|
||||||
f.handle = 0
|
|
||||||
} else {
|
|
||||||
f.wgLock.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes a win32File.
|
|
||||||
func (f *win32File) Close() error {
|
|
||||||
f.closeHandle()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsClosed checks if the file has been closed.
|
|
||||||
func (f *win32File) IsClosed() bool {
|
|
||||||
return f.closing.Load()
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareIO prepares for a new IO operation.
|
|
||||||
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
|
||||||
func (f *win32File) prepareIO() (*ioOperation, error) {
|
|
||||||
f.wgLock.RLock()
|
|
||||||
if f.closing.Load() {
|
|
||||||
f.wgLock.RUnlock()
|
|
||||||
return nil, ErrFileClosed
|
|
||||||
}
|
|
||||||
f.wg.Add(1)
|
|
||||||
f.wgLock.RUnlock()
|
|
||||||
c := &ioOperation{}
|
|
||||||
c.ch = make(chan ioResult)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ioCompletionProcessor processes completed async IOs forever.
|
|
||||||
func ioCompletionProcessor(h windows.Handle) {
|
|
||||||
for {
|
|
||||||
var bytes uint32
|
|
||||||
var key uintptr
|
|
||||||
var op *ioOperation
|
|
||||||
err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE)
|
|
||||||
if op == nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
op.ch <- ioResult{bytes, err}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: helsaawy - create an asyncIO version that takes a context
|
|
||||||
|
|
||||||
// asyncIO processes the return value from ReadFile or WriteFile, blocking until
|
|
||||||
// the operation has actually completed.
|
|
||||||
func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
|
|
||||||
if err != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
|
|
||||||
return int(bytes), err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.closing.Load() {
|
|
||||||
_ = cancelIoEx(f.handle, &c.o)
|
|
||||||
}
|
|
||||||
|
|
||||||
var timeout timeoutChan
|
|
||||||
if d != nil {
|
|
||||||
d.channelLock.Lock()
|
|
||||||
timeout = d.channel
|
|
||||||
d.channelLock.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
var r ioResult
|
|
||||||
select {
|
|
||||||
case r = <-c.ch:
|
|
||||||
err = r.err
|
|
||||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
|
||||||
if f.closing.Load() {
|
|
||||||
err = ErrFileClosed
|
|
||||||
}
|
|
||||||
} else if err != nil && f.socket {
|
|
||||||
// err is from Win32. Query the overlapped structure to get the winsock error.
|
|
||||||
var bytes, flags uint32
|
|
||||||
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
|
|
||||||
}
|
|
||||||
case <-timeout:
|
|
||||||
_ = cancelIoEx(f.handle, &c.o)
|
|
||||||
r = <-c.ch
|
|
||||||
err = r.err
|
|
||||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
|
||||||
err = ErrTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// runtime.KeepAlive is needed, as c is passed via native
|
|
||||||
// code to ioCompletionProcessor, c must remain alive
|
|
||||||
// until the channel read is complete.
|
|
||||||
// todo: (de)allocate *ioOperation via win32 heap functions, instead of needing to KeepAlive?
|
|
||||||
runtime.KeepAlive(c)
|
|
||||||
return int(r.bytes), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads from a file handle.
|
|
||||||
func (f *win32File) Read(b []byte) (int, error) {
|
|
||||||
c, err := f.prepareIO()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer f.wg.Done()
|
|
||||||
|
|
||||||
if f.readDeadline.timedout.Load() {
|
|
||||||
return 0, ErrTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes uint32
|
|
||||||
err = windows.ReadFile(f.handle, b, &bytes, &c.o)
|
|
||||||
n, err := f.asyncIO(c, &f.readDeadline, bytes, err)
|
|
||||||
runtime.KeepAlive(b)
|
|
||||||
|
|
||||||
// Handle EOF conditions.
|
|
||||||
if err == nil && n == 0 && len(b) != 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
} else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes to a file handle.
|
|
||||||
func (f *win32File) Write(b []byte) (int, error) {
|
|
||||||
c, err := f.prepareIO()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer f.wg.Done()
|
|
||||||
|
|
||||||
if f.writeDeadline.timedout.Load() {
|
|
||||||
return 0, ErrTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes uint32
|
|
||||||
err = windows.WriteFile(f.handle, b, &bytes, &c.o)
|
|
||||||
n, err := f.asyncIO(c, &f.writeDeadline, bytes, err)
|
|
||||||
runtime.KeepAlive(b)
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *win32File) SetReadDeadline(deadline time.Time) error {
|
|
||||||
return f.readDeadline.set(deadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
|
|
||||||
return f.writeDeadline.set(deadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *win32File) Flush() error {
|
|
||||||
return windows.FlushFileBuffers(f.handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *win32File) Fd() uintptr {
|
|
||||||
return uintptr(f.handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *deadlineHandler) set(deadline time.Time) error {
|
|
||||||
d.setLock.Lock()
|
|
||||||
defer d.setLock.Unlock()
|
|
||||||
|
|
||||||
if d.timer != nil {
|
|
||||||
if !d.timer.Stop() {
|
|
||||||
<-d.channel
|
|
||||||
}
|
|
||||||
d.timer = nil
|
|
||||||
}
|
|
||||||
d.timedout.Store(false)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-d.channel:
|
|
||||||
d.channelLock.Lock()
|
|
||||||
d.channel = make(chan struct{})
|
|
||||||
d.channelLock.Unlock()
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
if deadline.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
timeoutIO := func() {
|
|
||||||
d.timedout.Store(true)
|
|
||||||
close(d.channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
duration := deadline.Sub(now)
|
|
||||||
if deadline.After(now) {
|
|
||||||
// Deadline is in the future, set a timer to wait
|
|
||||||
d.timer = time.AfterFunc(duration, timeoutIO)
|
|
||||||
} else {
|
|
||||||
// Deadline is in the past. Cancel all pending IO now.
|
|
||||||
timeoutIO()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileBasicInfo contains file access time and file attributes information.
|
|
||||||
type FileBasicInfo struct {
|
|
||||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
|
|
||||||
FileAttributes uint32
|
|
||||||
_ uint32 // padding
|
|
||||||
}
|
|
||||||
|
|
||||||
// alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing
|
|
||||||
// uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64
|
|
||||||
// alignment is necessary to pass this as FILE_BASIC_INFO.
|
|
||||||
type alignedFileBasicInfo struct {
|
|
||||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64
|
|
||||||
FileAttributes uint32
|
|
||||||
_ uint32 // padding
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileBasicInfo retrieves times and attributes for a file.
|
|
||||||
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
|
||||||
bi := &alignedFileBasicInfo{}
|
|
||||||
if err := windows.GetFileInformationByHandleEx(
|
|
||||||
windows.Handle(f.Fd()),
|
|
||||||
windows.FileBasicInfo,
|
|
||||||
(*byte)(unsafe.Pointer(bi)),
|
|
||||||
uint32(unsafe.Sizeof(*bi)),
|
|
||||||
); err != nil {
|
|
||||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(f)
|
|
||||||
// Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the
|
|
||||||
// public API of this module. The data may be unnecessarily aligned.
|
|
||||||
return (*FileBasicInfo)(unsafe.Pointer(bi)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFileBasicInfo sets times and attributes for a file.
|
|
||||||
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
|
||||||
// Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is
|
|
||||||
// suitable to pass to GetFileInformationByHandleEx.
|
|
||||||
biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi))
|
|
||||||
if err := windows.SetFileInformationByHandle(
|
|
||||||
windows.Handle(f.Fd()),
|
|
||||||
windows.FileBasicInfo,
|
|
||||||
(*byte)(unsafe.Pointer(&biAligned)),
|
|
||||||
uint32(unsafe.Sizeof(biAligned)),
|
|
||||||
); err != nil {
|
|
||||||
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(f)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileStandardInfo contains extended information for the file.
|
|
||||||
// FILE_STANDARD_INFO in WinBase.h
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
|
|
||||||
type FileStandardInfo struct {
|
|
||||||
AllocationSize, EndOfFile int64
|
|
||||||
NumberOfLinks uint32
|
|
||||||
DeletePending, Directory bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileStandardInfo retrieves ended information for the file.
|
|
||||||
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
|
|
||||||
si := &FileStandardInfo{}
|
|
||||||
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()),
|
|
||||||
windows.FileStandardInfo,
|
|
||||||
(*byte)(unsafe.Pointer(si)),
|
|
||||||
uint32(unsafe.Sizeof(*si))); err != nil {
|
|
||||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(f)
|
|
||||||
return si, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
|
|
||||||
// unique on a system.
|
|
||||||
type FileIDInfo struct {
|
|
||||||
VolumeSerialNumber uint64
|
|
||||||
FileID [16]byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
|
||||||
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
|
||||||
fileID := &FileIDInfo{}
|
|
||||||
if err := windows.GetFileInformationByHandleEx(
|
|
||||||
windows.Handle(f.Fd()),
|
|
||||||
windows.FileIdInfo,
|
|
||||||
(*byte)(unsafe.Pointer(fileID)),
|
|
||||||
uint32(unsafe.Sizeof(*fileID)),
|
|
||||||
); err != nil {
|
|
||||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(f)
|
|
||||||
return fileID, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,582 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/internal/socket"
|
|
||||||
"github.com/Microsoft/go-winio/pkg/guid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const afHVSock = 34 // AF_HYPERV
|
|
||||||
|
|
||||||
// Well known Service and VM IDs
|
|
||||||
// https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
|
|
||||||
|
|
||||||
// HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions.
|
|
||||||
func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
|
|
||||||
return guid.GUID{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions.
|
|
||||||
func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff
|
|
||||||
return guid.GUID{
|
|
||||||
Data1: 0xffffffff,
|
|
||||||
Data2: 0xffff,
|
|
||||||
Data3: 0xffff,
|
|
||||||
Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector.
|
|
||||||
func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838
|
|
||||||
return guid.GUID{
|
|
||||||
Data1: 0xe0e16197,
|
|
||||||
Data2: 0xdd56,
|
|
||||||
Data3: 0x4a10,
|
|
||||||
Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HvsockGUIDSiloHost is the address of a silo's host partition:
|
|
||||||
// - The silo host of a hosted silo is the utility VM.
|
|
||||||
// - The silo host of a silo on a physical host is the physical host.
|
|
||||||
func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568
|
|
||||||
return guid.GUID{
|
|
||||||
Data1: 0x36bd0c5c,
|
|
||||||
Data2: 0x7276,
|
|
||||||
Data3: 0x4223,
|
|
||||||
Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions.
|
|
||||||
func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd
|
|
||||||
return guid.GUID{
|
|
||||||
Data1: 0x90db8b89,
|
|
||||||
Data2: 0xd35,
|
|
||||||
Data3: 0x4f79,
|
|
||||||
Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition.
|
|
||||||
// Listening on this VmId accepts connection from:
|
|
||||||
// - Inside silos: silo host partition.
|
|
||||||
// - Inside hosted silo: host of the VM.
|
|
||||||
// - Inside VM: VM host.
|
|
||||||
// - Physical host: Not supported.
|
|
||||||
func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878
|
|
||||||
return guid.GUID{
|
|
||||||
Data1: 0xa42e7cda,
|
|
||||||
Data2: 0xd03f,
|
|
||||||
Data3: 0x480c,
|
|
||||||
Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol.
|
|
||||||
func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3
|
|
||||||
return guid.GUID{
|
|
||||||
Data2: 0xfacb,
|
|
||||||
Data3: 0x11e6,
|
|
||||||
Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An HvsockAddr is an address for a AF_HYPERV socket.
|
|
||||||
type HvsockAddr struct {
|
|
||||||
VMID guid.GUID
|
|
||||||
ServiceID guid.GUID
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawHvsockAddr struct {
|
|
||||||
Family uint16
|
|
||||||
_ uint16
|
|
||||||
VMID guid.GUID
|
|
||||||
ServiceID guid.GUID
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ socket.RawSockaddr = &rawHvsockAddr{}
|
|
||||||
|
|
||||||
// Network returns the address's network name, "hvsock".
|
|
||||||
func (*HvsockAddr) Network() string {
|
|
||||||
return "hvsock"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (addr *HvsockAddr) String() string {
|
|
||||||
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
|
||||||
func VsockServiceID(port uint32) guid.GUID {
|
|
||||||
g := hvsockVsockServiceTemplate() // make a copy
|
|
||||||
g.Data1 = port
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
|
||||||
return rawHvsockAddr{
|
|
||||||
Family: afHVSock,
|
|
||||||
VMID: addr.VMID,
|
|
||||||
ServiceID: addr.ServiceID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
|
|
||||||
addr.VMID = raw.VMID
|
|
||||||
addr.ServiceID = raw.ServiceID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sockaddr returns a pointer to and the size of this struct.
|
|
||||||
//
|
|
||||||
// Implements the [socket.RawSockaddr] interface, and allows use in
|
|
||||||
// [socket.Bind] and [socket.ConnectEx].
|
|
||||||
func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) {
|
|
||||||
return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`.
|
|
||||||
func (r *rawHvsockAddr) FromBytes(b []byte) error {
|
|
||||||
n := int(unsafe.Sizeof(rawHvsockAddr{}))
|
|
||||||
|
|
||||||
if len(b) < n {
|
|
||||||
return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n])
|
|
||||||
if r.Family != afHVSock {
|
|
||||||
return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
|
||||||
type HvsockListener struct {
|
|
||||||
sock *win32File
|
|
||||||
addr HvsockAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Listener = &HvsockListener{}
|
|
||||||
|
|
||||||
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
|
||||||
type HvsockConn struct {
|
|
||||||
sock *win32File
|
|
||||||
local, remote HvsockAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Conn = &HvsockConn{}
|
|
||||||
|
|
||||||
func newHVSocket() (*win32File, error) {
|
|
||||||
fd, err := windows.Socket(afHVSock, windows.SOCK_STREAM, 1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.NewSyscallError("socket", err)
|
|
||||||
}
|
|
||||||
f, err := makeWin32File(fd)
|
|
||||||
if err != nil {
|
|
||||||
windows.Close(fd)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.socket = true
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenHvsock listens for connections on the specified hvsock address.
|
|
||||||
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
|
|
||||||
l := &HvsockListener{addr: *addr}
|
|
||||||
|
|
||||||
var sock *win32File
|
|
||||||
sock, err = newHVSocket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, l.opErr("listen", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = sock.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sa := addr.raw()
|
|
||||||
err = socket.Bind(sock.handle, &sa)
|
|
||||||
if err != nil {
|
|
||||||
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
|
||||||
}
|
|
||||||
err = windows.Listen(sock.handle, 16)
|
|
||||||
if err != nil {
|
|
||||||
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
|
|
||||||
}
|
|
||||||
return &HvsockListener{sock: sock, addr: *addr}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *HvsockListener) opErr(op string, err error) error {
|
|
||||||
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns the listener's network address.
|
|
||||||
func (l *HvsockListener) Addr() net.Addr {
|
|
||||||
return &l.addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept waits for the next connection and returns it.
|
|
||||||
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
|
||||||
sock, err := newHVSocket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, l.opErr("accept", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if sock != nil {
|
|
||||||
sock.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
c, err := l.sock.prepareIO()
|
|
||||||
if err != nil {
|
|
||||||
return nil, l.opErr("accept", err)
|
|
||||||
}
|
|
||||||
defer l.sock.wg.Done()
|
|
||||||
|
|
||||||
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
|
||||||
//
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex
|
|
||||||
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
|
||||||
var addrbuf [addrlen * 2]byte
|
|
||||||
|
|
||||||
var bytes uint32
|
|
||||||
err = windows.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o)
|
|
||||||
if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil {
|
|
||||||
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := &HvsockConn{
|
|
||||||
sock: sock,
|
|
||||||
}
|
|
||||||
// The local address returned in the AcceptEx buffer is the same as the Listener socket's
|
|
||||||
// address. However, the service GUID reported by GetSockName is different from the Listeners
|
|
||||||
// socket, and is sometimes the same as the local address of the socket that dialed the
|
|
||||||
// address, with the service GUID.Data1 incremented, but othertimes is different.
|
|
||||||
// todo: does the local address matter? is the listener's address or the actual address appropriate?
|
|
||||||
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
|
||||||
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
|
||||||
|
|
||||||
// initialize the accepted socket and update its properties with those of the listening socket
|
|
||||||
if err = windows.Setsockopt(sock.handle,
|
|
||||||
windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT,
|
|
||||||
(*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil {
|
|
||||||
return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
sock = nil
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the listener, causing any pending Accept calls to fail.
|
|
||||||
func (l *HvsockListener) Close() error {
|
|
||||||
return l.sock.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]).
|
|
||||||
type HvsockDialer struct {
|
|
||||||
// Deadline is the time the Dial operation must connect before erroring.
|
|
||||||
Deadline time.Time
|
|
||||||
|
|
||||||
// Retries is the number of additional connects to try if the connection times out, is refused,
|
|
||||||
// or the host is unreachable
|
|
||||||
Retries uint
|
|
||||||
|
|
||||||
// RetryWait is the time to wait after a connection error to retry
|
|
||||||
RetryWait time.Duration
|
|
||||||
|
|
||||||
rt *time.Timer // redial wait timer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial the Hyper-V socket at addr.
|
|
||||||
//
|
|
||||||
// See [HvsockDialer.Dial] for more information.
|
|
||||||
func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
|
||||||
return (&HvsockDialer{}).Dial(ctx, addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful.
|
|
||||||
// Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between
|
|
||||||
// retries.
|
|
||||||
//
|
|
||||||
// Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx.
|
|
||||||
func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
|
||||||
op := "dial"
|
|
||||||
// create the conn early to use opErr()
|
|
||||||
conn = &HvsockConn{
|
|
||||||
remote: *addr,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !d.Deadline.IsZero() {
|
|
||||||
var cancel context.CancelFunc
|
|
||||||
ctx, cancel = context.WithDeadline(ctx, d.Deadline)
|
|
||||||
defer cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// preemptive timeout/cancellation check
|
|
||||||
if err = ctx.Err(); err != nil {
|
|
||||||
return nil, conn.opErr(op, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sock, err := newHVSocket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, conn.opErr(op, err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if sock != nil {
|
|
||||||
sock.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sa := addr.raw()
|
|
||||||
err = socket.Bind(sock.handle, &sa)
|
|
||||||
if err != nil {
|
|
||||||
return nil, conn.opErr(op, os.NewSyscallError("bind", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := sock.prepareIO()
|
|
||||||
if err != nil {
|
|
||||||
return nil, conn.opErr(op, err)
|
|
||||||
}
|
|
||||||
defer sock.wg.Done()
|
|
||||||
var bytes uint32
|
|
||||||
for i := uint(0); i <= d.Retries; i++ {
|
|
||||||
err = socket.ConnectEx(
|
|
||||||
sock.handle,
|
|
||||||
&sa,
|
|
||||||
nil, // sendBuf
|
|
||||||
0, // sendDataLen
|
|
||||||
&bytes,
|
|
||||||
(*windows.Overlapped)(unsafe.Pointer(&c.o)))
|
|
||||||
_, err = sock.asyncIO(c, nil, bytes, err)
|
|
||||||
if i < d.Retries && canRedial(err) {
|
|
||||||
if err = d.redialWait(ctx); err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, conn.opErr(op, os.NewSyscallError("connectex", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the connection properties, so shutdown can be used
|
|
||||||
if err = windows.Setsockopt(
|
|
||||||
sock.handle,
|
|
||||||
windows.SOL_SOCKET,
|
|
||||||
windows.SO_UPDATE_CONNECT_CONTEXT,
|
|
||||||
nil, // optvalue
|
|
||||||
0, // optlen
|
|
||||||
); err != nil {
|
|
||||||
return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the local name
|
|
||||||
var sal rawHvsockAddr
|
|
||||||
err = socket.GetSockName(sock.handle, &sal)
|
|
||||||
if err != nil {
|
|
||||||
return nil, conn.opErr(op, os.NewSyscallError("getsockname", err))
|
|
||||||
}
|
|
||||||
conn.local.fromRaw(&sal)
|
|
||||||
|
|
||||||
// one last check for timeout, since asyncIO doesn't check the context
|
|
||||||
if err = ctx.Err(); err != nil {
|
|
||||||
return nil, conn.opErr(op, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.sock = sock
|
|
||||||
sock = nil
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// redialWait waits before attempting to redial, resetting the timer as appropriate.
|
|
||||||
func (d *HvsockDialer) redialWait(ctx context.Context) (err error) {
|
|
||||||
if d.RetryWait == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.rt == nil {
|
|
||||||
d.rt = time.NewTimer(d.RetryWait)
|
|
||||||
} else {
|
|
||||||
// should already be stopped and drained
|
|
||||||
d.rt.Reset(d.RetryWait)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
case <-d.rt.C:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stop and drain the timer
|
|
||||||
if !d.rt.Stop() {
|
|
||||||
<-d.rt.C
|
|
||||||
}
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// assumes error is a plain, unwrapped windows.Errno provided by direct syscall.
|
|
||||||
func canRedial(err error) bool {
|
|
||||||
//nolint:errorlint // guaranteed to be an Errno
|
|
||||||
switch err {
|
|
||||||
case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT,
|
|
||||||
windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *HvsockConn) opErr(op string, err error) error {
|
|
||||||
// translate from "file closed" to "socket closed"
|
|
||||||
if errors.Is(err, ErrFileClosed) {
|
|
||||||
err = socket.ErrSocketClosed
|
|
||||||
}
|
|
||||||
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *HvsockConn) Read(b []byte) (int, error) {
|
|
||||||
c, err := conn.sock.prepareIO()
|
|
||||||
if err != nil {
|
|
||||||
return 0, conn.opErr("read", err)
|
|
||||||
}
|
|
||||||
defer conn.sock.wg.Done()
|
|
||||||
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
|
||||||
var flags, bytes uint32
|
|
||||||
err = windows.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
|
|
||||||
n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err)
|
|
||||||
if err != nil {
|
|
||||||
var eno windows.Errno
|
|
||||||
if errors.As(err, &eno) {
|
|
||||||
err = os.NewSyscallError("wsarecv", eno)
|
|
||||||
}
|
|
||||||
return 0, conn.opErr("read", err)
|
|
||||||
} else if n == 0 {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *HvsockConn) Write(b []byte) (int, error) {
|
|
||||||
t := 0
|
|
||||||
for len(b) != 0 {
|
|
||||||
n, err := conn.write(b)
|
|
||||||
if err != nil {
|
|
||||||
return t + n, err
|
|
||||||
}
|
|
||||||
t += n
|
|
||||||
b = b[n:]
|
|
||||||
}
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *HvsockConn) write(b []byte) (int, error) {
|
|
||||||
c, err := conn.sock.prepareIO()
|
|
||||||
if err != nil {
|
|
||||||
return 0, conn.opErr("write", err)
|
|
||||||
}
|
|
||||||
defer conn.sock.wg.Done()
|
|
||||||
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
|
||||||
var bytes uint32
|
|
||||||
err = windows.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
|
|
||||||
n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err)
|
|
||||||
if err != nil {
|
|
||||||
var eno windows.Errno
|
|
||||||
if errors.As(err, &eno) {
|
|
||||||
err = os.NewSyscallError("wsasend", eno)
|
|
||||||
}
|
|
||||||
return 0, conn.opErr("write", err)
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the socket connection, failing any pending read or write calls.
|
|
||||||
func (conn *HvsockConn) Close() error {
|
|
||||||
return conn.sock.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (conn *HvsockConn) IsClosed() bool {
|
|
||||||
return conn.sock.IsClosed()
|
|
||||||
}
|
|
||||||
|
|
||||||
// shutdown disables sending or receiving on a socket.
|
|
||||||
func (conn *HvsockConn) shutdown(how int) error {
|
|
||||||
if conn.IsClosed() {
|
|
||||||
return socket.ErrSocketClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
err := windows.Shutdown(conn.sock.handle, how)
|
|
||||||
if err != nil {
|
|
||||||
// If the connection was closed, shutdowns fail with "not connected"
|
|
||||||
if errors.Is(err, windows.WSAENOTCONN) ||
|
|
||||||
errors.Is(err, windows.WSAESHUTDOWN) {
|
|
||||||
err = socket.ErrSocketClosed
|
|
||||||
}
|
|
||||||
return os.NewSyscallError("shutdown", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseRead shuts down the read end of the socket, preventing future read operations.
|
|
||||||
func (conn *HvsockConn) CloseRead() error {
|
|
||||||
err := conn.shutdown(windows.SHUT_RD)
|
|
||||||
if err != nil {
|
|
||||||
return conn.opErr("closeread", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseWrite shuts down the write end of the socket, preventing future write operations and
|
|
||||||
// notifying the other endpoint that no more data will be written.
|
|
||||||
func (conn *HvsockConn) CloseWrite() error {
|
|
||||||
err := conn.shutdown(windows.SHUT_WR)
|
|
||||||
if err != nil {
|
|
||||||
return conn.opErr("closewrite", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAddr returns the local address of the connection.
|
|
||||||
func (conn *HvsockConn) LocalAddr() net.Addr {
|
|
||||||
return &conn.local
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteAddr returns the remote address of the connection.
|
|
||||||
func (conn *HvsockConn) RemoteAddr() net.Addr {
|
|
||||||
return &conn.remote
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline implements the net.Conn SetDeadline method.
|
|
||||||
func (conn *HvsockConn) SetDeadline(t time.Time) error {
|
|
||||||
// todo: implement `SetDeadline` for `win32File`
|
|
||||||
if err := conn.SetReadDeadline(t); err != nil {
|
|
||||||
return fmt.Errorf("set read deadline: %w", err)
|
|
||||||
}
|
|
||||||
if err := conn.SetWriteDeadline(t); err != nil {
|
|
||||||
return fmt.Errorf("set write deadline: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
|
||||||
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
|
|
||||||
return conn.sock.SetReadDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
|
||||||
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return conn.sock.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// This package contains Win32 filesystem functionality.
|
|
||||||
package fs
|
|
||||||
|
|
@ -1,262 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package fs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/internal/stringbuffer"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go
|
|
||||||
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
|
||||||
//sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW
|
|
||||||
|
|
||||||
const NullHandle windows.Handle = 0
|
|
||||||
|
|
||||||
// AccessMask defines standard, specific, and generic rights.
|
|
||||||
//
|
|
||||||
// Used with CreateFile and NtCreateFile (and co.).
|
|
||||||
//
|
|
||||||
// Bitmask:
|
|
||||||
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
|
|
||||||
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|
|
||||||
// +---------------+---------------+-------------------------------+
|
|
||||||
// |G|G|G|G|Resvd|A| StandardRights| SpecificRights |
|
|
||||||
// |R|W|E|A| |S| | |
|
|
||||||
// +-+-------------+---------------+-------------------------------+
|
|
||||||
//
|
|
||||||
// GR Generic Read
|
|
||||||
// GW Generic Write
|
|
||||||
// GE Generic Exectue
|
|
||||||
// GA Generic All
|
|
||||||
// Resvd Reserved
|
|
||||||
// AS Access Security System
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants
|
|
||||||
type AccessMask = windows.ACCESS_MASK
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
// Not actually any.
|
|
||||||
//
|
|
||||||
// For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device"
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters
|
|
||||||
FILE_ANY_ACCESS AccessMask = 0
|
|
||||||
|
|
||||||
GENERIC_READ AccessMask = 0x8000_0000
|
|
||||||
GENERIC_WRITE AccessMask = 0x4000_0000
|
|
||||||
GENERIC_EXECUTE AccessMask = 0x2000_0000
|
|
||||||
GENERIC_ALL AccessMask = 0x1000_0000
|
|
||||||
ACCESS_SYSTEM_SECURITY AccessMask = 0x0100_0000
|
|
||||||
|
|
||||||
// Specific Object Access
|
|
||||||
// from ntioapi.h
|
|
||||||
|
|
||||||
FILE_READ_DATA AccessMask = (0x0001) // file & pipe
|
|
||||||
FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory
|
|
||||||
|
|
||||||
FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe
|
|
||||||
FILE_ADD_FILE AccessMask = (0x0002) // directory
|
|
||||||
|
|
||||||
FILE_APPEND_DATA AccessMask = (0x0004) // file
|
|
||||||
FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory
|
|
||||||
FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe
|
|
||||||
|
|
||||||
FILE_READ_EA AccessMask = (0x0008) // file & directory
|
|
||||||
FILE_READ_PROPERTIES AccessMask = FILE_READ_EA
|
|
||||||
|
|
||||||
FILE_WRITE_EA AccessMask = (0x0010) // file & directory
|
|
||||||
FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA
|
|
||||||
|
|
||||||
FILE_EXECUTE AccessMask = (0x0020) // file
|
|
||||||
FILE_TRAVERSE AccessMask = (0x0020) // directory
|
|
||||||
|
|
||||||
FILE_DELETE_CHILD AccessMask = (0x0040) // directory
|
|
||||||
|
|
||||||
FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all
|
|
||||||
|
|
||||||
FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all
|
|
||||||
|
|
||||||
FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
|
|
||||||
FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE)
|
|
||||||
FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE)
|
|
||||||
FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE)
|
|
||||||
|
|
||||||
SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF
|
|
||||||
|
|
||||||
// Standard Access
|
|
||||||
// from ntseapi.h
|
|
||||||
|
|
||||||
DELETE AccessMask = 0x0001_0000
|
|
||||||
READ_CONTROL AccessMask = 0x0002_0000
|
|
||||||
WRITE_DAC AccessMask = 0x0004_0000
|
|
||||||
WRITE_OWNER AccessMask = 0x0008_0000
|
|
||||||
SYNCHRONIZE AccessMask = 0x0010_0000
|
|
||||||
|
|
||||||
STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000
|
|
||||||
|
|
||||||
STANDARD_RIGHTS_READ AccessMask = READ_CONTROL
|
|
||||||
STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL
|
|
||||||
STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL
|
|
||||||
|
|
||||||
STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileShareMode uint32
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
FILE_SHARE_NONE FileShareMode = 0x00
|
|
||||||
FILE_SHARE_READ FileShareMode = 0x01
|
|
||||||
FILE_SHARE_WRITE FileShareMode = 0x02
|
|
||||||
FILE_SHARE_DELETE FileShareMode = 0x04
|
|
||||||
FILE_SHARE_VALID_FLAGS FileShareMode = 0x07
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileCreationDisposition uint32
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
// from winbase.h
|
|
||||||
|
|
||||||
CREATE_NEW FileCreationDisposition = 0x01
|
|
||||||
CREATE_ALWAYS FileCreationDisposition = 0x02
|
|
||||||
OPEN_EXISTING FileCreationDisposition = 0x03
|
|
||||||
OPEN_ALWAYS FileCreationDisposition = 0x04
|
|
||||||
TRUNCATE_EXISTING FileCreationDisposition = 0x05
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create disposition values for NtCreate*
|
|
||||||
type NTFileCreationDisposition uint32
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
// From ntioapi.h
|
|
||||||
|
|
||||||
FILE_SUPERSEDE NTFileCreationDisposition = 0x00
|
|
||||||
FILE_OPEN NTFileCreationDisposition = 0x01
|
|
||||||
FILE_CREATE NTFileCreationDisposition = 0x02
|
|
||||||
FILE_OPEN_IF NTFileCreationDisposition = 0x03
|
|
||||||
FILE_OVERWRITE NTFileCreationDisposition = 0x04
|
|
||||||
FILE_OVERWRITE_IF NTFileCreationDisposition = 0x05
|
|
||||||
FILE_MAXIMUM_DISPOSITION NTFileCreationDisposition = 0x05
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateFile and co. take flags or attributes together as one parameter.
|
|
||||||
// Define alias until we can use generics to allow both
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
|
||||||
type FileFlagOrAttribute uint32
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
// from winnt.h
|
|
||||||
|
|
||||||
FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000
|
|
||||||
FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000
|
|
||||||
FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000
|
|
||||||
FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000
|
|
||||||
FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000
|
|
||||||
FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000
|
|
||||||
FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000
|
|
||||||
FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000
|
|
||||||
FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000
|
|
||||||
FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000
|
|
||||||
FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000
|
|
||||||
)
|
|
||||||
|
|
||||||
// NtCreate* functions take a dedicated CreateOptions parameter.
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/Winternl/nf-winternl-ntcreatefile
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/devnotes/nt-create-named-pipe-file
|
|
||||||
type NTCreateOptions uint32
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
// From ntioapi.h
|
|
||||||
|
|
||||||
FILE_DIRECTORY_FILE NTCreateOptions = 0x0000_0001
|
|
||||||
FILE_WRITE_THROUGH NTCreateOptions = 0x0000_0002
|
|
||||||
FILE_SEQUENTIAL_ONLY NTCreateOptions = 0x0000_0004
|
|
||||||
FILE_NO_INTERMEDIATE_BUFFERING NTCreateOptions = 0x0000_0008
|
|
||||||
|
|
||||||
FILE_SYNCHRONOUS_IO_ALERT NTCreateOptions = 0x0000_0010
|
|
||||||
FILE_SYNCHRONOUS_IO_NONALERT NTCreateOptions = 0x0000_0020
|
|
||||||
FILE_NON_DIRECTORY_FILE NTCreateOptions = 0x0000_0040
|
|
||||||
FILE_CREATE_TREE_CONNECTION NTCreateOptions = 0x0000_0080
|
|
||||||
|
|
||||||
FILE_COMPLETE_IF_OPLOCKED NTCreateOptions = 0x0000_0100
|
|
||||||
FILE_NO_EA_KNOWLEDGE NTCreateOptions = 0x0000_0200
|
|
||||||
FILE_DISABLE_TUNNELING NTCreateOptions = 0x0000_0400
|
|
||||||
FILE_RANDOM_ACCESS NTCreateOptions = 0x0000_0800
|
|
||||||
|
|
||||||
FILE_DELETE_ON_CLOSE NTCreateOptions = 0x0000_1000
|
|
||||||
FILE_OPEN_BY_FILE_ID NTCreateOptions = 0x0000_2000
|
|
||||||
FILE_OPEN_FOR_BACKUP_INTENT NTCreateOptions = 0x0000_4000
|
|
||||||
FILE_NO_COMPRESSION NTCreateOptions = 0x0000_8000
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileSQSFlag = FileFlagOrAttribute
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
// from winbase.h
|
|
||||||
|
|
||||||
SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16)
|
|
||||||
SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16)
|
|
||||||
SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16)
|
|
||||||
SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16)
|
|
||||||
|
|
||||||
SECURITY_SQOS_PRESENT FileSQSFlag = 0x0010_0000
|
|
||||||
SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F_0000
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetFinalPathNameByHandle flags
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters
|
|
||||||
type GetFinalPathFlag uint32
|
|
||||||
|
|
||||||
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
|
||||||
const (
|
|
||||||
GetFinalPathDefaultFlag GetFinalPathFlag = 0x0
|
|
||||||
|
|
||||||
FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0
|
|
||||||
FILE_NAME_OPENED GetFinalPathFlag = 0x8
|
|
||||||
|
|
||||||
VOLUME_NAME_DOS GetFinalPathFlag = 0x0
|
|
||||||
VOLUME_NAME_GUID GetFinalPathFlag = 0x1
|
|
||||||
VOLUME_NAME_NT GetFinalPathFlag = 0x2
|
|
||||||
VOLUME_NAME_NONE GetFinalPathFlag = 0x4
|
|
||||||
)
|
|
||||||
|
|
||||||
// getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle
|
|
||||||
// with the given handle and flags. It transparently takes care of creating a buffer of the
|
|
||||||
// correct size for the call.
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
|
|
||||||
func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) {
|
|
||||||
b := stringbuffer.NewWString()
|
|
||||||
//TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n?
|
|
||||||
for {
|
|
||||||
n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
// If the buffer wasn't large enough, n will be the total size needed (including null terminator).
|
|
||||||
// Resize and try again.
|
|
||||||
if n > b.Cap() {
|
|
||||||
b.ResizeTo(n)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If the buffer is large enough, n will be the size not including the null terminator.
|
|
||||||
// Convert to a Go string and return.
|
|
||||||
return b.String(), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package fs
|
|
||||||
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
|
|
||||||
type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32`
|
|
||||||
|
|
||||||
// Impersonation levels
|
|
||||||
const (
|
|
||||||
SecurityAnonymous SecurityImpersonationLevel = 0
|
|
||||||
SecurityIdentification SecurityImpersonationLevel = 1
|
|
||||||
SecurityImpersonation SecurityImpersonationLevel = 2
|
|
||||||
SecurityDelegation SecurityImpersonationLevel = 3
|
|
||||||
)
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package fs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
// Do the interface allocations only once for common
|
|
||||||
// Errno values.
|
|
||||||
const (
|
|
||||||
errnoERROR_IO_PENDING = 997
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
|
||||||
errERROR_EINVAL error = syscall.EINVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
// errnoErr returns common boxed Errno values, to prevent
|
|
||||||
// allocations at runtime.
|
|
||||||
func errnoErr(e syscall.Errno) error {
|
|
||||||
switch e {
|
|
||||||
case 0:
|
|
||||||
return errERROR_EINVAL
|
|
||||||
case errnoERROR_IO_PENDING:
|
|
||||||
return errERROR_IO_PENDING
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
|
|
||||||
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, err = syscall.UTF16PtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _CreateFile(_p0, access, mode, sa, createmode, attrs, templatefile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _CreateFile(name *uint16, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
|
|
||||||
r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile))
|
|
||||||
handle = windows.Handle(r0)
|
|
||||||
if handle == windows.InvalidHandle {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
package socket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RawSockaddr allows structs to be used with [Bind] and [ConnectEx]. The
|
|
||||||
// struct must meet the Win32 sockaddr requirements specified here:
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
|
|
||||||
//
|
|
||||||
// Specifically, the struct size must be least larger than an int16 (unsigned short)
|
|
||||||
// for the address family.
|
|
||||||
type RawSockaddr interface {
|
|
||||||
// Sockaddr returns a pointer to the RawSockaddr and its struct size, allowing
|
|
||||||
// for the RawSockaddr's data to be overwritten by syscalls (if necessary).
|
|
||||||
//
|
|
||||||
// It is the callers responsibility to validate that the values are valid; invalid
|
|
||||||
// pointers or size can cause a panic.
|
|
||||||
Sockaddr() (unsafe.Pointer, int32, error)
|
|
||||||
}
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package socket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/pkg/guid"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go socket.go
|
|
||||||
|
|
||||||
//sys getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getsockname
|
|
||||||
//sys getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getpeername
|
|
||||||
//sys bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
|
||||||
|
|
||||||
const socketError = uintptr(^uint32(0))
|
|
||||||
|
|
||||||
var (
|
|
||||||
// todo(helsaawy): create custom error types to store the desired vs actual size and addr family?
|
|
||||||
|
|
||||||
ErrBufferSize = errors.New("buffer size")
|
|
||||||
ErrAddrFamily = errors.New("address family")
|
|
||||||
ErrInvalidPointer = errors.New("invalid pointer")
|
|
||||||
ErrSocketClosed = fmt.Errorf("socket closed: %w", net.ErrClosed)
|
|
||||||
)
|
|
||||||
|
|
||||||
// todo(helsaawy): replace these with generics, ie: GetSockName[S RawSockaddr](s windows.Handle) (S, error)
|
|
||||||
|
|
||||||
// GetSockName writes the local address of socket s to the [RawSockaddr] rsa.
|
|
||||||
// If rsa is not large enough, the [windows.WSAEFAULT] is returned.
|
|
||||||
func GetSockName(s windows.Handle, rsa RawSockaddr) error {
|
|
||||||
ptr, l, err := rsa.Sockaddr()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// although getsockname returns WSAEFAULT if the buffer is too small, it does not set
|
|
||||||
// &l to the correct size, so--apart from doubling the buffer repeatedly--there is no remedy
|
|
||||||
return getsockname(s, ptr, &l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerName returns the remote address the socket is connected to.
|
|
||||||
//
|
|
||||||
// See [GetSockName] for more information.
|
|
||||||
func GetPeerName(s windows.Handle, rsa RawSockaddr) error {
|
|
||||||
ptr, l, err := rsa.Sockaddr()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getpeername(s, ptr, &l)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Bind(s windows.Handle, rsa RawSockaddr) (err error) {
|
|
||||||
ptr, l, err := rsa.Sockaddr()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return bind(s, ptr, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// "golang.org/x/sys/windows".ConnectEx and .Bind only accept internal implementations of the
|
|
||||||
// their sockaddr interface, so they cannot be used with HvsockAddr
|
|
||||||
// Replicate functionality here from
|
|
||||||
// https://cs.opensource.google/go/x/sys/+/master:windows/syscall_windows.go
|
|
||||||
|
|
||||||
// The function pointers to `AcceptEx`, `ConnectEx` and `GetAcceptExSockaddrs` must be loaded at
|
|
||||||
// runtime via a WSAIoctl call:
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex#remarks
|
|
||||||
|
|
||||||
type runtimeFunc struct {
|
|
||||||
id guid.GUID
|
|
||||||
once sync.Once
|
|
||||||
addr uintptr
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *runtimeFunc) Load() error {
|
|
||||||
f.once.Do(func() {
|
|
||||||
var s windows.Handle
|
|
||||||
s, f.err = windows.Socket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP)
|
|
||||||
if f.err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer windows.CloseHandle(s) //nolint:errcheck
|
|
||||||
|
|
||||||
var n uint32
|
|
||||||
f.err = windows.WSAIoctl(s,
|
|
||||||
windows.SIO_GET_EXTENSION_FUNCTION_POINTER,
|
|
||||||
(*byte)(unsafe.Pointer(&f.id)),
|
|
||||||
uint32(unsafe.Sizeof(f.id)),
|
|
||||||
(*byte)(unsafe.Pointer(&f.addr)),
|
|
||||||
uint32(unsafe.Sizeof(f.addr)),
|
|
||||||
&n,
|
|
||||||
nil, // overlapped
|
|
||||||
0, // completionRoutine
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return f.err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// todo: add `AcceptEx` and `GetAcceptExSockaddrs`
|
|
||||||
WSAID_CONNECTEX = guid.GUID{ //revive:disable-line:var-naming ALL_CAPS
|
|
||||||
Data1: 0x25a207b9,
|
|
||||||
Data2: 0xddf3,
|
|
||||||
Data3: 0x4660,
|
|
||||||
Data4: [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e},
|
|
||||||
}
|
|
||||||
|
|
||||||
connectExFunc = runtimeFunc{id: WSAID_CONNECTEX}
|
|
||||||
)
|
|
||||||
|
|
||||||
func ConnectEx(
|
|
||||||
fd windows.Handle,
|
|
||||||
rsa RawSockaddr,
|
|
||||||
sendBuf *byte,
|
|
||||||
sendDataLen uint32,
|
|
||||||
bytesSent *uint32,
|
|
||||||
overlapped *windows.Overlapped,
|
|
||||||
) error {
|
|
||||||
if err := connectExFunc.Load(); err != nil {
|
|
||||||
return fmt.Errorf("failed to load ConnectEx function pointer: %w", err)
|
|
||||||
}
|
|
||||||
ptr, n, err := rsa.Sockaddr()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BOOL LpfnConnectex(
|
|
||||||
// [in] SOCKET s,
|
|
||||||
// [in] const sockaddr *name,
|
|
||||||
// [in] int namelen,
|
|
||||||
// [in, optional] PVOID lpSendBuffer,
|
|
||||||
// [in] DWORD dwSendDataLength,
|
|
||||||
// [out] LPDWORD lpdwBytesSent,
|
|
||||||
// [in] LPOVERLAPPED lpOverlapped
|
|
||||||
// )
|
|
||||||
|
|
||||||
func connectEx(
|
|
||||||
s windows.Handle,
|
|
||||||
name unsafe.Pointer,
|
|
||||||
namelen int32,
|
|
||||||
sendBuf *byte,
|
|
||||||
sendDataLen uint32,
|
|
||||||
bytesSent *uint32,
|
|
||||||
overlapped *windows.Overlapped,
|
|
||||||
) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(connectExFunc.addr,
|
|
||||||
uintptr(s),
|
|
||||||
uintptr(name),
|
|
||||||
uintptr(namelen),
|
|
||||||
uintptr(unsafe.Pointer(sendBuf)),
|
|
||||||
uintptr(sendDataLen),
|
|
||||||
uintptr(unsafe.Pointer(bytesSent)),
|
|
||||||
uintptr(unsafe.Pointer(overlapped)),
|
|
||||||
)
|
|
||||||
|
|
||||||
if r1 == 0 {
|
|
||||||
if e1 != 0 {
|
|
||||||
err = error(e1)
|
|
||||||
} else {
|
|
||||||
err = syscall.EINVAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package socket
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
// Do the interface allocations only once for common
|
|
||||||
// Errno values.
|
|
||||||
const (
|
|
||||||
errnoERROR_IO_PENDING = 997
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
|
||||||
errERROR_EINVAL error = syscall.EINVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
// errnoErr returns common boxed Errno values, to prevent
|
|
||||||
// allocations at runtime.
|
|
||||||
func errnoErr(e syscall.Errno) error {
|
|
||||||
switch e {
|
|
||||||
case 0:
|
|
||||||
return errERROR_EINVAL
|
|
||||||
case errnoERROR_IO_PENDING:
|
|
||||||
return errERROR_IO_PENDING
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
|
||||||
|
|
||||||
procbind = modws2_32.NewProc("bind")
|
|
||||||
procgetpeername = modws2_32.NewProc("getpeername")
|
|
||||||
procgetsockname = modws2_32.NewProc("getsockname")
|
|
||||||
)
|
|
||||||
|
|
||||||
func bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen))
|
|
||||||
if r1 == socketError {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procgetpeername.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
|
|
||||||
if r1 == socketError {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procgetsockname.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
|
|
||||||
if r1 == socketError {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
package stringbuffer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"unicode/utf16"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: worth exporting and using in mkwinsyscall?
|
|
||||||
|
|
||||||
// Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate
|
|
||||||
// large path strings:
|
|
||||||
// MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310.
|
|
||||||
const MinWStringCap = 310
|
|
||||||
|
|
||||||
// use *[]uint16 since []uint16 creates an extra allocation where the slice header
|
|
||||||
// is copied to heap and then referenced via pointer in the interface header that sync.Pool
|
|
||||||
// stores.
|
|
||||||
var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly
|
|
||||||
New: func() interface{} {
|
|
||||||
b := make([]uint16, MinWStringCap)
|
|
||||||
return &b
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) }
|
|
||||||
|
|
||||||
// freeBuffer copies the slice header data, and puts a pointer to that in the pool.
|
|
||||||
// This avoids taking a pointer to the slice header in WString, which can be set to nil.
|
|
||||||
func freeBuffer(b []uint16) { pathPool.Put(&b) }
|
|
||||||
|
|
||||||
// WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings
|
|
||||||
// for interacting with Win32 APIs.
|
|
||||||
// Sizes are specified as uint32 and not int.
|
|
||||||
//
|
|
||||||
// It is not thread safe.
|
|
||||||
type WString struct {
|
|
||||||
// type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future.
|
|
||||||
|
|
||||||
// raw buffer
|
|
||||||
b []uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWString returns a [WString] allocated from a shared pool with an
|
|
||||||
// initial capacity of at least [MinWStringCap].
|
|
||||||
// Since the buffer may have been previously used, its contents are not guaranteed to be empty.
|
|
||||||
//
|
|
||||||
// The buffer should be freed via [WString.Free]
|
|
||||||
func NewWString() *WString {
|
|
||||||
return &WString{
|
|
||||||
b: newBuffer(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *WString) Free() {
|
|
||||||
if b.empty() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
freeBuffer(b.b)
|
|
||||||
b.b = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResizeTo grows the buffer to at least c and returns the new capacity, freeing the
|
|
||||||
// previous buffer back into pool.
|
|
||||||
func (b *WString) ResizeTo(c uint32) uint32 {
|
|
||||||
// already sufficient (or n is 0)
|
|
||||||
if c <= b.Cap() {
|
|
||||||
return b.Cap()
|
|
||||||
}
|
|
||||||
|
|
||||||
if c <= MinWStringCap {
|
|
||||||
c = MinWStringCap
|
|
||||||
}
|
|
||||||
// allocate at-least double buffer size, as is done in [bytes.Buffer] and other places
|
|
||||||
if c <= 2*b.Cap() {
|
|
||||||
c = 2 * b.Cap()
|
|
||||||
}
|
|
||||||
|
|
||||||
b2 := make([]uint16, c)
|
|
||||||
if !b.empty() {
|
|
||||||
copy(b2, b.b)
|
|
||||||
freeBuffer(b.b)
|
|
||||||
}
|
|
||||||
b.b = b2
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer returns the underlying []uint16 buffer.
|
|
||||||
func (b *WString) Buffer() []uint16 {
|
|
||||||
if b.empty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return b.b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pointer returns a pointer to the first uint16 in the buffer.
|
|
||||||
// If the [WString.Free] has already been called, the pointer will be nil.
|
|
||||||
func (b *WString) Pointer() *uint16 {
|
|
||||||
if b.empty() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &b.b[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer.
|
|
||||||
//
|
|
||||||
// It assumes that the data is null-terminated.
|
|
||||||
func (b *WString) String() string {
|
|
||||||
// Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows"
|
|
||||||
// and would make this code Windows-only, which makes no sense.
|
|
||||||
// So copy UTF16ToString code into here.
|
|
||||||
// If other windows-specific code is added, switch to [windows.UTF16ToString]
|
|
||||||
|
|
||||||
s := b.b
|
|
||||||
for i, v := range s {
|
|
||||||
if v == 0 {
|
|
||||||
s = s[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(utf16.Decode(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cap returns the underlying buffer capacity.
|
|
||||||
func (b *WString) Cap() uint32 {
|
|
||||||
if b.empty() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return b.cap()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *WString) cap() uint32 { return uint32(cap(b.b)) }
|
|
||||||
func (b *WString) empty() bool { return b == nil || b.cap() == 0 }
|
|
||||||
|
|
@ -1,586 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/internal/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
//sys connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) = ConnectNamedPipe
|
|
||||||
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateNamedPipeW
|
|
||||||
//sys disconnectNamedPipe(pipe windows.Handle) (err error) = DisconnectNamedPipe
|
|
||||||
//sys getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
|
||||||
//sys getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
|
||||||
//sys ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile
|
|
||||||
//sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
|
|
||||||
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U
|
|
||||||
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl
|
|
||||||
|
|
||||||
type PipeConn interface {
|
|
||||||
net.Conn
|
|
||||||
Disconnect() error
|
|
||||||
Flush() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// type aliases for mkwinsyscall code
|
|
||||||
type (
|
|
||||||
ntAccessMask = fs.AccessMask
|
|
||||||
ntFileShareMode = fs.FileShareMode
|
|
||||||
ntFileCreationDisposition = fs.NTFileCreationDisposition
|
|
||||||
ntFileOptions = fs.NTCreateOptions
|
|
||||||
)
|
|
||||||
|
|
||||||
type ioStatusBlock struct {
|
|
||||||
Status, Information uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct _OBJECT_ATTRIBUTES {
|
|
||||||
// ULONG Length;
|
|
||||||
// HANDLE RootDirectory;
|
|
||||||
// PUNICODE_STRING ObjectName;
|
|
||||||
// ULONG Attributes;
|
|
||||||
// PVOID SecurityDescriptor;
|
|
||||||
// PVOID SecurityQualityOfService;
|
|
||||||
// } OBJECT_ATTRIBUTES;
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes
|
|
||||||
type objectAttributes struct {
|
|
||||||
Length uintptr
|
|
||||||
RootDirectory uintptr
|
|
||||||
ObjectName *unicodeString
|
|
||||||
Attributes uintptr
|
|
||||||
SecurityDescriptor *securityDescriptor
|
|
||||||
SecurityQoS uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
type unicodeString struct {
|
|
||||||
Length uint16
|
|
||||||
MaximumLength uint16
|
|
||||||
Buffer uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// typedef struct _SECURITY_DESCRIPTOR {
|
|
||||||
// BYTE Revision;
|
|
||||||
// BYTE Sbz1;
|
|
||||||
// SECURITY_DESCRIPTOR_CONTROL Control;
|
|
||||||
// PSID Owner;
|
|
||||||
// PSID Group;
|
|
||||||
// PACL Sacl;
|
|
||||||
// PACL Dacl;
|
|
||||||
// } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor
|
|
||||||
type securityDescriptor struct {
|
|
||||||
Revision byte
|
|
||||||
Sbz1 byte
|
|
||||||
Control uint16
|
|
||||||
Owner uintptr
|
|
||||||
Group uintptr
|
|
||||||
Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl
|
|
||||||
Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl
|
|
||||||
}
|
|
||||||
|
|
||||||
type ntStatus int32
|
|
||||||
|
|
||||||
func (status ntStatus) Err() error {
|
|
||||||
if status >= 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return rtlNtStatusToDosError(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
|
||||||
ErrPipeListenerClosed = net.ErrClosed
|
|
||||||
|
|
||||||
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
|
||||||
)
|
|
||||||
|
|
||||||
type win32Pipe struct {
|
|
||||||
*win32File
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ PipeConn = (*win32Pipe)(nil)
|
|
||||||
|
|
||||||
type win32MessageBytePipe struct {
|
|
||||||
win32Pipe
|
|
||||||
writeClosed bool
|
|
||||||
readEOF bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipeAddress string
|
|
||||||
|
|
||||||
func (f *win32Pipe) LocalAddr() net.Addr {
|
|
||||||
return pipeAddress(f.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *win32Pipe) RemoteAddr() net.Addr {
|
|
||||||
return pipeAddress(f.path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *win32Pipe) SetDeadline(t time.Time) error {
|
|
||||||
if err := f.SetReadDeadline(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return f.SetWriteDeadline(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *win32Pipe) Disconnect() error {
|
|
||||||
return disconnectNamedPipe(f.win32File.handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseWrite closes the write side of a message pipe in byte mode.
|
|
||||||
func (f *win32MessageBytePipe) CloseWrite() error {
|
|
||||||
if f.writeClosed {
|
|
||||||
return errPipeWriteClosed
|
|
||||||
}
|
|
||||||
err := f.win32File.Flush()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = f.win32File.Write(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.writeClosed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
|
||||||
// they are used to implement CloseWrite().
|
|
||||||
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
|
||||||
if f.writeClosed {
|
|
||||||
return 0, errPipeWriteClosed
|
|
||||||
}
|
|
||||||
if len(b) == 0 {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return f.win32File.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
|
||||||
// mode pipe will return io.EOF, as will all subsequent reads.
|
|
||||||
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
|
||||||
if f.readEOF {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
n, err := f.win32File.Read(b)
|
|
||||||
if err == io.EOF { //nolint:errorlint
|
|
||||||
// If this was the result of a zero-byte read, then
|
|
||||||
// it is possible that the read was due to a zero-size
|
|
||||||
// message. Since we are simulating CloseWrite with a
|
|
||||||
// zero-byte message, ensure that all future Read() calls
|
|
||||||
// also return EOF.
|
|
||||||
f.readEOF = true
|
|
||||||
} else if err == windows.ERROR_MORE_DATA { //nolint:errorlint // err is Errno
|
|
||||||
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
|
||||||
// and the message still has more bytes. Treat this as a success, since
|
|
||||||
// this package presents all named pipes as byte streams.
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pipeAddress) Network() string {
|
|
||||||
return "pipe"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s pipeAddress) String() string {
|
|
||||||
return string(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
|
||||||
func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask, impLevel PipeImpLevel) (windows.Handle, error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return windows.Handle(0), ctx.Err()
|
|
||||||
default:
|
|
||||||
h, err := fs.CreateFile(*path,
|
|
||||||
access,
|
|
||||||
0, // mode
|
|
||||||
nil, // security attributes
|
|
||||||
fs.OPEN_EXISTING,
|
|
||||||
fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.FileSQSFlag(impLevel),
|
|
||||||
0, // template file handle
|
|
||||||
)
|
|
||||||
if err == nil {
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno
|
|
||||||
return h, &os.PathError{Err: err, Op: "open", Path: *path}
|
|
||||||
}
|
|
||||||
// Wait 10 msec and try again. This is a rather simplistic
|
|
||||||
// view, as we always try each 10 milliseconds.
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialPipe connects to a named pipe by path, timing out if the connection
|
|
||||||
// takes longer than the specified duration. If timeout is nil, then we use
|
|
||||||
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
|
|
||||||
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
|
||||||
var absTimeout time.Time
|
|
||||||
if timeout != nil {
|
|
||||||
absTimeout = time.Now().Add(*timeout)
|
|
||||||
} else {
|
|
||||||
absTimeout = time.Now().Add(2 * time.Second)
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithDeadline(context.Background(), absTimeout)
|
|
||||||
defer cancel()
|
|
||||||
conn, err := DialPipeContext(ctx, path)
|
|
||||||
if errors.Is(err, context.DeadlineExceeded) {
|
|
||||||
return nil, ErrTimeout
|
|
||||||
}
|
|
||||||
return conn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
|
|
||||||
// cancellation or timeout.
|
|
||||||
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
|
|
||||||
return DialPipeAccess(ctx, path, uint32(fs.GENERIC_READ|fs.GENERIC_WRITE))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PipeImpLevel is an enumeration of impersonation levels that may be set
|
|
||||||
// when calling DialPipeAccessImpersonation.
|
|
||||||
type PipeImpLevel uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
PipeImpLevelAnonymous = PipeImpLevel(fs.SECURITY_ANONYMOUS)
|
|
||||||
PipeImpLevelIdentification = PipeImpLevel(fs.SECURITY_IDENTIFICATION)
|
|
||||||
PipeImpLevelImpersonation = PipeImpLevel(fs.SECURITY_IMPERSONATION)
|
|
||||||
PipeImpLevelDelegation = PipeImpLevel(fs.SECURITY_DELEGATION)
|
|
||||||
)
|
|
||||||
|
|
||||||
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
|
|
||||||
// cancellation or timeout.
|
|
||||||
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
|
|
||||||
return DialPipeAccessImpLevel(ctx, path, access, PipeImpLevelAnonymous)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialPipeAccessImpLevel attempts to connect to a named pipe by `path` with
|
|
||||||
// `access` at `impLevel` until `ctx` cancellation or timeout. The other
|
|
||||||
// DialPipe* implementations use PipeImpLevelAnonymous.
|
|
||||||
func DialPipeAccessImpLevel(ctx context.Context, path string, access uint32, impLevel PipeImpLevel) (net.Conn, error) {
|
|
||||||
var err error
|
|
||||||
var h windows.Handle
|
|
||||||
h, err = tryDialPipe(ctx, &path, fs.AccessMask(access), impLevel)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags uint32
|
|
||||||
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := makeWin32File(h)
|
|
||||||
if err != nil {
|
|
||||||
windows.Close(h)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the pipe is in message mode, return a message byte pipe, which
|
|
||||||
// supports CloseWrite().
|
|
||||||
if flags&windows.PIPE_TYPE_MESSAGE != 0 {
|
|
||||||
return &win32MessageBytePipe{
|
|
||||||
win32Pipe: win32Pipe{win32File: f, path: path},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return &win32Pipe{win32File: f, path: path}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type acceptResponse struct {
|
|
||||||
f *win32File
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type win32PipeListener struct {
|
|
||||||
firstHandle windows.Handle
|
|
||||||
path string
|
|
||||||
config PipeConfig
|
|
||||||
acceptCh chan (chan acceptResponse)
|
|
||||||
closeCh chan int
|
|
||||||
doneCh chan int
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (windows.Handle, error) {
|
|
||||||
path16, err := windows.UTF16FromString(path)
|
|
||||||
if err != nil {
|
|
||||||
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
var oa objectAttributes
|
|
||||||
oa.Length = unsafe.Sizeof(oa)
|
|
||||||
|
|
||||||
var ntPath unicodeString
|
|
||||||
if err := rtlDosPathNameToNtPathName(&path16[0],
|
|
||||||
&ntPath,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
).Err(); err != nil {
|
|
||||||
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
|
||||||
}
|
|
||||||
defer windows.LocalFree(windows.Handle(ntPath.Buffer)) //nolint:errcheck
|
|
||||||
oa.ObjectName = &ntPath
|
|
||||||
oa.Attributes = windows.OBJ_CASE_INSENSITIVE
|
|
||||||
|
|
||||||
// The security descriptor is only needed for the first pipe.
|
|
||||||
if first {
|
|
||||||
if sd != nil {
|
|
||||||
//todo: does `sdb` need to be allocated on the heap, or can go allocate it?
|
|
||||||
l := uint32(len(sd))
|
|
||||||
sdb, err := windows.LocalAlloc(0, l)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("LocalAlloc for security descriptor with of length %d: %w", l, err)
|
|
||||||
}
|
|
||||||
defer windows.LocalFree(windows.Handle(sdb)) //nolint:errcheck
|
|
||||||
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
|
|
||||||
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
|
|
||||||
} else {
|
|
||||||
// Construct the default named pipe security descriptor.
|
|
||||||
var dacl uintptr
|
|
||||||
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
|
|
||||||
return 0, fmt.Errorf("getting default named pipe ACL: %w", err)
|
|
||||||
}
|
|
||||||
defer windows.LocalFree(windows.Handle(dacl)) //nolint:errcheck
|
|
||||||
|
|
||||||
sdb := &securityDescriptor{
|
|
||||||
Revision: 1,
|
|
||||||
Control: windows.SE_DACL_PRESENT,
|
|
||||||
Dacl: dacl,
|
|
||||||
}
|
|
||||||
oa.SecurityDescriptor = sdb
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
|
|
||||||
if c.MessageMode {
|
|
||||||
typ |= windows.FILE_PIPE_MESSAGE_TYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
disposition := fs.FILE_OPEN
|
|
||||||
access := fs.GENERIC_READ | fs.GENERIC_WRITE | fs.SYNCHRONIZE
|
|
||||||
if first {
|
|
||||||
disposition = fs.FILE_CREATE
|
|
||||||
// By not asking for read or write access, the named pipe file system
|
|
||||||
// will put this pipe into an initially disconnected state, blocking
|
|
||||||
// client connections until the next call with first == false.
|
|
||||||
access = fs.SYNCHRONIZE
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := int64(-50 * 10000) // 50ms
|
|
||||||
|
|
||||||
var (
|
|
||||||
h windows.Handle
|
|
||||||
iosb ioStatusBlock
|
|
||||||
)
|
|
||||||
err = ntCreateNamedPipeFile(&h,
|
|
||||||
access,
|
|
||||||
&oa,
|
|
||||||
&iosb,
|
|
||||||
fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE,
|
|
||||||
disposition,
|
|
||||||
0,
|
|
||||||
typ,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0xffffffff,
|
|
||||||
uint32(c.InputBufferSize),
|
|
||||||
uint32(c.OutputBufferSize),
|
|
||||||
&timeout).Err()
|
|
||||||
if err != nil {
|
|
||||||
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
runtime.KeepAlive(ntPath)
|
|
||||||
return h, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
|
||||||
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f, err := makeWin32File(h)
|
|
||||||
if err != nil {
|
|
||||||
windows.Close(h)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
|
||||||
p, err := l.makeServerPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the client to connect.
|
|
||||||
ch := make(chan error)
|
|
||||||
go func(p *win32File) {
|
|
||||||
ch <- connectPipe(p)
|
|
||||||
}(p)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err = <-ch:
|
|
||||||
if err != nil {
|
|
||||||
p.Close()
|
|
||||||
p = nil
|
|
||||||
}
|
|
||||||
case <-l.closeCh:
|
|
||||||
// Abort the connect request by closing the handle.
|
|
||||||
p.Close()
|
|
||||||
p = nil
|
|
||||||
err = <-ch
|
|
||||||
if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno
|
|
||||||
err = ErrPipeListenerClosed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *win32PipeListener) listenerRoutine() {
|
|
||||||
closed := false
|
|
||||||
for !closed {
|
|
||||||
select {
|
|
||||||
case <-l.closeCh:
|
|
||||||
closed = true
|
|
||||||
case responseCh := <-l.acceptCh:
|
|
||||||
var (
|
|
||||||
p *win32File
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
p, err = l.makeConnectedServerPipe()
|
|
||||||
// If the connection was immediately closed by the client, try
|
|
||||||
// again.
|
|
||||||
if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
responseCh <- acceptResponse{p, err}
|
|
||||||
closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno
|
|
||||||
}
|
|
||||||
}
|
|
||||||
windows.Close(l.firstHandle)
|
|
||||||
l.firstHandle = 0
|
|
||||||
// Notify Close() and Accept() callers that the handle has been closed.
|
|
||||||
close(l.doneCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PipeConfig contain configuration for the pipe listener.
|
|
||||||
type PipeConfig struct {
|
|
||||||
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
|
|
||||||
SecurityDescriptor string
|
|
||||||
|
|
||||||
// MessageMode determines whether the pipe is in byte or message mode. In either
|
|
||||||
// case the pipe is read in byte mode by default. The only practical difference in
|
|
||||||
// this implementation is that CloseWrite() is only supported for message mode pipes;
|
|
||||||
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
|
|
||||||
// transferred to the reader (and returned as io.EOF in this implementation)
|
|
||||||
// when the pipe is in message mode.
|
|
||||||
MessageMode bool
|
|
||||||
|
|
||||||
// InputBufferSize specifies the size of the input buffer, in bytes.
|
|
||||||
InputBufferSize int32
|
|
||||||
|
|
||||||
// OutputBufferSize specifies the size of the output buffer, in bytes.
|
|
||||||
OutputBufferSize int32
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
|
|
||||||
// The pipe must not already exist.
|
|
||||||
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
|
||||||
var (
|
|
||||||
sd []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if c == nil {
|
|
||||||
c = &PipeConfig{}
|
|
||||||
}
|
|
||||||
if c.SecurityDescriptor != "" {
|
|
||||||
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
h, err := makeServerPipeHandle(path, sd, c, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
l := &win32PipeListener{
|
|
||||||
firstHandle: h,
|
|
||||||
path: path,
|
|
||||||
config: *c,
|
|
||||||
acceptCh: make(chan (chan acceptResponse)),
|
|
||||||
closeCh: make(chan int),
|
|
||||||
doneCh: make(chan int),
|
|
||||||
}
|
|
||||||
go l.listenerRoutine()
|
|
||||||
return l, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectPipe(p *win32File) error {
|
|
||||||
c, err := p.prepareIO()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer p.wg.Done()
|
|
||||||
|
|
||||||
err = connectNamedPipe(p.handle, &c.o)
|
|
||||||
_, err = p.asyncIO(c, nil, 0, err)
|
|
||||||
if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *win32PipeListener) Accept() (net.Conn, error) {
|
|
||||||
ch := make(chan acceptResponse)
|
|
||||||
select {
|
|
||||||
case l.acceptCh <- ch:
|
|
||||||
response := <-ch
|
|
||||||
err := response.err
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if l.config.MessageMode {
|
|
||||||
return &win32MessageBytePipe{
|
|
||||||
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
|
||||||
case <-l.doneCh:
|
|
||||||
return nil, ErrPipeListenerClosed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *win32PipeListener) Close() error {
|
|
||||||
select {
|
|
||||||
case l.closeCh <- 1:
|
|
||||||
<-l.doneCh
|
|
||||||
case <-l.doneCh:
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *win32PipeListener) Addr() net.Addr {
|
|
||||||
return pipeAddress(l.path)
|
|
||||||
}
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
||||||
// Package guid provides a GUID type. The backing structure for a GUID is
|
|
||||||
// identical to that used by the golang.org/x/sys/windows GUID type.
|
|
||||||
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
|
||||||
// and the Windows (mixed-endian) encoding. See here for details:
|
|
||||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
|
||||||
package guid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/sha1" //nolint:gosec // not used for secure application
|
|
||||||
"encoding"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment
|
|
||||||
|
|
||||||
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
|
||||||
// how the entirety of the rest of the GUID is interpreted.
|
|
||||||
type Variant uint8
|
|
||||||
|
|
||||||
// The variants specified by RFC 4122 section 4.1.1.
|
|
||||||
const (
|
|
||||||
// VariantUnknown specifies a GUID variant which does not conform to one of
|
|
||||||
// the variant encodings specified in RFC 4122.
|
|
||||||
VariantUnknown Variant = iota
|
|
||||||
VariantNCS
|
|
||||||
VariantRFC4122 // RFC 4122
|
|
||||||
VariantMicrosoft
|
|
||||||
VariantFuture
|
|
||||||
)
|
|
||||||
|
|
||||||
// Version specifies how the bits in the GUID were generated. For instance, a
|
|
||||||
// version 4 GUID is randomly generated, and a version 5 is generated from the
|
|
||||||
// hash of an input string.
|
|
||||||
type Version uint8
|
|
||||||
|
|
||||||
func (v Version) String() string {
|
|
||||||
return strconv.FormatUint(uint64(v), 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = (encoding.TextMarshaler)(GUID{})
|
|
||||||
var _ = (encoding.TextUnmarshaler)(&GUID{})
|
|
||||||
|
|
||||||
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
|
|
||||||
func NewV4() (GUID, error) {
|
|
||||||
var b [16]byte
|
|
||||||
if _, err := rand.Read(b[:]); err != nil {
|
|
||||||
return GUID{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
g := FromArray(b)
|
|
||||||
g.setVersion(4) // Version 4 means randomly generated.
|
|
||||||
g.setVariant(VariantRFC4122)
|
|
||||||
|
|
||||||
return g, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
|
|
||||||
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
|
|
||||||
// and the sample code treats it as a series of bytes, so we do the same here.
|
|
||||||
//
|
|
||||||
// Some implementations, such as those found on Windows, treat the name as a
|
|
||||||
// big-endian UTF16 stream of bytes. If that is desired, the string can be
|
|
||||||
// encoded as such before being passed to this function.
|
|
||||||
func NewV5(namespace GUID, name []byte) (GUID, error) {
|
|
||||||
b := sha1.New() //nolint:gosec // not used for secure application
|
|
||||||
namespaceBytes := namespace.ToArray()
|
|
||||||
b.Write(namespaceBytes[:])
|
|
||||||
b.Write(name)
|
|
||||||
|
|
||||||
a := [16]byte{}
|
|
||||||
copy(a[:], b.Sum(nil))
|
|
||||||
|
|
||||||
g := FromArray(a)
|
|
||||||
g.setVersion(5) // Version 5 means generated from a string.
|
|
||||||
g.setVariant(VariantRFC4122)
|
|
||||||
|
|
||||||
return g, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
|
|
||||||
var g GUID
|
|
||||||
g.Data1 = order.Uint32(b[0:4])
|
|
||||||
g.Data2 = order.Uint16(b[4:6])
|
|
||||||
g.Data3 = order.Uint16(b[6:8])
|
|
||||||
copy(g.Data4[:], b[8:16])
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
|
|
||||||
b := [16]byte{}
|
|
||||||
order.PutUint32(b[0:4], g.Data1)
|
|
||||||
order.PutUint16(b[4:6], g.Data2)
|
|
||||||
order.PutUint16(b[6:8], g.Data3)
|
|
||||||
copy(b[8:16], g.Data4[:])
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
|
|
||||||
func FromArray(b [16]byte) GUID {
|
|
||||||
return fromArray(b, binary.BigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToArray returns an array of 16 bytes representing the GUID in big-endian
|
|
||||||
// encoding.
|
|
||||||
func (g GUID) ToArray() [16]byte {
|
|
||||||
return g.toArray(binary.BigEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
|
|
||||||
func FromWindowsArray(b [16]byte) GUID {
|
|
||||||
return fromArray(b, binary.LittleEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
|
|
||||||
// encoding.
|
|
||||||
func (g GUID) ToWindowsArray() [16]byte {
|
|
||||||
return g.toArray(binary.LittleEndian)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g GUID) String() string {
|
|
||||||
return fmt.Sprintf(
|
|
||||||
"%08x-%04x-%04x-%04x-%012x",
|
|
||||||
g.Data1,
|
|
||||||
g.Data2,
|
|
||||||
g.Data3,
|
|
||||||
g.Data4[:2],
|
|
||||||
g.Data4[2:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromString parses a string containing a GUID and returns the GUID. The only
|
|
||||||
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
|
||||||
// format.
|
|
||||||
func FromString(s string) (GUID, error) {
|
|
||||||
if len(s) != 36 {
|
|
||||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
||||||
}
|
|
||||||
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
|
||||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
var g GUID
|
|
||||||
|
|
||||||
data1, err := strconv.ParseUint(s[0:8], 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
||||||
}
|
|
||||||
g.Data1 = uint32(data1)
|
|
||||||
|
|
||||||
data2, err := strconv.ParseUint(s[9:13], 16, 16)
|
|
||||||
if err != nil {
|
|
||||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
||||||
}
|
|
||||||
g.Data2 = uint16(data2)
|
|
||||||
|
|
||||||
data3, err := strconv.ParseUint(s[14:18], 16, 16)
|
|
||||||
if err != nil {
|
|
||||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
||||||
}
|
|
||||||
g.Data3 = uint16(data3)
|
|
||||||
|
|
||||||
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
|
||||||
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
|
|
||||||
if err != nil {
|
|
||||||
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
|
||||||
}
|
|
||||||
g.Data4[i] = uint8(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return g, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GUID) setVariant(v Variant) {
|
|
||||||
d := g.Data4[0]
|
|
||||||
switch v {
|
|
||||||
case VariantNCS:
|
|
||||||
d = (d & 0x7f)
|
|
||||||
case VariantRFC4122:
|
|
||||||
d = (d & 0x3f) | 0x80
|
|
||||||
case VariantMicrosoft:
|
|
||||||
d = (d & 0x1f) | 0xc0
|
|
||||||
case VariantFuture:
|
|
||||||
d = (d & 0x0f) | 0xe0
|
|
||||||
case VariantUnknown:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("invalid variant: %d", v))
|
|
||||||
}
|
|
||||||
g.Data4[0] = d
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant returns the GUID variant, as defined in RFC 4122.
|
|
||||||
func (g GUID) Variant() Variant {
|
|
||||||
b := g.Data4[0]
|
|
||||||
if b&0x80 == 0 {
|
|
||||||
return VariantNCS
|
|
||||||
} else if b&0xc0 == 0x80 {
|
|
||||||
return VariantRFC4122
|
|
||||||
} else if b&0xe0 == 0xc0 {
|
|
||||||
return VariantMicrosoft
|
|
||||||
} else if b&0xe0 == 0xe0 {
|
|
||||||
return VariantFuture
|
|
||||||
}
|
|
||||||
return VariantUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GUID) setVersion(v Version) {
|
|
||||||
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version returns the GUID version, as defined in RFC 4122.
|
|
||||||
func (g GUID) Version() Version {
|
|
||||||
return Version((g.Data3 & 0xF000) >> 12)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText returns the textual representation of the GUID.
|
|
||||||
func (g GUID) MarshalText() ([]byte, error) {
|
|
||||||
return []byte(g.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
|
|
||||||
// into this GUID.
|
|
||||||
func (g *GUID) UnmarshalText(text []byte) error {
|
|
||||||
g2, err := FromString(string(text))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*g = g2
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package guid
|
|
||||||
|
|
||||||
// GUID represents a GUID/UUID. It has the same structure as
|
|
||||||
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
|
||||||
// that type. It is defined as its own type as that is only available to builds
|
|
||||||
// targeted at `windows`. The representation matches that used by native Windows
|
|
||||||
// code.
|
|
||||||
type GUID struct {
|
|
||||||
Data1 uint32
|
|
||||||
Data2 uint16
|
|
||||||
Data3 uint16
|
|
||||||
Data4 [8]byte
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package guid
|
|
||||||
|
|
||||||
import "golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
// GUID represents a GUID/UUID. It has the same structure as
|
|
||||||
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
|
||||||
// that type. It is defined as its own type so that stringification and
|
|
||||||
// marshaling can be supported. The representation matches that used by native
|
|
||||||
// Windows code.
|
|
||||||
type GUID windows.GUID
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
// Code generated by "stringer -type=Variant -trimprefix=Variant -linecomment"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package guid
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
func _() {
|
|
||||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
|
||||||
// Re-run the stringer command to generate them again.
|
|
||||||
var x [1]struct{}
|
|
||||||
_ = x[VariantUnknown-0]
|
|
||||||
_ = x[VariantNCS-1]
|
|
||||||
_ = x[VariantRFC4122-2]
|
|
||||||
_ = x[VariantMicrosoft-3]
|
|
||||||
_ = x[VariantFuture-4]
|
|
||||||
}
|
|
||||||
|
|
||||||
const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture"
|
|
||||||
|
|
||||||
var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33}
|
|
||||||
|
|
||||||
func (i Variant) String() string {
|
|
||||||
if i >= Variant(len(_Variant_index)-1) {
|
|
||||||
return "Variant(" + strconv.FormatInt(int64(i), 10) + ")"
|
|
||||||
}
|
|
||||||
return _Variant_name[_Variant_index[i]:_Variant_index[i+1]]
|
|
||||||
}
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"unicode/utf16"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
|
||||||
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
|
||||||
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
|
||||||
//sys openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
|
||||||
//sys getCurrentThread() (h windows.Handle) = GetCurrentThread
|
|
||||||
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
|
||||||
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
|
||||||
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
|
||||||
|
|
||||||
const (
|
|
||||||
//revive:disable-next-line:var-naming ALL_CAPS
|
|
||||||
SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED
|
|
||||||
|
|
||||||
//revive:disable-next-line:var-naming ALL_CAPS
|
|
||||||
ERROR_NOT_ALL_ASSIGNED windows.Errno = windows.ERROR_NOT_ALL_ASSIGNED
|
|
||||||
|
|
||||||
SeBackupPrivilege = "SeBackupPrivilege"
|
|
||||||
SeRestorePrivilege = "SeRestorePrivilege"
|
|
||||||
SeSecurityPrivilege = "SeSecurityPrivilege"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
privNames = make(map[string]uint64)
|
|
||||||
privNameMutex sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrivilegeError represents an error enabling privileges.
|
|
||||||
type PrivilegeError struct {
|
|
||||||
privileges []uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *PrivilegeError) Error() string {
|
|
||||||
s := "Could not enable privilege "
|
|
||||||
if len(e.privileges) > 1 {
|
|
||||||
s = "Could not enable privileges "
|
|
||||||
}
|
|
||||||
for i, p := range e.privileges {
|
|
||||||
if i != 0 {
|
|
||||||
s += ", "
|
|
||||||
}
|
|
||||||
s += `"`
|
|
||||||
s += getPrivilegeName(p)
|
|
||||||
s += `"`
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunWithPrivilege enables a single privilege for a function call.
|
|
||||||
func RunWithPrivilege(name string, fn func() error) error {
|
|
||||||
return RunWithPrivileges([]string{name}, fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunWithPrivileges enables privileges for a function call.
|
|
||||||
func RunWithPrivileges(names []string, fn func() error) error {
|
|
||||||
privileges, err := mapPrivileges(names)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
token, err := newThreadToken()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer releaseThreadToken(token)
|
|
||||||
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fn()
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapPrivileges(names []string) ([]uint64, error) {
|
|
||||||
privileges := make([]uint64, 0, len(names))
|
|
||||||
privNameMutex.Lock()
|
|
||||||
defer privNameMutex.Unlock()
|
|
||||||
for _, name := range names {
|
|
||||||
p, ok := privNames[name]
|
|
||||||
if !ok {
|
|
||||||
err := lookupPrivilegeValue("", name, &p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
privNames[name] = p
|
|
||||||
}
|
|
||||||
privileges = append(privileges, p)
|
|
||||||
}
|
|
||||||
return privileges, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnableProcessPrivileges enables privileges globally for the process.
|
|
||||||
func EnableProcessPrivileges(names []string) error {
|
|
||||||
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableProcessPrivileges disables privileges globally for the process.
|
|
||||||
func DisableProcessPrivileges(names []string) error {
|
|
||||||
return enableDisableProcessPrivilege(names, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func enableDisableProcessPrivilege(names []string, action uint32) error {
|
|
||||||
privileges, err := mapPrivileges(names)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p := windows.CurrentProcess()
|
|
||||||
var token windows.Token
|
|
||||||
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer token.Close()
|
|
||||||
return adjustPrivileges(token, privileges, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
|
|
||||||
var b bytes.Buffer
|
|
||||||
_ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
|
||||||
for _, p := range privileges {
|
|
||||||
_ = binary.Write(&b, binary.LittleEndian, p)
|
|
||||||
_ = binary.Write(&b, binary.LittleEndian, action)
|
|
||||||
}
|
|
||||||
prevState := make([]byte, b.Len())
|
|
||||||
reqSize := uint32(0)
|
|
||||||
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
|
||||||
if !success {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno
|
|
||||||
return &PrivilegeError{privileges}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPrivilegeName(luid uint64) string {
|
|
||||||
var nameBuffer [256]uint16
|
|
||||||
bufSize := uint32(len(nameBuffer))
|
|
||||||
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("<unknown privilege %d>", luid)
|
|
||||||
}
|
|
||||||
|
|
||||||
var displayNameBuffer [256]uint16
|
|
||||||
displayBufSize := uint32(len(displayNameBuffer))
|
|
||||||
var langID uint32
|
|
||||||
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func newThreadToken() (windows.Token, error) {
|
|
||||||
err := impersonateSelf(windows.SecurityImpersonation)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var token windows.Token
|
|
||||||
err = openThreadToken(getCurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, false, &token)
|
|
||||||
if err != nil {
|
|
||||||
rerr := revertToSelf()
|
|
||||||
if rerr != nil {
|
|
||||||
panic(rerr)
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func releaseThreadToken(h windows.Token) {
|
|
||||||
err := revertToSelf()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
h.Close()
|
|
||||||
}
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf16"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
reparseTagMountPoint = 0xA0000003
|
|
||||||
reparseTagSymlink = 0xA000000C
|
|
||||||
)
|
|
||||||
|
|
||||||
type reparseDataBuffer struct {
|
|
||||||
ReparseTag uint32
|
|
||||||
ReparseDataLength uint16
|
|
||||||
Reserved uint16
|
|
||||||
SubstituteNameOffset uint16
|
|
||||||
SubstituteNameLength uint16
|
|
||||||
PrintNameOffset uint16
|
|
||||||
PrintNameLength uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReparsePoint describes a Win32 symlink or mount point.
|
|
||||||
type ReparsePoint struct {
|
|
||||||
Target string
|
|
||||||
IsMountPoint bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
|
||||||
// mount point reparse point.
|
|
||||||
type UnsupportedReparsePointError struct {
|
|
||||||
Tag uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *UnsupportedReparsePointError) Error() string {
|
|
||||||
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
|
|
||||||
// or a mount point.
|
|
||||||
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
|
||||||
tag := binary.LittleEndian.Uint32(b[0:4])
|
|
||||||
return DecodeReparsePointData(tag, b[8:])
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
|
||||||
isMountPoint := false
|
|
||||||
switch tag {
|
|
||||||
case reparseTagMountPoint:
|
|
||||||
isMountPoint = true
|
|
||||||
case reparseTagSymlink:
|
|
||||||
default:
|
|
||||||
return nil, &UnsupportedReparsePointError{tag}
|
|
||||||
}
|
|
||||||
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
|
|
||||||
if !isMountPoint {
|
|
||||||
nameOffset += 4
|
|
||||||
}
|
|
||||||
nameLength := binary.LittleEndian.Uint16(b[6:8])
|
|
||||||
name := make([]uint16, nameLength/2)
|
|
||||||
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDriveLetter(c byte) bool {
|
|
||||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
|
||||||
// mount point.
|
|
||||||
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
|
||||||
// Generate an NT path and determine if this is a relative path.
|
|
||||||
var ntTarget string
|
|
||||||
relative := false
|
|
||||||
if strings.HasPrefix(rp.Target, `\\?\`) {
|
|
||||||
ntTarget = `\??\` + rp.Target[4:]
|
|
||||||
} else if strings.HasPrefix(rp.Target, `\\`) {
|
|
||||||
ntTarget = `\??\UNC\` + rp.Target[2:]
|
|
||||||
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
|
||||||
ntTarget = `\??\` + rp.Target
|
|
||||||
} else {
|
|
||||||
ntTarget = rp.Target
|
|
||||||
relative = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The paths must be NUL-terminated even though they are counted strings.
|
|
||||||
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
|
|
||||||
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
|
|
||||||
|
|
||||||
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
|
|
||||||
size += len(ntTarget16)*2 + len(target16)*2
|
|
||||||
|
|
||||||
tag := uint32(reparseTagMountPoint)
|
|
||||||
if !rp.IsMountPoint {
|
|
||||||
tag = reparseTagSymlink
|
|
||||||
size += 4 // Add room for symlink flags
|
|
||||||
}
|
|
||||||
|
|
||||||
data := reparseDataBuffer{
|
|
||||||
ReparseTag: tag,
|
|
||||||
ReparseDataLength: uint16(size),
|
|
||||||
SubstituteNameOffset: 0,
|
|
||||||
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
|
|
||||||
PrintNameOffset: uint16(len(ntTarget16) * 2),
|
|
||||||
PrintNameLength: uint16((len(target16) - 1) * 2),
|
|
||||||
}
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
_ = binary.Write(&b, binary.LittleEndian, &data)
|
|
||||||
if !rp.IsMountPoint {
|
|
||||||
flags := uint32(0)
|
|
||||||
if relative {
|
|
||||||
flags |= 1
|
|
||||||
}
|
|
||||||
_ = binary.Write(&b, binary.LittleEndian, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = binary.Write(&b, binary.LittleEndian, ntTarget16)
|
|
||||||
_ = binary.Write(&b, binary.LittleEndian, target16)
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
|
||||||
//sys lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountSidW
|
|
||||||
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
|
||||||
//sys convertStringSidToSid(str *uint16, sid **byte) (err error) = advapi32.ConvertStringSidToSidW
|
|
||||||
|
|
||||||
type AccountLookupError struct {
|
|
||||||
Name string
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *AccountLookupError) Error() string {
|
|
||||||
if e.Name == "" {
|
|
||||||
return "lookup account: empty account name specified"
|
|
||||||
}
|
|
||||||
var s string
|
|
||||||
switch {
|
|
||||||
case errors.Is(e.Err, windows.ERROR_INVALID_SID):
|
|
||||||
s = "the security ID structure is invalid"
|
|
||||||
case errors.Is(e.Err, windows.ERROR_NONE_MAPPED):
|
|
||||||
s = "not found"
|
|
||||||
default:
|
|
||||||
s = e.Err.Error()
|
|
||||||
}
|
|
||||||
return "lookup account " + e.Name + ": " + s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *AccountLookupError) Unwrap() error { return e.Err }
|
|
||||||
|
|
||||||
type SddlConversionError struct {
|
|
||||||
Sddl string
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SddlConversionError) Error() string {
|
|
||||||
return "convert " + e.Sddl + ": " + e.Err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *SddlConversionError) Unwrap() error { return e.Err }
|
|
||||||
|
|
||||||
// LookupSidByName looks up the SID of an account by name
|
|
||||||
//
|
|
||||||
//revive:disable-next-line:var-naming SID, not Sid
|
|
||||||
func LookupSidByName(name string) (sid string, err error) {
|
|
||||||
if name == "" {
|
|
||||||
return "", &AccountLookupError{name, windows.ERROR_NONE_MAPPED}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sidSize, sidNameUse, refDomainSize uint32
|
|
||||||
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
|
||||||
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
|
|
||||||
return "", &AccountLookupError{name, err}
|
|
||||||
}
|
|
||||||
sidBuffer := make([]byte, sidSize)
|
|
||||||
refDomainBuffer := make([]uint16, refDomainSize)
|
|
||||||
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
|
||||||
if err != nil {
|
|
||||||
return "", &AccountLookupError{name, err}
|
|
||||||
}
|
|
||||||
var strBuffer *uint16
|
|
||||||
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
|
|
||||||
if err != nil {
|
|
||||||
return "", &AccountLookupError{name, err}
|
|
||||||
}
|
|
||||||
sid = windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
|
||||||
_, _ = windows.LocalFree(windows.Handle(unsafe.Pointer(strBuffer)))
|
|
||||||
return sid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupNameBySid looks up the name of an account by SID
|
|
||||||
//
|
|
||||||
//revive:disable-next-line:var-naming SID, not Sid
|
|
||||||
func LookupNameBySid(sid string) (name string, err error) {
|
|
||||||
if sid == "" {
|
|
||||||
return "", &AccountLookupError{sid, windows.ERROR_NONE_MAPPED}
|
|
||||||
}
|
|
||||||
|
|
||||||
sidBuffer, err := windows.UTF16PtrFromString(sid)
|
|
||||||
if err != nil {
|
|
||||||
return "", &AccountLookupError{sid, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sidPtr *byte
|
|
||||||
if err = convertStringSidToSid(sidBuffer, &sidPtr); err != nil {
|
|
||||||
return "", &AccountLookupError{sid, err}
|
|
||||||
}
|
|
||||||
defer windows.LocalFree(windows.Handle(unsafe.Pointer(sidPtr))) //nolint:errcheck
|
|
||||||
|
|
||||||
var nameSize, refDomainSize, sidNameUse uint32
|
|
||||||
err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse)
|
|
||||||
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
|
|
||||||
return "", &AccountLookupError{sid, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
nameBuffer := make([]uint16, nameSize)
|
|
||||||
refDomainBuffer := make([]uint16, refDomainSize)
|
|
||||||
err = lookupAccountSid(nil, sidPtr, &nameBuffer[0], &nameSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
|
||||||
if err != nil {
|
|
||||||
return "", &AccountLookupError{sid, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
name = windows.UTF16ToString(nameBuffer)
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
|
||||||
sd, err := windows.SecurityDescriptorFromString(sddl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, &SddlConversionError{Sddl: sddl, Err: err}
|
|
||||||
}
|
|
||||||
b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length())
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
|
||||||
if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l {
|
|
||||||
return "", fmt.Errorf("SecurityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE)
|
|
||||||
}
|
|
||||||
s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0]))
|
|
||||||
return s.String(), nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go
|
|
||||||
|
|
@ -1,377 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package vhd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/pkg/guid"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zvhd_windows.go vhd.go
|
|
||||||
|
|
||||||
//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) = virtdisk.CreateVirtualDisk
|
|
||||||
//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) = virtdisk.OpenVirtualDisk
|
|
||||||
//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk
|
|
||||||
//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk
|
|
||||||
//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath
|
|
||||||
|
|
||||||
type (
|
|
||||||
CreateVirtualDiskFlag uint32
|
|
||||||
VirtualDiskFlag uint32
|
|
||||||
AttachVirtualDiskFlag uint32
|
|
||||||
DetachVirtualDiskFlag uint32
|
|
||||||
VirtualDiskAccessMask uint32
|
|
||||||
)
|
|
||||||
|
|
||||||
type VirtualStorageType struct {
|
|
||||||
DeviceID uint32
|
|
||||||
VendorID guid.GUID
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateVersion2 struct {
|
|
||||||
UniqueID guid.GUID
|
|
||||||
MaximumSize uint64
|
|
||||||
BlockSizeInBytes uint32
|
|
||||||
SectorSizeInBytes uint32
|
|
||||||
PhysicalSectorSizeInByte uint32
|
|
||||||
ParentPath *uint16 // string
|
|
||||||
SourcePath *uint16 // string
|
|
||||||
OpenFlags uint32
|
|
||||||
ParentVirtualStorageType VirtualStorageType
|
|
||||||
SourceVirtualStorageType VirtualStorageType
|
|
||||||
ResiliencyGUID guid.GUID
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateVirtualDiskParameters struct {
|
|
||||||
Version uint32 // Must always be set to 2
|
|
||||||
Version2 CreateVersion2
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenVersion2 struct {
|
|
||||||
GetInfoOnly bool
|
|
||||||
ReadOnly bool
|
|
||||||
ResiliencyGUID guid.GUID
|
|
||||||
}
|
|
||||||
|
|
||||||
type OpenVirtualDiskParameters struct {
|
|
||||||
Version uint32 // Must always be set to 2
|
|
||||||
Version2 OpenVersion2
|
|
||||||
}
|
|
||||||
|
|
||||||
// The higher level `OpenVersion2` struct uses `bool`s to refer to `GetInfoOnly` and `ReadOnly` for ease of use. However,
|
|
||||||
// the internal windows structure uses `BOOL`s aka int32s for these types. `openVersion2` is used for translating
|
|
||||||
// `OpenVersion2` fields to the correct windows internal field types on the `Open____` methods.
|
|
||||||
type openVersion2 struct {
|
|
||||||
getInfoOnly int32
|
|
||||||
readOnly int32
|
|
||||||
resiliencyGUID guid.GUID
|
|
||||||
}
|
|
||||||
|
|
||||||
type openVirtualDiskParameters struct {
|
|
||||||
version uint32
|
|
||||||
version2 openVersion2
|
|
||||||
}
|
|
||||||
|
|
||||||
type AttachVersion2 struct {
|
|
||||||
RestrictedOffset uint64
|
|
||||||
RestrictedLength uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type AttachVirtualDiskParameters struct {
|
|
||||||
Version uint32
|
|
||||||
Version2 AttachVersion2
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
//revive:disable-next-line:var-naming ALL_CAPS
|
|
||||||
VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3
|
|
||||||
|
|
||||||
// Access Mask for opening a VHD.
|
|
||||||
VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000
|
|
||||||
VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000
|
|
||||||
VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000
|
|
||||||
VirtualDiskAccessDetach VirtualDiskAccessMask = 0x00040000
|
|
||||||
VirtualDiskAccessGetInfo VirtualDiskAccessMask = 0x00080000
|
|
||||||
VirtualDiskAccessCreate VirtualDiskAccessMask = 0x00100000
|
|
||||||
VirtualDiskAccessMetaOps VirtualDiskAccessMask = 0x00200000
|
|
||||||
VirtualDiskAccessRead VirtualDiskAccessMask = 0x000d0000
|
|
||||||
VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000
|
|
||||||
VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000
|
|
||||||
|
|
||||||
// Flags for creating a VHD.
|
|
||||||
CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0
|
|
||||||
CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1
|
|
||||||
CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2
|
|
||||||
CreateVirtualDiskFlagDoNotCopyMetadataFromParent CreateVirtualDiskFlag = 0x4
|
|
||||||
CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8
|
|
||||||
CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10
|
|
||||||
CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20
|
|
||||||
CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40 //revive:disable-line:var-naming VHD, not Vhd
|
|
||||||
CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80
|
|
||||||
CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100 //revive:disable-line:var-naming PMEM, not Pmem
|
|
||||||
CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200
|
|
||||||
|
|
||||||
// Flags for opening a VHD.
|
|
||||||
OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000
|
|
||||||
OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001
|
|
||||||
OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002
|
|
||||||
OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x00000004
|
|
||||||
OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x00000008
|
|
||||||
OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x00000010
|
|
||||||
OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x00000020
|
|
||||||
OpenVirtualDiskFlagVhdsetFileOnly VirtualDiskFlag = 0x00000040
|
|
||||||
OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x00000080
|
|
||||||
OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100
|
|
||||||
OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200
|
|
||||||
|
|
||||||
// Flags for attaching a VHD.
|
|
||||||
AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000
|
|
||||||
AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001
|
|
||||||
AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002
|
|
||||||
AttachVirtualDiskFlagPermanentLifetime AttachVirtualDiskFlag = 0x00000004
|
|
||||||
AttachVirtualDiskFlagNoLocalHost AttachVirtualDiskFlag = 0x00000008
|
|
||||||
AttachVirtualDiskFlagNoSecurityDescriptor AttachVirtualDiskFlag = 0x00000010
|
|
||||||
AttachVirtualDiskFlagBypassDefaultEncryptionPolicy AttachVirtualDiskFlag = 0x00000020
|
|
||||||
AttachVirtualDiskFlagNonPnp AttachVirtualDiskFlag = 0x00000040
|
|
||||||
AttachVirtualDiskFlagRestrictedRange AttachVirtualDiskFlag = 0x00000080
|
|
||||||
AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100
|
|
||||||
AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200
|
|
||||||
|
|
||||||
// Flags for detaching a VHD.
|
|
||||||
DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateVhdx is a helper function to create a simple vhdx file at the given path using
|
|
||||||
// default values.
|
|
||||||
//
|
|
||||||
//revive:disable-next-line:var-naming VHDX, not Vhdx
|
|
||||||
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
|
|
||||||
params := CreateVirtualDiskParameters{
|
|
||||||
Version: 2,
|
|
||||||
Version2: CreateVersion2{
|
|
||||||
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
|
|
||||||
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
handle, err := CreateVirtualDisk(path, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, ¶ms)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return syscall.CloseHandle(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetachVirtualDisk detaches a virtual hard disk by handle.
|
|
||||||
func DetachVirtualDisk(handle syscall.Handle) (err error) {
|
|
||||||
if err := detachVirtualDisk(handle, 0, 0); err != nil {
|
|
||||||
return fmt.Errorf("failed to detach virtual disk: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetachVhd detaches a vhd found at `path`.
|
|
||||||
//
|
|
||||||
//revive:disable-next-line:var-naming VHD, not Vhd
|
|
||||||
func DetachVhd(path string) error {
|
|
||||||
handle, err := OpenVirtualDisk(
|
|
||||||
path,
|
|
||||||
VirtualDiskAccessNone,
|
|
||||||
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer syscall.CloseHandle(handle) //nolint:errcheck
|
|
||||||
return DetachVirtualDisk(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachVirtualDisk attaches a virtual hard disk for use.
|
|
||||||
func AttachVirtualDisk(
|
|
||||||
handle syscall.Handle,
|
|
||||||
attachVirtualDiskFlag AttachVirtualDiskFlag,
|
|
||||||
parameters *AttachVirtualDiskParameters,
|
|
||||||
) (err error) {
|
|
||||||
// Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5.
|
|
||||||
if err := attachVirtualDisk(
|
|
||||||
handle,
|
|
||||||
nil,
|
|
||||||
uint32(attachVirtualDiskFlag),
|
|
||||||
0,
|
|
||||||
parameters,
|
|
||||||
nil,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("failed to attach virtual disk: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachVhd attaches a virtual hard disk at `path` for use. Attaches using version 2
|
|
||||||
// of the ATTACH_VIRTUAL_DISK_PARAMETERS.
|
|
||||||
//
|
|
||||||
//revive:disable-next-line:var-naming VHD, not Vhd
|
|
||||||
func AttachVhd(path string) (err error) {
|
|
||||||
handle, err := OpenVirtualDisk(
|
|
||||||
path,
|
|
||||||
VirtualDiskAccessNone,
|
|
||||||
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer syscall.CloseHandle(handle) //nolint:errcheck
|
|
||||||
params := AttachVirtualDiskParameters{Version: 2}
|
|
||||||
if err := AttachVirtualDisk(
|
|
||||||
handle,
|
|
||||||
AttachVirtualDiskFlagNone,
|
|
||||||
¶ms,
|
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("failed to attach virtual disk: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
|
|
||||||
func OpenVirtualDisk(
|
|
||||||
vhdPath string,
|
|
||||||
virtualDiskAccessMask VirtualDiskAccessMask,
|
|
||||||
openVirtualDiskFlags VirtualDiskFlag,
|
|
||||||
) (syscall.Handle, error) {
|
|
||||||
parameters := OpenVirtualDiskParameters{Version: 2}
|
|
||||||
handle, err := OpenVirtualDiskWithParameters(
|
|
||||||
vhdPath,
|
|
||||||
virtualDiskAccessMask,
|
|
||||||
openVirtualDiskFlags,
|
|
||||||
¶meters,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenVirtualDiskWithParameters obtains a handle to a VHD opened with supplied access mask, flags and parameters.
|
|
||||||
func OpenVirtualDiskWithParameters(
|
|
||||||
vhdPath string,
|
|
||||||
virtualDiskAccessMask VirtualDiskAccessMask,
|
|
||||||
openVirtualDiskFlags VirtualDiskFlag,
|
|
||||||
parameters *OpenVirtualDiskParameters,
|
|
||||||
) (syscall.Handle, error) {
|
|
||||||
var (
|
|
||||||
handle syscall.Handle
|
|
||||||
defaultType VirtualStorageType
|
|
||||||
getInfoOnly int32
|
|
||||||
readOnly int32
|
|
||||||
)
|
|
||||||
if parameters.Version != 2 {
|
|
||||||
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
|
|
||||||
}
|
|
||||||
if parameters.Version2.GetInfoOnly {
|
|
||||||
getInfoOnly = 1
|
|
||||||
}
|
|
||||||
if parameters.Version2.ReadOnly {
|
|
||||||
readOnly = 1
|
|
||||||
}
|
|
||||||
params := &openVirtualDiskParameters{
|
|
||||||
version: parameters.Version,
|
|
||||||
version2: openVersion2{
|
|
||||||
getInfoOnly,
|
|
||||||
readOnly,
|
|
||||||
parameters.Version2.ResiliencyGUID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := openVirtualDisk(
|
|
||||||
&defaultType,
|
|
||||||
vhdPath,
|
|
||||||
uint32(virtualDiskAccessMask),
|
|
||||||
uint32(openVirtualDiskFlags),
|
|
||||||
params,
|
|
||||||
&handle,
|
|
||||||
); err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to open virtual disk: %w", err)
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateVirtualDisk creates a virtual harddisk and returns a handle to the disk.
|
|
||||||
func CreateVirtualDisk(
|
|
||||||
path string,
|
|
||||||
virtualDiskAccessMask VirtualDiskAccessMask,
|
|
||||||
createVirtualDiskFlags CreateVirtualDiskFlag,
|
|
||||||
parameters *CreateVirtualDiskParameters,
|
|
||||||
) (syscall.Handle, error) {
|
|
||||||
var (
|
|
||||||
handle syscall.Handle
|
|
||||||
defaultType VirtualStorageType
|
|
||||||
)
|
|
||||||
if parameters.Version != 2 {
|
|
||||||
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := createVirtualDisk(
|
|
||||||
&defaultType,
|
|
||||||
path,
|
|
||||||
uint32(virtualDiskAccessMask),
|
|
||||||
nil,
|
|
||||||
uint32(createVirtualDiskFlags),
|
|
||||||
0,
|
|
||||||
parameters,
|
|
||||||
nil,
|
|
||||||
&handle,
|
|
||||||
); err != nil {
|
|
||||||
return handle, fmt.Errorf("failed to create virtual disk: %w", err)
|
|
||||||
}
|
|
||||||
return handle, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVirtualDiskPhysicalPath takes a handle to a virtual hard disk and returns the physical
|
|
||||||
// path of the disk on the machine. This path is in the form \\.\PhysicalDriveX where X is an integer
|
|
||||||
// that represents the particular enumeration of the physical disk on the caller's system.
|
|
||||||
func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) {
|
|
||||||
var (
|
|
||||||
diskPathSizeInBytes uint32 = 256 * 2 // max path length 256 wide chars
|
|
||||||
diskPhysicalPathBuf [256]uint16
|
|
||||||
)
|
|
||||||
if err := getVirtualDiskPhysicalPath(
|
|
||||||
handle,
|
|
||||||
&diskPathSizeInBytes,
|
|
||||||
&diskPhysicalPathBuf[0],
|
|
||||||
); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to get disk physical path: %w", err)
|
|
||||||
}
|
|
||||||
return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateDiffVhd is a helper function to create a differencing virtual disk.
|
|
||||||
//
|
|
||||||
//revive:disable-next-line:var-naming VHD, not Vhd
|
|
||||||
func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error {
|
|
||||||
// Setting `ParentPath` is how to signal to create a differencing disk.
|
|
||||||
createParams := &CreateVirtualDiskParameters{
|
|
||||||
Version: 2,
|
|
||||||
Version2: CreateVersion2{
|
|
||||||
ParentPath: windows.StringToUTF16Ptr(baseVhdPath),
|
|
||||||
BlockSizeInBytes: blockSizeInMB * 1024 * 1024,
|
|
||||||
OpenFlags: uint32(OpenVirtualDiskFlagCachedIO),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
vhdHandle, err := CreateVirtualDisk(
|
|
||||||
diffVhdPath,
|
|
||||||
VirtualDiskAccessNone,
|
|
||||||
CreateVirtualDiskFlagNone,
|
|
||||||
createParams,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create differencing vhd: %w", err)
|
|
||||||
}
|
|
||||||
if err := syscall.CloseHandle(vhdHandle); err != nil {
|
|
||||||
return fmt.Errorf("failed to close differencing vhd handle: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package vhd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
// Do the interface allocations only once for common
|
|
||||||
// Errno values.
|
|
||||||
const (
|
|
||||||
errnoERROR_IO_PENDING = 997
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
|
||||||
errERROR_EINVAL error = syscall.EINVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
// errnoErr returns common boxed Errno values, to prevent
|
|
||||||
// allocations at runtime.
|
|
||||||
func errnoErr(e syscall.Errno) error {
|
|
||||||
switch e {
|
|
||||||
case 0:
|
|
||||||
return errERROR_EINVAL
|
|
||||||
case errnoERROR_IO_PENDING:
|
|
||||||
return errERROR_IO_PENDING
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
modvirtdisk = windows.NewLazySystemDLL("virtdisk.dll")
|
|
||||||
|
|
||||||
procAttachVirtualDisk = modvirtdisk.NewProc("AttachVirtualDisk")
|
|
||||||
procCreateVirtualDisk = modvirtdisk.NewProc("CreateVirtualDisk")
|
|
||||||
procDetachVirtualDisk = modvirtdisk.NewProc("DetachVirtualDisk")
|
|
||||||
procGetVirtualDiskPhysicalPath = modvirtdisk.NewProc("GetVirtualDiskPhysicalPath")
|
|
||||||
procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk")
|
|
||||||
)
|
|
||||||
|
|
||||||
func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procAttachVirtualDisk.Addr(), uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)))
|
|
||||||
if r0 != 0 {
|
|
||||||
win32err = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, win32err = syscall.UTF16PtrFromString(path)
|
|
||||||
if win32err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procCreateVirtualDisk.Addr(), uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle)))
|
|
||||||
if r0 != 0 {
|
|
||||||
win32err = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procDetachVirtualDisk.Addr(), uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags))
|
|
||||||
if r0 != 0 {
|
|
||||||
win32err = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procGetVirtualDiskPhysicalPath.Addr(), uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer)))
|
|
||||||
if r0 != 0 {
|
|
||||||
win32err = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, win32err = syscall.UTF16PtrFromString(path)
|
|
||||||
if win32err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procOpenVirtualDisk.Addr(), uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
|
|
||||||
if r0 != 0 {
|
|
||||||
win32err = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,378 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package winio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
// Do the interface allocations only once for common
|
|
||||||
// Errno values.
|
|
||||||
const (
|
|
||||||
errnoERROR_IO_PENDING = 997
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
|
||||||
errERROR_EINVAL error = syscall.EINVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
// errnoErr returns common boxed Errno values, to prevent
|
|
||||||
// allocations at runtime.
|
|
||||||
func errnoErr(e syscall.Errno) error {
|
|
||||||
switch e {
|
|
||||||
case 0:
|
|
||||||
return errERROR_EINVAL
|
|
||||||
case errnoERROR_IO_PENDING:
|
|
||||||
return errERROR_IO_PENDING
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
|
||||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
|
||||||
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
|
||||||
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
|
||||||
|
|
||||||
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
|
||||||
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
|
||||||
procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW")
|
|
||||||
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
|
||||||
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
|
||||||
procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW")
|
|
||||||
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
|
||||||
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
|
||||||
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
|
||||||
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
|
||||||
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
|
||||||
procBackupRead = modkernel32.NewProc("BackupRead")
|
|
||||||
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
|
||||||
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
|
||||||
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
|
||||||
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
|
||||||
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
|
||||||
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
|
|
||||||
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
|
||||||
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
|
||||||
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
|
||||||
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
|
||||||
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
|
||||||
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
|
|
||||||
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
|
|
||||||
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
|
|
||||||
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
|
|
||||||
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
|
||||||
)
|
|
||||||
|
|
||||||
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
|
||||||
var _p0 uint32
|
|
||||||
if releaseAll {
|
|
||||||
_p0 = 1
|
|
||||||
}
|
|
||||||
r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
|
||||||
success = r0 != 0
|
|
||||||
if true {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertStringSidToSid(str *uint16, sid **byte) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procConvertStringSidToSidW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(sid)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func impersonateSelf(level uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, err = syscall.UTF16PtrFromString(accountName)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procLookupAccountSidW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, err = syscall.UTF16PtrFromString(systemName)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, err = syscall.UTF16PtrFromString(systemName)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _lookupPrivilegeName(_p0, luid, buffer, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, err = syscall.UTF16PtrFromString(systemName)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, err = syscall.UTF16PtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _lookupPrivilegeValue(_p0, _p1, luid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
|
||||||
var _p0 uint32
|
|
||||||
if openAsSelf {
|
|
||||||
_p0 = 1
|
|
||||||
}
|
|
||||||
r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func revertToSelf() (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr())
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
if len(b) > 0 {
|
|
||||||
_p0 = &b[0]
|
|
||||||
}
|
|
||||||
var _p1 uint32
|
|
||||||
if abort {
|
|
||||||
_p1 = 1
|
|
||||||
}
|
|
||||||
var _p2 uint32
|
|
||||||
if processSecurity {
|
|
||||||
_p2 = 1
|
|
||||||
}
|
|
||||||
r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
|
||||||
var _p0 *byte
|
|
||||||
if len(b) > 0 {
|
|
||||||
_p0 = &b[0]
|
|
||||||
}
|
|
||||||
var _p1 uint32
|
|
||||||
if abort {
|
|
||||||
_p1 = 1
|
|
||||||
}
|
|
||||||
var _p2 uint32
|
|
||||||
if processSecurity {
|
|
||||||
_p2 = 1
|
|
||||||
}
|
|
||||||
r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) {
|
|
||||||
r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount))
|
|
||||||
newport = windows.Handle(r0)
|
|
||||||
if newport == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, err = syscall.UTF16PtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) {
|
|
||||||
r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)))
|
|
||||||
handle = windows.Handle(r0)
|
|
||||||
if handle == windows.InvalidHandle {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func disconnectNamedPipe(pipe windows.Handle) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procDisconnectNamedPipe.Addr(), uintptr(pipe))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCurrentThread() (h windows.Handle) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr())
|
|
||||||
h = windows.Handle(r0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) {
|
|
||||||
r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)))
|
|
||||||
status = ntStatus(r0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl)))
|
|
||||||
status = ntStatus(r0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved))
|
|
||||||
status = ntStatus(r0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func rtlNtStatusToDosError(status ntStatus) (winerr error) {
|
|
||||||
r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status))
|
|
||||||
if r0 != 0 {
|
|
||||||
winerr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
|
||||||
var _p0 uint32
|
|
||||||
if wait {
|
|
||||||
_p0 = 1
|
|
||||||
}
|
|
||||||
r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)))
|
|
||||||
if r1 == 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
Language: Cpp
|
|
||||||
BasedOnStyle: Microsoft
|
|
||||||
BreakBeforeBraces: Attach
|
|
||||||
PointerAlignment: Left
|
|
||||||
AllowShortFunctionsOnASingleLine: All
|
|
||||||
# match Go style
|
|
||||||
IndentCaseLabels: false
|
|
||||||
# don't break comments over line limit (needed for CodeQL exceptions)
|
|
||||||
ReflowComments: false
|
|
||||||
InsertNewlineAtEOF: true
|
|
||||||
KeepEmptyLines:
|
|
||||||
AtEndOfFile: true
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
* text=auto eol=lf
|
|
||||||
vendor/** -text
|
|
||||||
test/vendor/** -text
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
# Binaries for programs and plugins
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.so
|
|
||||||
*.dylib
|
|
||||||
|
|
||||||
# Ignore vscode setting files
|
|
||||||
.vscode/
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Test binary, build with `go test -c`
|
|
||||||
*.test
|
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
|
||||||
*.out
|
|
||||||
|
|
||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
|
||||||
.glide/
|
|
||||||
|
|
||||||
# Ignore gcs bin directory
|
|
||||||
service/bin/
|
|
||||||
service/pkg/
|
|
||||||
|
|
||||||
*.img
|
|
||||||
*.vhd
|
|
||||||
*.tar.gz
|
|
||||||
*.tar
|
|
||||||
|
|
||||||
# Make stuff
|
|
||||||
.rootfs-done
|
|
||||||
bin/*
|
|
||||||
rootfs/*
|
|
||||||
rootfs-conv/*
|
|
||||||
*.o
|
|
||||||
/build/
|
|
||||||
|
|
||||||
deps/*
|
|
||||||
out/*
|
|
||||||
|
|
||||||
# protobuf files
|
|
||||||
# only files at root of the repo, otherwise this will cause issues with vendoring
|
|
||||||
/protobuf/*
|
|
||||||
|
|
||||||
# test results
|
|
||||||
test/results
|
|
||||||
|
|
||||||
# go workspace files
|
|
||||||
go.work
|
|
||||||
go.work.sum
|
|
||||||
|
|
||||||
# keys and related artifacts
|
|
||||||
*.pem
|
|
||||||
*.cose
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
run:
|
|
||||||
timeout: 8m
|
|
||||||
tests: true
|
|
||||||
build-tags:
|
|
||||||
- admin
|
|
||||||
- functional
|
|
||||||
- integration
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable:
|
|
||||||
# defaults:
|
|
||||||
# - errcheck
|
|
||||||
# - gosimple
|
|
||||||
# - govet
|
|
||||||
# - ineffassign
|
|
||||||
# - staticcheck
|
|
||||||
# - typecheck
|
|
||||||
# - unused
|
|
||||||
|
|
||||||
- errorlint # error wrapping (eg, not using `errors.Is`, using `%s` instead of `%w` in `fmt.Errorf`)
|
|
||||||
- gofmt # whether code was gofmt-ed
|
|
||||||
- govet # enabled by default, but just to be sure
|
|
||||||
- nolintlint # ill-formed or insufficient nolint directives
|
|
||||||
- stylecheck # golint replacement
|
|
||||||
- thelper # test helpers without t.Helper()
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
govet:
|
|
||||||
enable-all: true
|
|
||||||
disable:
|
|
||||||
# struct order is often for Win32 compat
|
|
||||||
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
|
||||||
- fieldalignment
|
|
||||||
|
|
||||||
stylecheck:
|
|
||||||
# https://staticcheck.io/docs/checks
|
|
||||||
checks: ["all"]
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude-dirs:
|
|
||||||
# paths are relative to module root
|
|
||||||
- cri-containerd/test-images
|
|
||||||
exclude-rules:
|
|
||||||
# err is very often shadowed in nested scopes
|
|
||||||
- linters:
|
|
||||||
- govet
|
|
||||||
text: '^shadow: declaration of "err" shadows declaration'
|
|
||||||
|
|
||||||
# path is relative to module root, which is ./test/
|
|
||||||
- path: cri-containerd
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "^ST1003: should not use underscores in package names$"
|
|
||||||
source: "^package cri_containerd$"
|
|
||||||
|
|
||||||
# don't bother with propper error wrapping in test code
|
|
||||||
- path: cri-containerd
|
|
||||||
linters:
|
|
||||||
- errorlint
|
|
||||||
text: "non-wrapping format verb for fmt.Errorf"
|
|
||||||
|
|
||||||
# This repo has a LOT of generated schema files, operating system bindings, and other
|
|
||||||
# things that ST1003 from stylecheck won't like (screaming case Windows api constants for example).
|
|
||||||
# There's also some structs that we *could* change the initialisms to be Go friendly
|
|
||||||
# (Id -> ID) but they're exported and it would be a breaking change.
|
|
||||||
# This makes it so that most new code, code that isn't supposed to be a pretty faithful
|
|
||||||
# mapping to an OS call/constants, or non-generated code still checks if we're following idioms,
|
|
||||||
# while ignoring the things that are just noise or would be more of a hassle than it'd be worth to change.
|
|
||||||
- path: layer.go
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: hcsshim.go
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: cmd\\ncproxy\\nodenetsvc\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: cmd\\ncproxy_mock\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\hcs\\schema2\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
- gofmt
|
|
||||||
|
|
||||||
- path: internal\\wclayer\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: hcn\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\hcs\\schema1\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\hns\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: ext4\\internal\\compactext4\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: ext4\\internal\\format\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\guestrequest\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\guest\\prot\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\windevice\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\winapi\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\vmcompute\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\regstate\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
- path: internal\\hcserror\\
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
||||||
# v0 APIs are deprecated, but still retained for backwards compatability
|
|
||||||
- path: cmd\\ncproxy\\
|
|
||||||
linters:
|
|
||||||
- staticcheck
|
|
||||||
text: "^SA1019: .*(ncproxygrpc|nodenetsvc)[/]?v0"
|
|
||||||
|
|
||||||
- path: internal\\tools\\networkagent
|
|
||||||
linters:
|
|
||||||
- staticcheck
|
|
||||||
text: "^SA1019: .*nodenetsvc[/]?v0"
|
|
||||||
|
|
||||||
- path: internal\\vhdx\\info
|
|
||||||
linters:
|
|
||||||
- stylecheck
|
|
||||||
text: "ST1003:"
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
* @microsoft/containerplat
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2015 Microsoft
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
include Makefile.bootfiles
|
|
||||||
|
|
||||||
GO:=go
|
|
||||||
GO_FLAGS:=-ldflags "-s -w" # strip Go binaries
|
|
||||||
CGO_ENABLED:=0
|
|
||||||
GOMODVENDOR:=
|
|
||||||
KMOD:=0
|
|
||||||
|
|
||||||
CFLAGS:=-O2 -Wall
|
|
||||||
LDFLAGS:=-static -s #strip C binaries
|
|
||||||
LDLIBS:=
|
|
||||||
PREPROCESSORFLAGS:=
|
|
||||||
ifeq "$(KMOD)" "1"
|
|
||||||
LDFLAGS:= -s
|
|
||||||
LDLIBS:= -lkmod
|
|
||||||
PREPROCESSORFLAGS:=-DMODULES=1
|
|
||||||
endif
|
|
||||||
|
|
||||||
GO_FLAGS_EXTRA:=
|
|
||||||
ifeq "$(GOMODVENDOR)" "1"
|
|
||||||
GO_FLAGS_EXTRA += -mod=vendor
|
|
||||||
endif
|
|
||||||
GO_BUILD_TAGS:=
|
|
||||||
ifneq ($(strip $(GO_BUILD_TAGS)),)
|
|
||||||
GO_FLAGS_EXTRA += -tags="$(GO_BUILD_TAGS)"
|
|
||||||
endif
|
|
||||||
GO_BUILD:=CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GO_FLAGS) $(GO_FLAGS_EXTRA)
|
|
||||||
|
|
||||||
SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST))))
|
|
||||||
# additional directories to search for rule prerequisites and targets
|
|
||||||
VPATH=$(SRCROOT)
|
|
||||||
|
|
||||||
# The link aliases for gcstools
|
|
||||||
GCS_TOOLS=\
|
|
||||||
generichook \
|
|
||||||
install-drivers
|
|
||||||
|
|
||||||
test:
|
|
||||||
cd $(SRCROOT) && $(GO) test -v ./internal/guest/...
|
|
||||||
|
|
||||||
# This target includes utilities which may be useful for testing purposes.
|
|
||||||
out/delta-dev.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report
|
|
||||||
rm -rf rootfs-dev
|
|
||||||
mkdir rootfs-dev
|
|
||||||
tar -xzf out/delta.tar.gz -C rootfs-dev
|
|
||||||
cp bin/internal/tools/snp-report rootfs-dev/bin/
|
|
||||||
tar -zcf $@ -C rootfs-dev .
|
|
||||||
rm -rf rootfs-dev
|
|
||||||
|
|
||||||
out/delta-snp.tar.gz: out/delta.tar.gz bin/internal/tools/snp-report boot/startup_v2056.sh boot/startup_simple.sh boot/startup.sh
|
|
||||||
rm -rf rootfs-snp
|
|
||||||
mkdir rootfs-snp
|
|
||||||
tar -xzf out/delta.tar.gz -C rootfs-snp
|
|
||||||
cp boot/startup_v2056.sh rootfs-snp/startup_v2056.sh
|
|
||||||
cp boot/startup_simple.sh rootfs-snp/startup_simple.sh
|
|
||||||
cp boot/startup.sh rootfs-snp/startup.sh
|
|
||||||
cp bin/internal/tools/snp-report rootfs-snp/bin/
|
|
||||||
chmod a+x rootfs-snp/startup_v2056.sh
|
|
||||||
chmod a+x rootfs-snp/startup_simple.sh
|
|
||||||
chmod a+x rootfs-snp/startup.sh
|
|
||||||
tar -zcf $@ -C rootfs-snp .
|
|
||||||
rm -rf rootfs-snp
|
|
||||||
|
|
||||||
out/delta.tar.gz: bin/init bin/vsockexec bin/cmd/gcs bin/cmd/gcstools bin/cmd/hooks/wait-paths Makefile
|
|
||||||
@mkdir -p out
|
|
||||||
rm -rf rootfs
|
|
||||||
mkdir -p rootfs/bin/
|
|
||||||
mkdir -p rootfs/info/
|
|
||||||
cp bin/init rootfs/
|
|
||||||
cp bin/vsockexec rootfs/bin/
|
|
||||||
cp bin/cmd/gcs rootfs/bin/
|
|
||||||
cp bin/cmd/gcstools rootfs/bin/
|
|
||||||
cp bin/cmd/hooks/wait-paths rootfs/bin/
|
|
||||||
for tool in $(GCS_TOOLS); do ln -s gcstools rootfs/bin/$$tool; done
|
|
||||||
git -C $(SRCROOT) rev-parse HEAD > rootfs/info/gcs.commit && \
|
|
||||||
git -C $(SRCROOT) rev-parse --abbrev-ref HEAD > rootfs/info/gcs.branch && \
|
|
||||||
date --iso-8601=minute --utc > rootfs/info/tar.date
|
|
||||||
$(if $(and $(realpath $(subst .tar,.testdata.json,$(BASE))), $(shell which jq)), \
|
|
||||||
jq -r '.IMAGE_NAME' $(subst .tar,.testdata.json,$(BASE)) 2>/dev/null > rootfs/info/image.name && \
|
|
||||||
jq -r '.DATETIME' $(subst .tar,.testdata.json,$(BASE)) 2>/dev/null > rootfs/info/build.date)
|
|
||||||
tar -zcf $@ -C rootfs .
|
|
||||||
rm -rf rootfs
|
|
||||||
|
|
||||||
bin/cmd/gcs bin/cmd/gcstools bin/cmd/hooks/wait-paths bin/cmd/tar2ext4 bin/internal/tools/snp-report:
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
GOOS=linux $(GO_BUILD) -o $@ $(SRCROOT)/$(@:bin/%=%)
|
|
||||||
|
|
||||||
bin/vsockexec: vsockexec/vsockexec.o vsockexec/vsock.o
|
|
||||||
@mkdir -p bin
|
|
||||||
$(CC) $(LDFLAGS) -o $@ $^
|
|
||||||
|
|
||||||
bin/init: init/init.o vsockexec/vsock.o
|
|
||||||
@mkdir -p bin
|
|
||||||
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
|
|
||||||
|
|
||||||
%.o: %.c
|
|
||||||
@mkdir -p $(dir $@)
|
|
||||||
$(CC) $(PREPROCESSORFLAGS) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
|
|
||||||
|
|
@ -1,197 +0,0 @@
|
||||||
BASE:=base.tar.gz
|
|
||||||
DEV_BUILD:=0
|
|
||||||
|
|
||||||
DELTA_TARGET=out/delta.tar.gz
|
|
||||||
|
|
||||||
ifeq "$(DEV_BUILD)" "1"
|
|
||||||
DELTA_TARGET=out/delta-dev.tar.gz
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifeq "$(SNP_BUILD)" "1"
|
|
||||||
DELTA_TARGET=out/delta-snp.tar.gz
|
|
||||||
endif
|
|
||||||
|
|
||||||
SRCROOT=$(dir $(abspath $(firstword $(MAKEFILE_LIST))))
|
|
||||||
|
|
||||||
PATH_PREFIX:=
|
|
||||||
# These have PATH_PREFIX prepended to obtain the full path in recipies e.g. $(PATH_PREFIX)/$(VMGS_TOOL)
|
|
||||||
VMGS_TOOL:=
|
|
||||||
IGVM_TOOL:=
|
|
||||||
KERNEL_PATH:=
|
|
||||||
TAR2EXT4_TOOL:=bin/cmd/tar2ext4
|
|
||||||
|
|
||||||
ROOTFS_DEVICE:=/dev/sda
|
|
||||||
HASH_DEVICE:=/dev/sdb
|
|
||||||
|
|
||||||
.PHONY: all always rootfs test snp simple
|
|
||||||
|
|
||||||
.DEFAULT_GOAL := all
|
|
||||||
|
|
||||||
all: out/initrd.img out/rootfs.tar.gz
|
|
||||||
|
|
||||||
clean:
|
|
||||||
find -name '*.o' -print0 | xargs -0 -r rm
|
|
||||||
rm -rf bin rootfs out
|
|
||||||
|
|
||||||
rootfs: out/rootfs.vhd
|
|
||||||
|
|
||||||
snp: out/kernel.vmgs out/rootfs-verity.vhd out/v2056.vmgs out/v2056combined.vmgs
|
|
||||||
|
|
||||||
simple: out/simple.vmgs snp
|
|
||||||
|
|
||||||
%.vmgs: %.bin
|
|
||||||
rm -f $@
|
|
||||||
# du -BM returns the size of the bin file in M, eg 7M. The sed command replaces the M with *1024*1024 and then bc does the math to convert to bytes
|
|
||||||
$(PATH_PREFIX)/$(VMGS_TOOL) create --filepath $@ --filesize `du -BM $< | sed "s/M.*/*1024*1024/" | bc`
|
|
||||||
$(PATH_PREFIX)/$(VMGS_TOOL) write --filepath $@ --datapath $< -i=8
|
|
||||||
|
|
||||||
# Simplest debug UVM used to test changes to the linux kernel. No dmverity protection. Boots an initramdisk rather than directly booting a vhd disk.
|
|
||||||
out/simple.bin: out/initrd.img $(PATH_PREFIX)/$(KERNEL_PATH) boot/startup_simple.sh
|
|
||||||
rm -f $@
|
|
||||||
python3 $(PATH_PREFIX)/$(IGVM_TOOL) \
|
|
||||||
-o $@ \
|
|
||||||
-kernel $(PATH_PREFIX)/$(KERNEL_PATH) \
|
|
||||||
-append "8250_core.nr_uarts=0 panic=-1 debug loglevel=7 rdinit=/startup_simple.sh" \
|
|
||||||
-rdinit out/initrd.img \
|
|
||||||
-vtl 0
|
|
||||||
|
|
||||||
# The boot performance is optimized by supplying rootfs as a SCSI attachment. In this case the kernel boots with
|
|
||||||
# dm-verity to ensure the integrity. Similar to layer VHDs the verity Merkle tree is appended to ext4 filesystem.
|
|
||||||
# It transpires that the /dev/sd* order is not deterministic wrt the scsi device order. Thus build a single userland
|
|
||||||
# fs + merkle tree device and boot that.
|
|
||||||
#
|
|
||||||
# From https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-init.html
|
|
||||||
#
|
|
||||||
# dm-mod.create=<name>,<uuid>,<minor>,<flags>,<table>[,<table>+][;<name>,<uuid>,<minor>,<flags>,<table>[,<table>+]+]
|
|
||||||
#
|
|
||||||
# where:
|
|
||||||
# <name> ::= The device name.
|
|
||||||
# <uuid> ::= xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ""
|
|
||||||
# <minor> ::= The device minor number | ""
|
|
||||||
# <flags> ::= "ro" | "rw"
|
|
||||||
# <table> ::= <start_sector> <num_sectors> <target_type> <target_args>
|
|
||||||
# <target_type> ::= "verity" | "linear" | ... (see list below)
|
|
||||||
#
|
|
||||||
# From https://docs.kernel.org/admin-guide/device-mapper/verity.html
|
|
||||||
# <version> <dev> <hash_dev>
|
|
||||||
# <data_block_size> <hash_block_size>
|
|
||||||
# <num_data_blocks> <hash_start_block>
|
|
||||||
# <algorithm> <digest> <salt>
|
|
||||||
# [<#opt_params> <opt_params>]
|
|
||||||
#
|
|
||||||
# typical igvm tool line once all the macros are expanded
|
|
||||||
# python3 /home/user/igvmfile.py -o out/v2056.bin -kernel /hose/user/bzImage -append "8250_core.nr_uarts=0 panic=-1 debug loglevel=9 ignore_loglevel dev.scsi.logging_level=9411 root=/dev/dm-0 dm-mod.create=\"dmverity,,,ro,0 196744 verity 1 /dev/sda /dev/sdb 4096 4096 24593 0 sha256 6d625a306aafdf73125a84388b7bfdd2c3a154bd8d698955f4adffc736bdfd66 b9065c23231f0d8901cc3a68e1d3b8d624213e76d6f9f6d3ccbcb829f9c710ba 1 ignore_corruption\" init=/startup_v2056.sh" -vtl 0
|
|
||||||
#
|
|
||||||
# so a kernel command line of:
|
|
||||||
# 8250_core.nr_uarts=0 panic=-1 debug loglevel=9 ignore_loglevel dev.scsi.logging_level=9411 root=/dev/dm-0 dm-mod.create=\"dmverity,,,ro,0 196744 verity 1 /dev/sda /dev/sdb 4096 4096 24593 0 sha256 6d625a306aafdf73125a84388b7bfdd2c3a154bd8d698955f4adffc736bdfd66 b9065c23231f0d8901cc3a68e1d3b8d624213e76d6f9f6d3ccbcb829f9c710ba 1 ignore_corruption\" init=/startup_v2056.sh
|
|
||||||
#
|
|
||||||
# and a dm-mod.create of:
|
|
||||||
# dmverity,,,ro,0 196744 verity 1 /dev/sda /dev/sdb 4096 4096 24593 0 sha256 6d625a306aafdf73125a84388b7bfdd2c3a154bd8d698955f4adffc736bdfd66 b9065c23231f0d8901cc3a68e1d3b8d624213e76d6f9f6d3ccbcb829f9c710ba 1 ignore_corruption
|
|
||||||
#
|
|
||||||
# which breaks down to:
|
|
||||||
#
|
|
||||||
# name = "dmverity"
|
|
||||||
# uuid = ""
|
|
||||||
# minor = ""
|
|
||||||
# flags = "ro"
|
|
||||||
# table = 0 196744 verity "args"
|
|
||||||
# start_sector = 0
|
|
||||||
# num_sectors = 196744
|
|
||||||
# target_type = verity
|
|
||||||
# target_args = 1 /dev/sda /dev/sdb 4096 4096 24593 0 sha256 6d625a306aafdf73125a84388b7bfdd2c3a154bd8d698955f4adffc736bdfd66 b9065c23231f0d8901cc3a68e1d3b8d624213e76d6f9f6d3ccbcb829f9c710ba 1 ignore_corruption
|
|
||||||
# args:
|
|
||||||
# version 1
|
|
||||||
# dev /dev/sda
|
|
||||||
# hash_dev /dev/sdb
|
|
||||||
# data_block_size 4096
|
|
||||||
# hash_block_size 4096
|
|
||||||
# num_data_blocks 24593
|
|
||||||
# hash_start_block 0
|
|
||||||
# algorithm sha256
|
|
||||||
# digest 6d625a306aafdf73125a84388b7bfdd2c3a154bd8d698955f4adffc736bdfd66
|
|
||||||
# salt b9065c23231f0d8901cc3a68e1d3b8d624213e76d6f9f6d3ccbcb829f9c710ba
|
|
||||||
# opt_params
|
|
||||||
# count = 1
|
|
||||||
# ignore_corruption
|
|
||||||
#
|
|
||||||
# combined typical (not bigger count of sectors for the whole device)
|
|
||||||
# dmverity,,,ro,0 199672 verity 1 /dev/sda /dev/sda 4096 4096 24959 24959 sha256 4aa6e79866ee946ddbd9cddd6554bc6449272942fcc65934326817785a3bd374 adc4956274489c936395bab046a2d476f21ef436e571ba53da2fdf3aee59bf0a
|
|
||||||
#
|
|
||||||
# A few notes:
|
|
||||||
# - num_sectors is the size of the final (aka target) verity device, i.e. the size of our rootfs excluding the Merkle
|
|
||||||
# tree.
|
|
||||||
# - We don't add verity superblock, so the <hash_start_block> will be exactly at the end of ext4 filesystem and equal
|
|
||||||
# to its size. In the case when verity superblock is present an extra block should be added to the offset value,
|
|
||||||
# i.e. 24959 becomes 24960.
|
|
||||||
|
|
||||||
|
|
||||||
# Debug build for use with uvmtester. UVM with dm-verity protected vhd disk mounted directly via the kernel command line.
|
|
||||||
# Ignores corruption in dm-verity protected disk. (Use dmesg to see if dm-verity is ignoring data corruption.)
|
|
||||||
out/v2056.bin: out/rootfs.vhd out/rootfs.hash.vhd $(PATH_PREFIX)/$(KERNEL_PATH) out/rootfs.hash.datasectors out/rootfs.hash.datablocksize out/rootfs.hash.hashblocksize out/rootfs.hash.datablocks out/rootfs.hash.rootdigest out/rootfs.hash.salt boot/startup_v2056.sh
|
|
||||||
rm -f $@
|
|
||||||
python3 $(PATH_PREFIX)/$(IGVM_TOOL) \
|
|
||||||
-o $@ \
|
|
||||||
-kernel $(PATH_PREFIX)/$(KERNEL_PATH) \
|
|
||||||
-append "8250_core.nr_uarts=0 panic=-1 debug loglevel=9 root=/dev/dm-0 dm-mod.create=\"dmverity,,,ro,0 $(shell cat out/rootfs.hash.datasectors) verity 1 $(ROOTFS_DEVICE) $(HASH_DEVICE) $(shell cat out/rootfs.hash.datablocksize) $(shell cat out/rootfs.hash.hashblocksize) $(shell cat out/rootfs.hash.datablocks) $(shell cat out/rootfs.hash.datablocks) sha256 $(shell cat out/rootfs.hash.rootdigest) $(shell cat out/rootfs.hash.salt) 1 ignore_corruption\" init=/startup_v2056.sh" \
|
|
||||||
-vtl 0
|
|
||||||
|
|
||||||
out/v2056combined.bin: out/rootfs-verity.vhd $(PATH_PREFIX)/$(KERNEL_PATH) out/rootfs.hash.datablocksize out/rootfs.hash.hashblocksize out/rootfs.hash.datablocks out/rootfs.hash.rootdigest out/rootfs.hash.salt boot/startup_v2056.sh
|
|
||||||
rm -f $@
|
|
||||||
echo root=/dev/dm-0 dm-mod.create=\"dmverity,,,ro,0 $(shell cat out/rootfs.hash.datasectors) verity 1 $(ROOTFS_DEVICE) $(ROOTFS_DEVICE) $(shell cat out/rootfs.hash.datablocksize) $(shell cat out/rootfs.hash.hashblocksize) $(shell cat out/rootfs.hash.datablocks) $(shell cat out/rootfs.hash.datablocks) sha256 $(shell cat out/rootfs.hash.rootdigest) $(shell cat out/rootfs.hash.salt) 1 ignore_corruption\"
|
|
||||||
python3 $(PATH_PREFIX)/$(IGVM_TOOL) \
|
|
||||||
-o $@ \
|
|
||||||
-kernel $(PATH_PREFIX)/$(KERNEL_PATH) \
|
|
||||||
-append "8250_core.nr_uarts=0 panic=-1 debug loglevel=9 ignore_loglevel dev.scsi.logging_level=9411 root=/dev/dm-0 dm-mod.create=\"dmverity,,,ro,0 $(shell cat out/rootfs.hash.datasectors) verity 1 $(ROOTFS_DEVICE) $(ROOTFS_DEVICE) $(shell cat out/rootfs.hash.datablocksize) $(shell cat out/rootfs.hash.hashblocksize) $(shell cat out/rootfs.hash.datablocks) $(shell cat out/rootfs.hash.datablocks) sha256 $(shell cat out/rootfs.hash.rootdigest) $(shell cat out/rootfs.hash.salt) 1 ignore_corruption\" init=/startup_v2056.sh" \
|
|
||||||
-vtl 0
|
|
||||||
|
|
||||||
# Full UVM with dm-verity protected vhd disk mounted directly via the kernel command line.
|
|
||||||
out/kernel.bin: out/rootfs-verity.vhd $(PATH_PREFIX)/$(KERNEL_PATH) out/rootfs.hash.datasectors out/rootfs.hash.datablocksize out/rootfs.hash.hashblocksize out/rootfs.hash.datablocks out/rootfs.hash.rootdigest out/rootfs.hash.salt boot/startup.sh
|
|
||||||
rm -f $@
|
|
||||||
echo root=/dev/dm-0 dm-mod.create=\"dmverity,,,ro,0 $(shell cat out/rootfs.hash.datasectors) verity 1 $(ROOTFS_DEVICE) $(ROOTFS_DEVICE) $(shell cat out/rootfs.hash.datablocksize) $(shell cat out/rootfs.hash.hashblocksize) $(shell cat out/rootfs.hash.datablocks) $(shell cat out/rootfs.hash.datablocks) sha256 $(shell cat out/rootfs.hash.rootdigest) $(shell cat out/rootfs.hash.salt)\"
|
|
||||||
python3 $(PATH_PREFIX)/$(IGVM_TOOL) \
|
|
||||||
-o $@ \
|
|
||||||
-kernel $(PATH_PREFIX)/$(KERNEL_PATH) \
|
|
||||||
-append "8250_core.nr_uarts=0 panic=-1 debug loglevel=7 root=/dev/dm-0 dm-mod.create=\"dmverity,,,ro,0 $(shell cat out/rootfs.hash.datasectors) verity 1 $(ROOTFS_DEVICE) $(ROOTFS_DEVICE) $(shell cat out/rootfs.hash.datablocksize) $(shell cat out/rootfs.hash.hashblocksize) $(shell cat out/rootfs.hash.datablocks) $(shell cat out/rootfs.hash.datablocks) sha256 $(shell cat out/rootfs.hash.rootdigest) $(shell cat out/rootfs.hash.salt)\" init=/startup.sh" \
|
|
||||||
-vtl 0
|
|
||||||
|
|
||||||
# Rule to make a vhd from a file. This is used to create the rootfs.hash.vhd from rootfs.hash.
|
|
||||||
%.vhd: % $(TAR2EXT4_TOOL)
|
|
||||||
$(TAR2EXT4_TOOL) -only-vhd -i $< -o $@
|
|
||||||
|
|
||||||
# Rule to make a vhd from an ext4 file. This is used to create the rootfs.vhd from rootfs.ext4.
|
|
||||||
%.vhd: %.ext4 $(TAR2EXT4_TOOL)
|
|
||||||
$(TAR2EXT4_TOOL) -only-vhd -i $< -o $@
|
|
||||||
|
|
||||||
%.hash %.hash.info %.hash.datablocks %.hash.rootdigest %hash.datablocksize %.hash.datasectors %.hash.hashblocksize: %.ext4 %.hash.salt
|
|
||||||
veritysetup format --no-superblock --salt $(shell cat out/rootfs.hash.salt) $< $*.hash > $*.hash.info
|
|
||||||
# Retrieve info required by dm-verity at boot time
|
|
||||||
# Get the blocksize of rootfs
|
|
||||||
cat $*.hash.info | awk '/^Root hash:/{ print $$3 }' > $*.hash.rootdigest
|
|
||||||
cat $*.hash.info | awk '/^Salt:/{ print $$2 }' > $*.hash.salt
|
|
||||||
cat $*.hash.info | awk '/^Data block size:/{ print $$4 }' > $*.hash.datablocksize
|
|
||||||
cat $*.hash.info | awk '/^Hash block size:/{ print $$4 }' > $*.hash.hashblocksize
|
|
||||||
cat $*.hash.info | awk '/^Data blocks:/{ print $$3 }' > $*.hash.datablocks
|
|
||||||
echo $$(( $$(cat $*.hash.datablocks) * $$(cat $*.hash.datablocksize) / 512 )) > $*.hash.datasectors
|
|
||||||
|
|
||||||
out/rootfs.hash.salt:
|
|
||||||
hexdump -vn32 -e'8/4 "%08X" 1 "\n"' /dev/random > $@
|
|
||||||
|
|
||||||
out/rootfs.ext4: out/rootfs.tar.gz $(TAR2EXT4_TOOL)
|
|
||||||
gzip -f -d ./out/rootfs.tar.gz
|
|
||||||
$(TAR2EXT4_TOOL) -i ./out/rootfs.tar -o $@
|
|
||||||
|
|
||||||
out/rootfs-verity.ext4: out/rootfs.ext4 out/rootfs.hash
|
|
||||||
cp out/rootfs.ext4 $@
|
|
||||||
cat out/rootfs.hash >> $@
|
|
||||||
|
|
||||||
out/rootfs.tar.gz: out/initrd.img
|
|
||||||
rm -rf rootfs-conv
|
|
||||||
mkdir rootfs-conv
|
|
||||||
gunzip -c out/initrd.img | (cd rootfs-conv && cpio -imd)
|
|
||||||
tar -zcf $@ -C rootfs-conv .
|
|
||||||
rm -rf rootfs-conv
|
|
||||||
|
|
||||||
out/initrd.img: $(BASE) $(DELTA_TARGET) $(SRCROOT)/hack/catcpio.sh
|
|
||||||
$(SRCROOT)/hack/catcpio.sh "$(BASE)" $(DELTA_TARGET) > out/initrd.img.uncompressed
|
|
||||||
gzip -c out/initrd.img.uncompressed > $@
|
|
||||||
rm out/initrd.img.uncompressed
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
version = "2"
|
|
||||||
generators = ["go", "go-grpc"]
|
|
||||||
|
|
||||||
# Control protoc include paths.
|
|
||||||
[includes]
|
|
||||||
before = ["./protobuf"]
|
|
||||||
|
|
||||||
# defaults are "/usr/local/include" and "/usr/include", which don't exist on Windows.
|
|
||||||
# override defaults to supress errors about non-existant directories.
|
|
||||||
after = []
|
|
||||||
|
|
||||||
# This section maps protobuf imports to Go packages.
|
|
||||||
[packages]
|
|
||||||
# github.com/containerd/cgroups protofiles still list their go path as "github.com/containerd/cgroups/cgroup1/stats"
|
|
||||||
"github.com/containerd/cgroups/v3/cgroup1/stats/metrics.proto" = "github.com/containerd/cgroups/v3/cgroup1/stats"
|
|
||||||
|
|
||||||
[[overrides]]
|
|
||||||
prefixes = [
|
|
||||||
"github.com/Microsoft/hcsshim/internal/shimdiag",
|
|
||||||
"github.com/Microsoft/hcsshim/internal/extendedtask",
|
|
||||||
"github.com/Microsoft/hcsshim/internal/computeagent",
|
|
||||||
"github.com/Microsoft/hcsshim/internal/ncproxyttrpc",
|
|
||||||
"github.com/Microsoft/hcsshim/internal/vmservice",
|
|
||||||
]
|
|
||||||
generators = ["go", "go-ttrpc"]
|
|
||||||
|
|
@ -1,170 +0,0 @@
|
||||||
# hcsshim
|
|
||||||
|
|
||||||
[](https://github.com/microsoft/hcsshim/actions?query=branch%3Amaster)
|
|
||||||
|
|
||||||
This package contains the Golang interface for using the Windows [Host Compute Service](https://techcommunity.microsoft.com/t5/containers/introducing-the-host-compute-service-hcs/ba-p/382332) (HCS) to launch and manage [Windows Containers](https://docs.microsoft.com/en-us/virtualization/windowscontainers/about/). It also contains other helpers and functions for managing Windows Containers such as the Golang interface for the Host Network Service (HNS), as well as code for the [guest agent](./internal/guest/README.md) (commonly referred to as the GCS or Guest Compute Service in the codebase) used to support running Linux Hyper-V containers.
|
|
||||||
|
|
||||||
It is primarily used in the [Moby](https://github.com/moby/moby) and [Containerd](https://github.com/containerd/containerd) projects, but it can be freely used by other projects as well.
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
While this repository can be used as a library of sorts to call the HCS apis, there are a couple binaries built out of the repository as well. The main ones being the Linux guest agent, and an implementation of the [runtime v2 containerd shim api](https://github.com/containerd/containerd/blob/master/runtime/v2/README.md).
|
|
||||||
|
|
||||||
### Linux Hyper-V Container Guest Agent
|
|
||||||
|
|
||||||
To build the Linux guest agent itself all that's needed is to set your GOOS to "Linux" and build out of ./cmd/gcs.
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
C:\> $env:GOOS="linux"
|
|
||||||
C:\> go build .\cmd\gcs\
|
|
||||||
```
|
|
||||||
|
|
||||||
or on a Linux machine
|
|
||||||
|
|
||||||
```sh
|
|
||||||
> go build ./cmd/gcs
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want it to be packaged inside of a rootfs to boot with alongside all of the other tools then you'll need to provide a rootfs that it can be packaged inside of. An easy way is to export the rootfs of a container.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker pull busybox
|
|
||||||
docker run --name base_image_container busybox
|
|
||||||
docker export base_image_container | gzip > base.tar.gz
|
|
||||||
BASE=./base.tar.gz
|
|
||||||
make all
|
|
||||||
```
|
|
||||||
|
|
||||||
If the build is successful, in the `./out` folder you should see:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
> ls ./out/
|
|
||||||
delta.tar.gz initrd.img rootfs.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### Containerd Shim
|
|
||||||
|
|
||||||
For info on the [Runtime V2 API](https://github.com/containerd/containerd/blob/main/core/runtime/v2/README.md).
|
|
||||||
|
|
||||||
Contrary to the typical Linux architecture of shim -> runc, the runhcs shim is used both to launch and manage the lifetime of containers.
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
C:\> $env:GOOS="windows"
|
|
||||||
C:\> go build .\cmd\containerd-shim-runhcs-v1
|
|
||||||
```
|
|
||||||
|
|
||||||
Then place the binary in the same directory that Containerd is located at in your environment.
|
|
||||||
A default Containerd configuration file can be generated by running:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\containerd.exe config default | Out-File "C:\Program Files\containerd\config.toml" -Encoding ascii
|
|
||||||
```
|
|
||||||
|
|
||||||
This config file will already have the shim set as the default runtime for cri interactions.
|
|
||||||
|
|
||||||
To trial using the shim out with ctr.exe:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
C:\> ctr.exe run --runtime io.containerd.runhcs.v1 --rm mcr.microsoft.com/windows/nanoserver:2004 windows-test cmd /c "echo Hello World!"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
|
||||||
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
|
|
||||||
the rights to use your contribution. For details, visit [Microsoft CLA](https://cla.microsoft.com).
|
|
||||||
|
|
||||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
|
|
||||||
a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
|
|
||||||
provided by the bot. You will only need to do this once across all repos using our CLA.
|
|
||||||
|
|
||||||
We require that contributors sign their commits
|
|
||||||
to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
|
||||||
|
|
||||||
We also require that contributors sign their commits using using [`git commit --signoff`][git-commit-s]
|
|
||||||
to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
|
||||||
A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s].
|
|
||||||
|
|
||||||
Please see [the developer certificate](https://developercertificate.org) for more info,
|
|
||||||
as well as to make sure that you can attest to the rules listed.
|
|
||||||
Our CI uses the [DCO Github app](https://github.com/apps/dco) to ensure that all commits in a given PR are signed-off.
|
|
||||||
|
|
||||||
### Linting
|
|
||||||
|
|
||||||
Code must pass a linting stage, which uses [`golangci-lint`][lint].
|
|
||||||
Since `./test` is a separate Go module, the linter is run from both the root and the
|
|
||||||
`test` directories. Additionally, the linter is run with `GOOS` set to both `windows` and
|
|
||||||
`linux`.
|
|
||||||
|
|
||||||
The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run
|
|
||||||
automatically with VSCode by adding the following to your workspace or folder settings:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"go.lintTool": "golangci-lint",
|
|
||||||
"go.lintOnSave": "package",
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional editor [integrations options are also available][lint-ide].
|
|
||||||
|
|
||||||
Alternatively, `golangci-lint` can be [installed][lint-install] and run locally:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# use . or specify a path to only lint a package
|
|
||||||
# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0"
|
|
||||||
> golangci-lint run
|
|
||||||
```
|
|
||||||
|
|
||||||
To run across the entire repo for both `GOOS=windows` and `linux`:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
> foreach ( $goos in ('windows', 'linux') ) {
|
|
||||||
foreach ( $repo in ('.', 'test') ) {
|
|
||||||
pwsh -Command "cd $repo && go env -w GOOS=$goos && golangci-lint.exe run --verbose"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Go Generate
|
|
||||||
|
|
||||||
The pipeline checks that auto-generated code, via `go generate`, are up to date.
|
|
||||||
Similar to the [linting stage](#linting), `go generate` is run in both the root and test Go modules.
|
|
||||||
|
|
||||||
This can be done via:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
> go generate ./...
|
|
||||||
> cd test && go generate ./...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
|
||||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
|
||||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
This project requires Golang 1.18 or newer to build.
|
|
||||||
|
|
||||||
For system requirements to run this project, see the Microsoft docs on [Windows Container requirements](https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/system-requirements).
|
|
||||||
|
|
||||||
## Reporting Security Issues
|
|
||||||
|
|
||||||
Security issues and bugs should be reported privately, via email, to the Microsoft Security
|
|
||||||
Response Center (MSRC) at [secure@microsoft.com](mailto:secure@microsoft.com). You should
|
|
||||||
receive a response within 24 hours. If for some reason you do not, please follow up via
|
|
||||||
email to ensure we received your original message. Further information, including the
|
|
||||||
[MSRC PGP](https://technet.microsoft.com/en-us/security/dn606155) key, can be found in
|
|
||||||
the [Security TechCenter](https://technet.microsoft.com/en-us/security/default).
|
|
||||||
|
|
||||||
For additional details, see [Report a Computer Security Vulnerability](https://technet.microsoft.com/en-us/security/ff852094.aspx) on Technet
|
|
||||||
|
|
||||||
---------------
|
|
||||||
Copyright (c) 2018 Microsoft Corp. All rights reserved.
|
|
||||||
|
|
||||||
[lint]: https://golangci-lint.run/
|
|
||||||
[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration
|
|
||||||
[lint-install]: https://golangci-lint.run/usage/install/#local-installation
|
|
||||||
|
|
||||||
[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s
|
|
||||||
[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
|
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
|
||||||
|
|
||||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
|
||||||
|
|
||||||
## Reporting Security Issues
|
|
||||||
|
|
||||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
|
||||||
|
|
||||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
|
||||||
|
|
||||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
|
||||||
|
|
||||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
|
||||||
|
|
||||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
|
||||||
|
|
||||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
|
||||||
* Full paths of source file(s) related to the manifestation of the issue
|
|
||||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
|
||||||
* Any special configuration required to reproduce the issue
|
|
||||||
* Step-by-step instructions to reproduce the issue
|
|
||||||
* Proof-of-concept or exploit code (if possible)
|
|
||||||
* Impact of the issue, including how an attacker might exploit the issue
|
|
||||||
|
|
||||||
This information will help us triage your report more quickly.
|
|
||||||
|
|
||||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
|
||||||
|
|
||||||
## Preferred Languages
|
|
||||||
|
|
||||||
We prefer all communications to be in English.
|
|
||||||
|
|
||||||
## Policy
|
|
||||||
|
|
||||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
|
||||||
|
|
||||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
// AttachLayerStorageFilter sets up the layer storage filter on a writable
|
|
||||||
// container layer.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory the writable layer is mounted. If the
|
|
||||||
// path does not end in a `\` the platform will append it automatically.
|
|
||||||
//
|
|
||||||
// `layerData` is the parent read-only layer data.
|
|
||||||
func AttachLayerStorageFilter(ctx context.Context, layerPath string, layerData LayerData) (err error) {
|
|
||||||
title := "hcsshim::AttachLayerStorageFilter"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("layerPath", layerPath),
|
|
||||||
)
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(layerData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hcsAttachLayerStorageFilter(layerPath, string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to attach layer storage filter")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AttachOverlayFilter sets up a filter of the given type on a writable container layer. Currently the only
|
|
||||||
// supported filter types are WCIFS & UnionFS (defined in internal/hcs/schema2/layer.go)
|
|
||||||
//
|
|
||||||
// `volumePath` is volume path at which writable layer is mounted. If the
|
|
||||||
// path does not end in a `\` the platform will append it automatically.
|
|
||||||
//
|
|
||||||
// `layerData` is the parent read-only layer data.
|
|
||||||
func AttachOverlayFilter(ctx context.Context, volumePath string, layerData LayerData) (err error) {
|
|
||||||
title := "hcsshim::AttachOverlayFilter"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("volumePath", volumePath),
|
|
||||||
)
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(layerData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hcsAttachOverlayFilter(volumePath, string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to attach overlay filter")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DestroyLayer deletes a container layer.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory containing the layer to export.
|
|
||||||
func DestroyLayer(ctx context.Context, layerPath string) (err error) {
|
|
||||||
title := "hcsshim::DestroyLayer"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(trace.StringAttribute("layerPath", layerPath))
|
|
||||||
|
|
||||||
err = hcsDestroyLayer(layerPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to destroy layer")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DetachLayerStorageFilter detaches the layer storage filter on a writable container layer.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory containing the layer to export.
|
|
||||||
func DetachLayerStorageFilter(ctx context.Context, layerPath string) (err error) {
|
|
||||||
title := "hcsshim::DetachLayerStorageFilter"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(trace.StringAttribute("layerPath", layerPath))
|
|
||||||
|
|
||||||
err = hcsDetachLayerStorageFilter(layerPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to detach layer storage filter")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DetachOverlayFilter detaches the filter on a writable container layer.
|
|
||||||
//
|
|
||||||
// `volumePath` is a path to writable container volume.
|
|
||||||
func DetachOverlayFilter(ctx context.Context, volumePath string, filterType hcsschema.FileSystemFilterType) (err error) {
|
|
||||||
title := "hcsshim::DetachOverlayFilter"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(trace.StringAttribute("volumePath", volumePath))
|
|
||||||
|
|
||||||
layerData := LayerData{}
|
|
||||||
layerData.FilterType = filterType
|
|
||||||
bytes, err := json.Marshal(layerData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hcsDetachOverlayFilter(volumePath, string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to detach overlay filter")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExportLayer exports a container layer.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory containing the layer to export.
|
|
||||||
//
|
|
||||||
// `exportFolderPath` is a pre-existing folder to export the layer to.
|
|
||||||
//
|
|
||||||
// `layerData` is the parent layer data.
|
|
||||||
//
|
|
||||||
// `options` are the export options applied to the exported layer.
|
|
||||||
func ExportLayer(ctx context.Context, layerPath, exportFolderPath string, layerData LayerData, options ExportLayerOptions) (err error) {
|
|
||||||
title := "hcsshim::ExportLayer"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("layerPath", layerPath),
|
|
||||||
trace.StringAttribute("exportFolderPath", exportFolderPath),
|
|
||||||
)
|
|
||||||
|
|
||||||
ldBytes, err := json.Marshal(layerData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
oBytes, err := json.Marshal(options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hcsExportLayer(layerPath, exportFolderPath, string(ldBytes), string(oBytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to export layer")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FormatWritableLayerVhd formats a virtual disk for use as a writable container layer.
|
|
||||||
//
|
|
||||||
// If the VHD is not mounted it will be temporarily mounted.
|
|
||||||
//
|
|
||||||
// NOTE: This API had a breaking change in the operating system after Windows Server 2019.
|
|
||||||
// On ws2019 the API expects to get passed a file handle from CreateFile for the vhd that
|
|
||||||
// the caller wants to format. On > ws2019, its expected that the caller passes a vhd handle
|
|
||||||
// that can be obtained from the virtdisk APIs.
|
|
||||||
func FormatWritableLayerVhd(ctx context.Context, vhdHandle windows.Handle) (err error) {
|
|
||||||
title := "hcsshim::FormatWritableLayerVhd"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
|
|
||||||
err = hcsFormatWritableLayerVhd(vhdHandle)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to format writable layer vhd")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,199 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/vhd"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/memory"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/security"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
defaultVHDXBlockSizeInMB = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetupContainerBaseLayer is a helper to setup a containers scratch. It
|
|
||||||
// will create and format the vhdx's inside and the size is configurable with the sizeInGB
|
|
||||||
// parameter.
|
|
||||||
//
|
|
||||||
// `layerPath` is the path to the base container layer on disk.
|
|
||||||
//
|
|
||||||
// `baseVhdPath` is the path to where the base vhdx for the base layer should be created.
|
|
||||||
//
|
|
||||||
// `diffVhdPath` is the path where the differencing disk for the base layer should be created.
|
|
||||||
//
|
|
||||||
// `sizeInGB` is the size in gigabytes to make the base vhdx.
|
|
||||||
func SetupContainerBaseLayer(ctx context.Context, layerPath, baseVhdPath, diffVhdPath string, sizeInGB uint64) (err error) {
|
|
||||||
var (
|
|
||||||
hivesPath = filepath.Join(layerPath, "Hives")
|
|
||||||
layoutPath = filepath.Join(layerPath, "Layout")
|
|
||||||
)
|
|
||||||
|
|
||||||
// We need to remove the hives directory and layout file as `SetupBaseOSLayer` fails if these files
|
|
||||||
// already exist. `SetupBaseOSLayer` will create these files internally. We also remove the base and
|
|
||||||
// differencing disks if they exist in case we're asking for a different size.
|
|
||||||
if _, err := os.Stat(hivesPath); err == nil {
|
|
||||||
if err := os.RemoveAll(hivesPath); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to remove prexisting hives directory")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(layoutPath); err == nil {
|
|
||||||
if err := os.RemoveAll(layoutPath); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to remove prexisting layout file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(baseVhdPath); err == nil {
|
|
||||||
if err := os.RemoveAll(baseVhdPath); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to remove base vhdx path")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(diffVhdPath); err == nil {
|
|
||||||
if err := os.RemoveAll(diffVhdPath); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to remove differencing vhdx")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createParams := &vhd.CreateVirtualDiskParameters{
|
|
||||||
Version: 2,
|
|
||||||
Version2: vhd.CreateVersion2{
|
|
||||||
MaximumSize: sizeInGB * memory.GiB,
|
|
||||||
BlockSizeInBytes: defaultVHDXBlockSizeInMB * memory.MiB,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create vhdx")
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = syscall.CloseHandle(handle)
|
|
||||||
os.RemoveAll(baseVhdPath)
|
|
||||||
os.RemoveAll(diffVhdPath)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = FormatWritableLayerVhd(ctx, windows.Handle(handle)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Base vhd handle must be closed before calling SetupBaseLayer in case of Container layer
|
|
||||||
if err = syscall.CloseHandle(handle); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to close vhdx handle")
|
|
||||||
}
|
|
||||||
|
|
||||||
options := OsLayerOptions{
|
|
||||||
Type: OsLayerTypeContainer,
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetupBaseOSLayer expects an empty vhd handle for a container layer and will
|
|
||||||
// error out otherwise.
|
|
||||||
if err = SetupBaseOSLayer(ctx, layerPath, 0, options); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Create the differencing disk that will be what's copied for the final rw layer
|
|
||||||
// for a container.
|
|
||||||
if err = vhd.CreateDiffVhd(diffVhdPath, baseVhdPath, defaultVHDXBlockSizeInMB); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create differencing disk")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = security.GrantVmGroupAccess(baseVhdPath); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to grant vm group access to %s", baseVhdPath)
|
|
||||||
}
|
|
||||||
if err = security.GrantVmGroupAccess(diffVhdPath); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to grant vm group access to %s", diffVhdPath)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetupUtilityVMBaseLayer is a helper to setup a UVMs scratch space. It will create and format
|
|
||||||
// the vhdx inside and the size is configurable by the sizeInGB parameter.
|
|
||||||
//
|
|
||||||
// `uvmPath` is the path to the UtilityVM filesystem.
|
|
||||||
//
|
|
||||||
// `baseVhdPath` is the path to where the base vhdx for the UVM should be created.
|
|
||||||
//
|
|
||||||
// `diffVhdPath` is the path where the differencing disk for the UVM should be created.
|
|
||||||
//
|
|
||||||
// `sizeInGB` specifies the size in gigabytes to make the base vhdx.
|
|
||||||
func SetupUtilityVMBaseLayer(ctx context.Context, uvmPath, baseVhdPath, diffVhdPath string, sizeInGB uint64) (err error) {
|
|
||||||
// Remove the base and differencing disks if they exist in case we're asking for a different size.
|
|
||||||
if _, err := os.Stat(baseVhdPath); err == nil {
|
|
||||||
if err := os.RemoveAll(baseVhdPath); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to remove base vhdx")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(diffVhdPath); err == nil {
|
|
||||||
if err := os.RemoveAll(diffVhdPath); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to remove differencing vhdx")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just create the vhdx for utilityVM layer, no need to format it.
|
|
||||||
createParams := &vhd.CreateVirtualDiskParameters{
|
|
||||||
Version: 2,
|
|
||||||
Version2: vhd.CreateVersion2{
|
|
||||||
MaximumSize: sizeInGB * memory.GiB,
|
|
||||||
BlockSizeInBytes: defaultVHDXBlockSizeInMB * memory.MiB,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
handle, err := vhd.CreateVirtualDisk(baseVhdPath, vhd.VirtualDiskAccessNone, vhd.CreateVirtualDiskFlagNone, createParams)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create vhdx")
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
_ = syscall.CloseHandle(handle)
|
|
||||||
os.RemoveAll(baseVhdPath)
|
|
||||||
os.RemoveAll(diffVhdPath)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// If it is a UtilityVM layer then the base vhdx must be attached when calling
|
|
||||||
// `SetupBaseOSLayer`
|
|
||||||
attachParams := &vhd.AttachVirtualDiskParameters{
|
|
||||||
Version: 2,
|
|
||||||
}
|
|
||||||
if err := vhd.AttachVirtualDisk(handle, vhd.AttachVirtualDiskFlagNone, attachParams); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to attach virtual disk")
|
|
||||||
}
|
|
||||||
|
|
||||||
options := OsLayerOptions{
|
|
||||||
Type: OsLayerTypeVM,
|
|
||||||
}
|
|
||||||
if err := SetupBaseOSLayer(ctx, uvmPath, windows.Handle(handle), options); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detach and close the handle after setting up the layer as we don't need the handle
|
|
||||||
// for anything else and we no longer need to be attached either.
|
|
||||||
if err = vhd.DetachVirtualDisk(handle); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to detach vhdx")
|
|
||||||
}
|
|
||||||
if err = syscall.CloseHandle(handle); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to close vhdx handle")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the differencing disk that will be what's copied for the final rw layer
|
|
||||||
// for a container.
|
|
||||||
if err = vhd.CreateDiffVhd(diffVhdPath, baseVhdPath, defaultVHDXBlockSizeInMB); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to create differencing disk")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := security.GrantVmGroupAccess(baseVhdPath); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to grant vm group access to %s", baseVhdPath)
|
|
||||||
}
|
|
||||||
if err := security.GrantVmGroupAccess(diffVhdPath); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to grant vm group access to %s", diffVhdPath)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImportLayer imports a container layer.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory to import the layer to. If the directory
|
|
||||||
// does not exist it will be automatically created.
|
|
||||||
//
|
|
||||||
// `sourceFolderpath` is a pre-existing folder that contains the layer to
|
|
||||||
// import.
|
|
||||||
//
|
|
||||||
// `layerData` is the parent layer data.
|
|
||||||
func ImportLayer(ctx context.Context, layerPath, sourceFolderPath string, layerData LayerData) (err error) {
|
|
||||||
title := "hcsshim::ImportLayer"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("layerPath", layerPath),
|
|
||||||
trace.StringAttribute("sourceFolderPath", sourceFolderPath),
|
|
||||||
)
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(layerData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hcsImportLayer(layerPath, sourceFolderPath, string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to import layer")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitializeWritableLayer initializes a writable layer for a container.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory the layer is mounted. If the
|
|
||||||
// path does not end in a `\` the platform will append it automatically.
|
|
||||||
//
|
|
||||||
// `layerData` is the parent read-only layer data.
|
|
||||||
func InitializeWritableLayer(ctx context.Context, layerPath string, layerData LayerData) (err error) {
|
|
||||||
title := "hcsshim::InitializeWritableLayer"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("layerPath", layerPath),
|
|
||||||
)
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(layerData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options are not used in the platform as of RS5
|
|
||||||
err = hcsInitializeWritableLayer(layerPath, string(bytes), "")
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to intitialize container layer")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/interop"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetLayerVhdMountPath returns the volume path for a virtual disk of a writable container layer.
|
|
||||||
func GetLayerVhdMountPath(ctx context.Context, vhdHandle windows.Handle) (path string, err error) {
|
|
||||||
title := "hcsshim::GetLayerVhdMountPath"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
|
|
||||||
var mountPath *uint16
|
|
||||||
err = hcsGetLayerVhdMountPath(vhdHandle, &mountPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "failed to get vhd mount path")
|
|
||||||
}
|
|
||||||
path = interop.ConvertAndFreeCoTaskMemString(mountPath)
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/Microsoft/hcsshim/osversion"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetupBaseOSLayer sets up a layer that contains a base OS for a container.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory containing the layer.
|
|
||||||
//
|
|
||||||
// `vhdHandle` is an empty file handle of `options.Type == OsLayerTypeContainer`
|
|
||||||
// or else it is a file handle to the 'SystemTemplateBase.vhdx' if `options.Type
|
|
||||||
// == OsLayerTypeVm`.
|
|
||||||
//
|
|
||||||
// `options` are the options applied while processing the layer.
|
|
||||||
func SetupBaseOSLayer(ctx context.Context, layerPath string, vhdHandle windows.Handle, options OsLayerOptions) (err error) {
|
|
||||||
title := "hcsshim::SetupBaseOSLayer"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("layerPath", layerPath),
|
|
||||||
)
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hcsSetupBaseOSLayer(layerPath, vhdHandle, string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to setup base OS layer")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetupBaseOSVolume sets up a volume that contains a base OS for a container.
|
|
||||||
//
|
|
||||||
// `layerPath` is a path to a directory containing the layer.
|
|
||||||
//
|
|
||||||
// `volumePath` is the path to the volume to be used for setup.
|
|
||||||
//
|
|
||||||
// `options` are the options applied while processing the layer.
|
|
||||||
//
|
|
||||||
// NOTE: This API is only available on builds of Windows greater than 19645. Inside we
|
|
||||||
// check if the hosts build has the API available by using 'GetVersion' which requires
|
|
||||||
// the calling application to be manifested. https://docs.microsoft.com/en-us/windows/win32/sbscs/manifests
|
|
||||||
func SetupBaseOSVolume(ctx context.Context, layerPath, volumePath string, options OsLayerOptions) (err error) {
|
|
||||||
if osversion.Build() < 19645 {
|
|
||||||
return errors.New("SetupBaseOSVolume is not present on builds older than 19645")
|
|
||||||
}
|
|
||||||
title := "hcsshim::SetupBaseOSVolume"
|
|
||||||
ctx, span := oc.StartSpan(ctx, title) //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("layerPath", layerPath),
|
|
||||||
trace.StringAttribute("volumePath", volumePath),
|
|
||||||
)
|
|
||||||
|
|
||||||
bytes, err := json.Marshal(options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = hcsSetupBaseOSVolume(layerPath, volumePath, string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to setup base OS layer")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
// Package computestorage is a wrapper around the HCS storage APIs. These are new storage APIs introduced
|
|
||||||
// separate from the original graphdriver calls intended to give more freedom around creating
|
|
||||||
// and managing container layers and scratch spaces.
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go storage.go
|
|
||||||
|
|
||||||
//sys hcsImportLayer(layerPath string, sourceFolderPath string, layerData string) (hr error) = computestorage.HcsImportLayer?
|
|
||||||
//sys hcsExportLayer(layerPath string, exportFolderPath string, layerData string, options string) (hr error) = computestorage.HcsExportLayer?
|
|
||||||
//sys hcsDestroyLayer(layerPath string) (hr error) = computestorage.HcsDestroyLayer?
|
|
||||||
//sys hcsSetupBaseOSLayer(layerPath string, handle windows.Handle, options string) (hr error) = computestorage.HcsSetupBaseOSLayer?
|
|
||||||
//sys hcsInitializeWritableLayer(writableLayerPath string, layerData string, options string) (hr error) = computestorage.HcsInitializeWritableLayer?
|
|
||||||
//sys hcsAttachLayerStorageFilter(layerPath string, layerData string) (hr error) = computestorage.HcsAttachLayerStorageFilter?
|
|
||||||
//sys hcsDetachLayerStorageFilter(layerPath string) (hr error) = computestorage.HcsDetachLayerStorageFilter?
|
|
||||||
//sys hcsFormatWritableLayerVhd(handle windows.Handle) (hr error) = computestorage.HcsFormatWritableLayerVhd?
|
|
||||||
//sys hcsGetLayerVhdMountPath(vhdHandle windows.Handle, mountPath **uint16) (hr error) = computestorage.HcsGetLayerVhdMountPath?
|
|
||||||
//sys hcsSetupBaseOSVolume(layerPath string, volumePath string, options string) (hr error) = computestorage.HcsSetupBaseOSVolume?
|
|
||||||
//sys hcsAttachOverlayFilter(volumePath string, layerData string) (hr error) = computestorage.HcsAttachOverlayFilter?
|
|
||||||
//sys hcsDetachOverlayFilter(volumePath string, layerData string) (hr error) = computestorage.HcsDetachOverlayFilter?
|
|
||||||
|
|
||||||
type Version = hcsschema.Version
|
|
||||||
type Layer = hcsschema.Layer
|
|
||||||
|
|
||||||
// LayerData is the data used to describe parent layer information.
|
|
||||||
type LayerData struct {
|
|
||||||
SchemaVersion Version `json:"SchemaVersion,omitempty"`
|
|
||||||
Layers []Layer `json:"Layers,omitempty"`
|
|
||||||
FilterType hcsschema.FileSystemFilterType `json:"FilterType,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportLayerOptions are the set of options that are used with the `computestorage.HcsExportLayer` syscall.
|
|
||||||
type ExportLayerOptions struct {
|
|
||||||
IsWritableLayer bool `json:"IsWritableLayer,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OsLayerType is the type of layer being operated on.
|
|
||||||
type OsLayerType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
// OsLayerTypeContainer is a container layer.
|
|
||||||
OsLayerTypeContainer OsLayerType = "Container"
|
|
||||||
// OsLayerTypeVM is a virtual machine layer.
|
|
||||||
OsLayerTypeVM OsLayerType = "Vm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// OsLayerOptions are the set of options that are used with the `SetupBaseOSLayer` and
|
|
||||||
// `SetupBaseOSVolume` calls.
|
|
||||||
type OsLayerOptions struct {
|
|
||||||
Type OsLayerType `json:"Type,omitempty"`
|
|
||||||
DisableCiCacheOptimization bool `json:"DisableCiCacheOptimization,omitempty"`
|
|
||||||
SkipUpdateBcdForBoot bool `json:"SkipUpdateBcdForBoot,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,389 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
|
||||||
|
|
||||||
package computestorage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
// Do the interface allocations only once for common
|
|
||||||
// Errno values.
|
|
||||||
const (
|
|
||||||
errnoERROR_IO_PENDING = 997
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
|
||||||
errERROR_EINVAL error = syscall.EINVAL
|
|
||||||
)
|
|
||||||
|
|
||||||
// errnoErr returns common boxed Errno values, to prevent
|
|
||||||
// allocations at runtime.
|
|
||||||
func errnoErr(e syscall.Errno) error {
|
|
||||||
switch e {
|
|
||||||
case 0:
|
|
||||||
return errERROR_EINVAL
|
|
||||||
case errnoERROR_IO_PENDING:
|
|
||||||
return errERROR_IO_PENDING
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
modcomputestorage = windows.NewLazySystemDLL("computestorage.dll")
|
|
||||||
|
|
||||||
procHcsAttachLayerStorageFilter = modcomputestorage.NewProc("HcsAttachLayerStorageFilter")
|
|
||||||
procHcsAttachOverlayFilter = modcomputestorage.NewProc("HcsAttachOverlayFilter")
|
|
||||||
procHcsDestroyLayer = modcomputestorage.NewProc("HcsDestroyLayer")
|
|
||||||
procHcsDetachLayerStorageFilter = modcomputestorage.NewProc("HcsDetachLayerStorageFilter")
|
|
||||||
procHcsDetachOverlayFilter = modcomputestorage.NewProc("HcsDetachOverlayFilter")
|
|
||||||
procHcsExportLayer = modcomputestorage.NewProc("HcsExportLayer")
|
|
||||||
procHcsFormatWritableLayerVhd = modcomputestorage.NewProc("HcsFormatWritableLayerVhd")
|
|
||||||
procHcsGetLayerVhdMountPath = modcomputestorage.NewProc("HcsGetLayerVhdMountPath")
|
|
||||||
procHcsImportLayer = modcomputestorage.NewProc("HcsImportLayer")
|
|
||||||
procHcsInitializeWritableLayer = modcomputestorage.NewProc("HcsInitializeWritableLayer")
|
|
||||||
procHcsSetupBaseOSLayer = modcomputestorage.NewProc("HcsSetupBaseOSLayer")
|
|
||||||
procHcsSetupBaseOSVolume = modcomputestorage.NewProc("HcsSetupBaseOSVolume")
|
|
||||||
)
|
|
||||||
|
|
||||||
func hcsAttachLayerStorageFilter(layerPath string, layerData string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(layerData)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsAttachLayerStorageFilter(_p0, _p1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsAttachLayerStorageFilter(layerPath *uint16, layerData *uint16) (hr error) {
|
|
||||||
hr = procHcsAttachLayerStorageFilter.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsAttachLayerStorageFilter.Addr(), uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(layerData)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsAttachOverlayFilter(volumePath string, layerData string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(volumePath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(layerData)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsAttachOverlayFilter(_p0, _p1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsAttachOverlayFilter(volumePath *uint16, layerData *uint16) (hr error) {
|
|
||||||
hr = procHcsAttachOverlayFilter.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsAttachOverlayFilter.Addr(), uintptr(unsafe.Pointer(volumePath)), uintptr(unsafe.Pointer(layerData)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsDestroyLayer(layerPath string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsDestroyLayer(_p0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsDestroyLayer(layerPath *uint16) (hr error) {
|
|
||||||
hr = procHcsDestroyLayer.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsDestroyLayer.Addr(), uintptr(unsafe.Pointer(layerPath)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsDetachLayerStorageFilter(layerPath string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsDetachLayerStorageFilter(_p0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsDetachLayerStorageFilter(layerPath *uint16) (hr error) {
|
|
||||||
hr = procHcsDetachLayerStorageFilter.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsDetachLayerStorageFilter.Addr(), uintptr(unsafe.Pointer(layerPath)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsDetachOverlayFilter(volumePath string, layerData string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(volumePath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(layerData)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsDetachOverlayFilter(_p0, _p1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsDetachOverlayFilter(volumePath *uint16, layerData *uint16) (hr error) {
|
|
||||||
hr = procHcsDetachOverlayFilter.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsDetachOverlayFilter.Addr(), uintptr(unsafe.Pointer(volumePath)), uintptr(unsafe.Pointer(layerData)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsExportLayer(layerPath string, exportFolderPath string, layerData string, options string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(exportFolderPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p2 *uint16
|
|
||||||
_p2, hr = syscall.UTF16PtrFromString(layerData)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p3 *uint16
|
|
||||||
_p3, hr = syscall.UTF16PtrFromString(options)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsExportLayer(_p0, _p1, _p2, _p3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsExportLayer(layerPath *uint16, exportFolderPath *uint16, layerData *uint16, options *uint16) (hr error) {
|
|
||||||
hr = procHcsExportLayer.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsExportLayer.Addr(), uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(exportFolderPath)), uintptr(unsafe.Pointer(layerData)), uintptr(unsafe.Pointer(options)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsFormatWritableLayerVhd(handle windows.Handle) (hr error) {
|
|
||||||
hr = procHcsFormatWritableLayerVhd.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsFormatWritableLayerVhd.Addr(), uintptr(handle))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsGetLayerVhdMountPath(vhdHandle windows.Handle, mountPath **uint16) (hr error) {
|
|
||||||
hr = procHcsGetLayerVhdMountPath.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsGetLayerVhdMountPath.Addr(), uintptr(vhdHandle), uintptr(unsafe.Pointer(mountPath)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsImportLayer(layerPath string, sourceFolderPath string, layerData string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(sourceFolderPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p2 *uint16
|
|
||||||
_p2, hr = syscall.UTF16PtrFromString(layerData)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsImportLayer(_p0, _p1, _p2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsImportLayer(layerPath *uint16, sourceFolderPath *uint16, layerData *uint16) (hr error) {
|
|
||||||
hr = procHcsImportLayer.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsImportLayer.Addr(), uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(sourceFolderPath)), uintptr(unsafe.Pointer(layerData)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsInitializeWritableLayer(writableLayerPath string, layerData string, options string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(writableLayerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(layerData)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p2 *uint16
|
|
||||||
_p2, hr = syscall.UTF16PtrFromString(options)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsInitializeWritableLayer(_p0, _p1, _p2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsInitializeWritableLayer(writableLayerPath *uint16, layerData *uint16, options *uint16) (hr error) {
|
|
||||||
hr = procHcsInitializeWritableLayer.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsInitializeWritableLayer.Addr(), uintptr(unsafe.Pointer(writableLayerPath)), uintptr(unsafe.Pointer(layerData)), uintptr(unsafe.Pointer(options)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsSetupBaseOSLayer(layerPath string, handle windows.Handle, options string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(options)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsSetupBaseOSLayer(_p0, handle, _p1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsSetupBaseOSLayer(layerPath *uint16, handle windows.Handle, options *uint16) (hr error) {
|
|
||||||
hr = procHcsSetupBaseOSLayer.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsSetupBaseOSLayer.Addr(), uintptr(unsafe.Pointer(layerPath)), uintptr(handle), uintptr(unsafe.Pointer(options)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hcsSetupBaseOSVolume(layerPath string, volumePath string, options string) (hr error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, hr = syscall.UTF16PtrFromString(layerPath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p1 *uint16
|
|
||||||
_p1, hr = syscall.UTF16PtrFromString(volumePath)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var _p2 *uint16
|
|
||||||
_p2, hr = syscall.UTF16PtrFromString(options)
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _hcsSetupBaseOSVolume(_p0, _p1, _p2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _hcsSetupBaseOSVolume(layerPath *uint16, volumePath *uint16, options *uint16) (hr error) {
|
|
||||||
hr = procHcsSetupBaseOSVolume.Find()
|
|
||||||
if hr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r0, _, _ := syscall.SyscallN(procHcsSetupBaseOSVolume.Addr(), uintptr(unsafe.Pointer(layerPath)), uintptr(unsafe.Pointer(volumePath)), uintptr(unsafe.Pointer(options)))
|
|
||||||
if int32(r0) < 0 {
|
|
||||||
if r0&0x1fff0000 == 0x00070000 {
|
|
||||||
r0 &= 0xffff
|
|
||||||
}
|
|
||||||
hr = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,225 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hcs"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hcs/schema1"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/mergemaps"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContainerProperties holds the properties for a container and the processes running in that container
|
|
||||||
type ContainerProperties = schema1.ContainerProperties
|
|
||||||
|
|
||||||
// MemoryStats holds the memory statistics for a container
|
|
||||||
type MemoryStats = schema1.MemoryStats
|
|
||||||
|
|
||||||
// ProcessorStats holds the processor statistics for a container
|
|
||||||
type ProcessorStats = schema1.ProcessorStats
|
|
||||||
|
|
||||||
// StorageStats holds the storage statistics for a container
|
|
||||||
type StorageStats = schema1.StorageStats
|
|
||||||
|
|
||||||
// NetworkStats holds the network statistics for a container
|
|
||||||
type NetworkStats = schema1.NetworkStats
|
|
||||||
|
|
||||||
// Statistics is the structure returned by a statistics call on a container
|
|
||||||
type Statistics = schema1.Statistics
|
|
||||||
|
|
||||||
// ProcessList is the structure of an item returned by a ProcessList call on a container
|
|
||||||
type ProcessListItem = schema1.ProcessListItem
|
|
||||||
|
|
||||||
// MappedVirtualDiskController is the structure of an item returned by a MappedVirtualDiskList call on a container
|
|
||||||
type MappedVirtualDiskController = schema1.MappedVirtualDiskController
|
|
||||||
|
|
||||||
// Type of Request Support in ModifySystem
|
|
||||||
type RequestType = schema1.RequestType
|
|
||||||
|
|
||||||
// Type of Resource Support in ModifySystem
|
|
||||||
type ResourceType = schema1.ResourceType
|
|
||||||
|
|
||||||
// RequestType const
|
|
||||||
const (
|
|
||||||
Add = schema1.Add
|
|
||||||
Remove = schema1.Remove
|
|
||||||
Network = schema1.Network
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResourceModificationRequestResponse is the structure used to send request to the container to modify the system
|
|
||||||
// Supported resource types are Network and Request Types are Add/Remove
|
|
||||||
type ResourceModificationRequestResponse = schema1.ResourceModificationRequestResponse
|
|
||||||
|
|
||||||
type container struct {
|
|
||||||
system *hcs.System
|
|
||||||
waitOnce sync.Once
|
|
||||||
waitErr error
|
|
||||||
waitCh chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// createContainerAdditionalJSON is read from the environment at initialization
|
|
||||||
// time. It allows an environment variable to define additional JSON which
|
|
||||||
// is merged in the CreateComputeSystem call to HCS.
|
|
||||||
var createContainerAdditionalJSON []byte
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
createContainerAdditionalJSON = ([]byte)(os.Getenv("HCSSHIM_CREATECONTAINER_ADDITIONALJSON"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateContainer creates a new container with the given configuration but does not start it.
|
|
||||||
func CreateContainer(id string, c *ContainerConfig) (Container, error) {
|
|
||||||
fullConfig, err := mergemaps.MergeJSON(c, createContainerAdditionalJSON)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to merge additional JSON '%s': %w", createContainerAdditionalJSON, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
system, err := hcs.CreateComputeSystem(context.Background(), id, fullConfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &container{system: system}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenContainer opens an existing container by ID.
|
|
||||||
func OpenContainer(id string) (Container, error) {
|
|
||||||
system, err := hcs.OpenComputeSystem(context.Background(), id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &container{system: system}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContainers gets a list of the containers on the system that match the query
|
|
||||||
func GetContainers(q ComputeSystemQuery) ([]ContainerProperties, error) {
|
|
||||||
return hcs.GetComputeSystems(context.Background(), q)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start synchronously starts the container.
|
|
||||||
func (container *container) Start() error {
|
|
||||||
return convertSystemError(container.system.Start(context.Background()), container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
|
|
||||||
func (container *container) Shutdown() error {
|
|
||||||
err := container.system.Shutdown(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Shutdown"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
|
|
||||||
func (container *container) Terminate() error {
|
|
||||||
err := container.system.Terminate(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
return &ContainerError{Container: container, Err: ErrVmcomputeOperationPending, Operation: "hcsshim::ComputeSystem::Terminate"}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Waits synchronously waits for the container to shutdown or terminate.
|
|
||||||
func (container *container) Wait() error {
|
|
||||||
err := container.system.Wait()
|
|
||||||
if err == nil {
|
|
||||||
err = container.system.ExitError()
|
|
||||||
}
|
|
||||||
return convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
|
|
||||||
// returns false if timeout occurs.
|
|
||||||
func (container *container) WaitTimeout(timeout time.Duration) error {
|
|
||||||
container.waitOnce.Do(func() {
|
|
||||||
container.waitCh = make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
container.waitErr = container.Wait()
|
|
||||||
close(container.waitCh)
|
|
||||||
}()
|
|
||||||
})
|
|
||||||
t := time.NewTimer(timeout)
|
|
||||||
defer t.Stop()
|
|
||||||
select {
|
|
||||||
case <-t.C:
|
|
||||||
return &ContainerError{Container: container, Err: ErrTimeout, Operation: "hcsshim::ComputeSystem::Wait"}
|
|
||||||
case <-container.waitCh:
|
|
||||||
return container.waitErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pause pauses the execution of a container.
|
|
||||||
func (container *container) Pause() error {
|
|
||||||
return convertSystemError(container.system.Pause(context.Background()), container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resume resumes the execution of a container.
|
|
||||||
func (container *container) Resume() error {
|
|
||||||
return convertSystemError(container.system.Resume(context.Background()), container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPendingUpdates returns true if the container has updates pending to install
|
|
||||||
func (container *container) HasPendingUpdates() (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Statistics returns statistics for the container. This is a legacy v1 call
|
|
||||||
func (container *container) Statistics() (Statistics, error) {
|
|
||||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeStatistics)
|
|
||||||
if err != nil {
|
|
||||||
return Statistics{}, convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
return properties.Statistics, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessList returns an array of ProcessListItems for the container. This is a legacy v1 call
|
|
||||||
func (container *container) ProcessList() ([]ProcessListItem, error) {
|
|
||||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeProcessList)
|
|
||||||
if err != nil {
|
|
||||||
return nil, convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
return properties.ProcessList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a legacy v1 call
|
|
||||||
func (container *container) MappedVirtualDisks() (map[int]MappedVirtualDiskController, error) {
|
|
||||||
properties, err := container.system.Properties(context.Background(), schema1.PropertyTypeMappedVirtualDisk)
|
|
||||||
if err != nil {
|
|
||||||
return nil, convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
|
|
||||||
return properties.MappedVirtualDiskControllers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateProcess launches a new process within the container.
|
|
||||||
func (container *container) CreateProcess(c *ProcessConfig) (Process, error) {
|
|
||||||
p, err := container.system.CreateProcess(context.Background(), c)
|
|
||||||
if err != nil {
|
|
||||||
return nil, convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
return &process{p: p.(*hcs.Process)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenProcess gets an interface to an existing process within the container.
|
|
||||||
func (container *container) OpenProcess(pid int) (Process, error) {
|
|
||||||
p, err := container.system.OpenProcess(context.Background(), pid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, convertSystemError(err, container)
|
|
||||||
}
|
|
||||||
return &process{p: p}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close cleans up any state associated with the container but does not terminate or wait for it.
|
|
||||||
func (container *container) Close() error {
|
|
||||||
return convertSystemError(container.system.Close(), container)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify the System
|
|
||||||
func (container *container) Modify(config *ResourceModificationRequestResponse) error {
|
|
||||||
return convertSystemError(container.system.Modify(context.Background(), config), container)
|
|
||||||
}
|
|
||||||
|
|
@ -1,253 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hcs"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hcserror"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists = hcs.exist
|
|
||||||
ErrComputeSystemDoesNotExist = hcs.ErrComputeSystemDoesNotExist
|
|
||||||
|
|
||||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
|
||||||
ErrElementNotFound = hcs.ErrElementNotFound
|
|
||||||
|
|
||||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
|
||||||
ErrNotSupported = hcs.ErrNotSupported
|
|
||||||
|
|
||||||
// ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported
|
|
||||||
// decimal -2147024883 / hex 0x8007000d
|
|
||||||
ErrInvalidData = hcs.ErrInvalidData
|
|
||||||
|
|
||||||
// ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed
|
|
||||||
ErrHandleClose = hcs.ErrHandleClose
|
|
||||||
|
|
||||||
// ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method
|
|
||||||
ErrAlreadyClosed = hcs.ErrAlreadyClosed
|
|
||||||
|
|
||||||
// ErrInvalidNotificationType is an error encountered when an invalid notification type is used
|
|
||||||
ErrInvalidNotificationType = hcs.ErrInvalidNotificationType
|
|
||||||
|
|
||||||
// ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation
|
|
||||||
ErrInvalidProcessState = hcs.ErrInvalidProcessState
|
|
||||||
|
|
||||||
// ErrTimeout is an error encountered when waiting on a notification times out
|
|
||||||
ErrTimeout = hcs.ErrTimeout
|
|
||||||
|
|
||||||
// ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for
|
|
||||||
// a different expected notification
|
|
||||||
ErrUnexpectedContainerExit = hcs.ErrUnexpectedContainerExit
|
|
||||||
|
|
||||||
// ErrUnexpectedProcessAbort is the error encountered when communication with the compute service
|
|
||||||
// is lost while waiting for a notification
|
|
||||||
ErrUnexpectedProcessAbort = hcs.ErrUnexpectedProcessAbort
|
|
||||||
|
|
||||||
// ErrUnexpectedValue is an error encountered when hcs returns an invalid value
|
|
||||||
ErrUnexpectedValue = hcs.ErrUnexpectedValue
|
|
||||||
|
|
||||||
// ErrOperationDenied is an error when hcs attempts an operation that is explicitly denied
|
|
||||||
ErrOperationDenied = hcs.ErrOperationDenied
|
|
||||||
|
|
||||||
// ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container
|
|
||||||
ErrVmcomputeAlreadyStopped = hcs.ErrVmcomputeAlreadyStopped
|
|
||||||
|
|
||||||
// ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously
|
|
||||||
ErrVmcomputeOperationPending = hcs.ErrVmcomputeOperationPending
|
|
||||||
|
|
||||||
// ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation
|
|
||||||
ErrVmcomputeOperationInvalidState = hcs.ErrVmcomputeOperationInvalidState
|
|
||||||
|
|
||||||
// ErrProcNotFound is an error encountered when a procedure look up fails.
|
|
||||||
ErrProcNotFound = hcs.ErrProcNotFound
|
|
||||||
|
|
||||||
// ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2
|
|
||||||
// builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3.
|
|
||||||
ErrVmcomputeOperationAccessIsDenied = hcs.ErrVmcomputeOperationAccessIsDenied
|
|
||||||
|
|
||||||
// ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management
|
|
||||||
ErrVmcomputeInvalidJSON = hcs.ErrVmcomputeInvalidJSON
|
|
||||||
|
|
||||||
// ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message
|
|
||||||
ErrVmcomputeUnknownMessage = hcs.ErrVmcomputeUnknownMessage
|
|
||||||
|
|
||||||
// ErrNotSupported is an error encountered when hcs doesn't support the request
|
|
||||||
ErrPlatformNotSupported = hcs.ErrPlatformNotSupported
|
|
||||||
)
|
|
||||||
|
|
||||||
type EndpointNotFoundError = hns.EndpointNotFoundError
|
|
||||||
type NetworkNotFoundError = hns.NetworkNotFoundError
|
|
||||||
|
|
||||||
// ProcessError is an error encountered in HCS during an operation on a Process object
|
|
||||||
type ProcessError struct {
|
|
||||||
Process *process
|
|
||||||
Operation string
|
|
||||||
Err error
|
|
||||||
Events []hcs.ErrorEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerError is an error encountered in HCS during an operation on a Container object
|
|
||||||
type ContainerError struct {
|
|
||||||
Container *container
|
|
||||||
Operation string
|
|
||||||
Err error
|
|
||||||
Events []hcs.ErrorEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ContainerError) Error() string {
|
|
||||||
if e == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Container == nil {
|
|
||||||
return "unexpected nil container for error: " + e.Err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
s := "container " + e.Container.system.ID()
|
|
||||||
|
|
||||||
if e.Operation != "" {
|
|
||||||
s += " encountered an error during " + e.Operation
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:errorlint // legacy code
|
|
||||||
switch e.Err.(type) {
|
|
||||||
case nil:
|
|
||||||
break
|
|
||||||
case syscall.Errno:
|
|
||||||
s += fmt.Sprintf(": failure in a Windows system call: %s (0x%x)", e.Err, hcserror.Win32FromError(e.Err))
|
|
||||||
default:
|
|
||||||
s += fmt.Sprintf(": %s", e.Err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ev := range e.Events {
|
|
||||||
s += "\n" + ev.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ProcessError) Error() string {
|
|
||||||
if e == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
|
|
||||||
if e.Process == nil {
|
|
||||||
return "Unexpected nil process for error: " + e.Err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
s := fmt.Sprintf("process %d in container %s", e.Process.p.Pid(), e.Process.p.SystemID())
|
|
||||||
if e.Operation != "" {
|
|
||||||
s += " encountered an error during " + e.Operation
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:errorlint // legacy code
|
|
||||||
switch e.Err.(type) {
|
|
||||||
case nil:
|
|
||||||
break
|
|
||||||
case syscall.Errno:
|
|
||||||
s += fmt.Sprintf(": failure in a Windows system call: %s (0x%x)", e.Err, hcserror.Win32FromError(e.Err))
|
|
||||||
default:
|
|
||||||
s += fmt.Sprintf(": %s", e.Err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ev := range e.Events {
|
|
||||||
s += "\n" + ev.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNotExist checks if an error is caused by the Container or Process not existing.
|
|
||||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
|
||||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
|
||||||
// will currently return true when the error is ErrElementNotFound.
|
|
||||||
func IsNotExist(err error) bool {
|
|
||||||
if _, ok := err.(EndpointNotFoundError); ok { //nolint:errorlint // legacy code
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := err.(NetworkNotFoundError); ok { //nolint:errorlint // legacy code
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return hcs.IsNotExist(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAlreadyClosed checks if an error is caused by the Container or Process having been
|
|
||||||
// already closed by a call to the Close() method.
|
|
||||||
func IsAlreadyClosed(err error) bool {
|
|
||||||
return hcs.IsAlreadyClosed(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPending returns a boolean indicating whether the error is that
|
|
||||||
// the requested operation is being completed in the background.
|
|
||||||
func IsPending(err error) bool {
|
|
||||||
return hcs.IsPending(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTimeout returns a boolean indicating whether the error is caused by
|
|
||||||
// a timeout waiting for the operation to complete.
|
|
||||||
func IsTimeout(err error) bool {
|
|
||||||
return hcs.IsTimeout(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAlreadyStopped returns a boolean indicating whether the error is caused by
|
|
||||||
// a Container or Process being already stopped.
|
|
||||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
|
||||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
|
||||||
// will currently return true when the error is ErrElementNotFound.
|
|
||||||
func IsAlreadyStopped(err error) bool {
|
|
||||||
return hcs.IsAlreadyStopped(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNotSupported returns a boolean indicating whether the error is caused by
|
|
||||||
// unsupported platform requests
|
|
||||||
// Note: Currently Unsupported platform requests can be mean either
|
|
||||||
// ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage
|
|
||||||
// is thrown from the Platform
|
|
||||||
func IsNotSupported(err error) bool {
|
|
||||||
return hcs.IsNotSupported(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsOperationInvalidState returns true when err is caused by
|
|
||||||
// `ErrVmcomputeOperationInvalidState`.
|
|
||||||
func IsOperationInvalidState(err error) bool {
|
|
||||||
return hcs.IsOperationInvalidState(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAccessIsDenied returns true when err is caused by
|
|
||||||
// `ErrVmcomputeOperationAccessIsDenied`.
|
|
||||||
func IsAccessIsDenied(err error) bool {
|
|
||||||
return hcs.IsAccessIsDenied(getInnerError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInnerError(err error) error {
|
|
||||||
//nolint:errorlint // legacy code
|
|
||||||
switch pe := err.(type) {
|
|
||||||
case nil:
|
|
||||||
return nil
|
|
||||||
case *ContainerError:
|
|
||||||
err = pe.Err
|
|
||||||
case *ProcessError:
|
|
||||||
err = pe.Err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertSystemError(err error, c *container) error {
|
|
||||||
if serr, ok := err.(*hcs.SystemError); ok { //nolint:errorlint // legacy code
|
|
||||||
return &ContainerError{Container: c, Operation: serr.Op, Err: serr.Err, Events: serr.Events}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertProcessError(err error, p *process) error {
|
|
||||||
if perr, ok := err.(*hcs.ProcessError); ok { //nolint:errorlint // legacy code
|
|
||||||
return &ProcessError{Process: p, Operation: perr.Op, Err: perr.Err, Events: perr.Events}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
// Shim for the Host Compute Service (HCS) to manage Windows Server
|
|
||||||
// containers and Hyper-V containers.
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hcserror"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go hcsshim.go
|
|
||||||
|
|
||||||
//sys SetCurrentThreadCompartmentId(compartmentId uint32) (hr error) = iphlpapi.SetCurrentThreadCompartmentId
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Specific user-visible exit codes
|
|
||||||
WaitErrExecFailed = 32767
|
|
||||||
|
|
||||||
ERROR_GEN_FAILURE = windows.ERROR_GEN_FAILURE
|
|
||||||
ERROR_SHUTDOWN_IN_PROGRESS = windows.ERROR_SHUTDOWN_IN_PROGRESS
|
|
||||||
WSAEINVAL = windows.WSAEINVAL
|
|
||||||
|
|
||||||
// Timeout on wait calls
|
|
||||||
TimeoutInfinite = 0xFFFFFFFF
|
|
||||||
)
|
|
||||||
|
|
||||||
type HcsError = hcserror.HcsError
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HNSNnvManagementMacAddress represents management mac address
|
|
||||||
// which needs to be excluded from VF reassignment
|
|
||||||
type HNSNnvManagementMacAddress = hns.HNSNnvManagementMacAddress
|
|
||||||
|
|
||||||
// HNSNnvManagementMacList represents a list of management
|
|
||||||
// mac addresses for exclusion from VF reassignment
|
|
||||||
type HNSNnvManagementMacList = hns.HNSNnvManagementMacList
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrorEmptyMacAddressList = errors.New("management mac_address list is empty")
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetNnvManagementMacAddresses sets a list of
|
|
||||||
// management mac addresses in hns for exclusion from VF reassignment.
|
|
||||||
func SetNnvManagementMacAddresses(managementMacAddresses []string) (*HNSNnvManagementMacList, error) {
|
|
||||||
if len(managementMacAddresses) == 0 {
|
|
||||||
return nil, ErrorEmptyMacAddressList
|
|
||||||
}
|
|
||||||
nnvManagementMacList := &HNSNnvManagementMacList{}
|
|
||||||
for _, mac := range managementMacAddresses {
|
|
||||||
nnvManagementMacList.MacAddressList = append(nnvManagementMacList.MacAddressList, HNSNnvManagementMacAddress{MacAddress: mac})
|
|
||||||
}
|
|
||||||
return nnvManagementMacList.Set()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetNnvManagementMacAddresses retrieves a list of
|
|
||||||
// management mac addresses in hns for exclusion from VF reassignment.
|
|
||||||
func GetNnvManagementMacAddresses() (*HNSNnvManagementMacList, error) {
|
|
||||||
return hns.GetNnvManagementMacAddressList()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteNnvManagementMacAddresses delete list of
|
|
||||||
// management mac addresses in hns which are excluded from VF reassignment.
|
|
||||||
func DeleteNnvManagementMacAddresses() (*HNSNnvManagementMacList, error) {
|
|
||||||
return hns.DeleteNnvManagementMacAddressList()
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// HNSEndpoint represents a network endpoint in HNS
|
|
||||||
type HNSEndpoint = hns.HNSEndpoint
|
|
||||||
|
|
||||||
// HNSEndpointStats represent the stats for an networkendpoint in HNS
|
|
||||||
type HNSEndpointStats = hns.EndpointStats
|
|
||||||
|
|
||||||
// Namespace represents a Compartment.
|
|
||||||
type Namespace = hns.Namespace
|
|
||||||
|
|
||||||
// SystemType represents the type of the system on which actions are done
|
|
||||||
type SystemType string
|
|
||||||
|
|
||||||
// SystemType const
|
|
||||||
const (
|
|
||||||
ContainerType SystemType = "Container"
|
|
||||||
VirtualMachineType SystemType = "VirtualMachine"
|
|
||||||
HostType SystemType = "Host"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EndpointAttachDetachRequest is the structure used to send request to the container to modify the system
|
|
||||||
// Supported resource types are Network and Request Types are Add/Remove
|
|
||||||
type EndpointAttachDetachRequest = hns.EndpointAttachDetachRequest
|
|
||||||
|
|
||||||
// EndpointResquestResponse is object to get the endpoint request response
|
|
||||||
type EndpointResquestResponse = hns.EndpointResquestResponse
|
|
||||||
|
|
||||||
// HNSEndpointRequest makes a HNS call to modify/query a network endpoint
|
|
||||||
func HNSEndpointRequest(method, path, request string) (*HNSEndpoint, error) {
|
|
||||||
return hns.HNSEndpointRequest(method, path, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HNSListEndpointRequest makes a HNS call to query the list of available endpoints
|
|
||||||
func HNSListEndpointRequest() ([]HNSEndpoint, error) {
|
|
||||||
return hns.HNSListEndpointRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
// HotAttachEndpoint makes a HCS Call to attach the endpoint to the container
|
|
||||||
func HotAttachEndpoint(containerID string, endpointID string) error {
|
|
||||||
endpoint, err := GetHNSEndpointByID(endpointID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
isAttached, err := endpoint.IsAttached(containerID)
|
|
||||||
if isAttached {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return modifyNetworkEndpoint(containerID, endpointID, Add)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HotDetachEndpoint makes a HCS Call to detach the endpoint from the container
|
|
||||||
func HotDetachEndpoint(containerID string, endpointID string) error {
|
|
||||||
endpoint, err := GetHNSEndpointByID(endpointID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
isAttached, err := endpoint.IsAttached(containerID)
|
|
||||||
if !isAttached {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return modifyNetworkEndpoint(containerID, endpointID, Remove)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModifyContainer corresponding to the container id, by sending a request
|
|
||||||
func modifyContainer(id string, request *ResourceModificationRequestResponse) error {
|
|
||||||
container, err := OpenContainer(id)
|
|
||||||
if err != nil {
|
|
||||||
if IsNotExist(err) {
|
|
||||||
return ErrComputeSystemDoesNotExist
|
|
||||||
}
|
|
||||||
return getInnerError(err)
|
|
||||||
}
|
|
||||||
defer container.Close()
|
|
||||||
err = container.Modify(request)
|
|
||||||
if err != nil {
|
|
||||||
if IsNotSupported(err) {
|
|
||||||
return ErrPlatformNotSupported
|
|
||||||
}
|
|
||||||
return getInnerError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func modifyNetworkEndpoint(containerID string, endpointID string, request RequestType) error {
|
|
||||||
requestMessage := &ResourceModificationRequestResponse{
|
|
||||||
Resource: Network,
|
|
||||||
Request: request,
|
|
||||||
Data: endpointID,
|
|
||||||
}
|
|
||||||
err := modifyContainer(containerID, requestMessage)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHNSEndpointByID get the Endpoint by ID
|
|
||||||
func GetHNSEndpointByID(endpointID string) (*HNSEndpoint, error) {
|
|
||||||
return hns.GetHNSEndpointByID(endpointID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHNSEndpointByName gets the endpoint filtered by Name
|
|
||||||
func GetHNSEndpointByName(endpointName string) (*HNSEndpoint, error) {
|
|
||||||
return hns.GetHNSEndpointByName(endpointName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHNSEndpointStats gets the endpoint stats by ID
|
|
||||||
func GetHNSEndpointStats(endpointName string) (*HNSEndpointStats, error) {
|
|
||||||
return hns.GetHNSEndpointStats(endpointName)
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HNSGlobals = hns.HNSGlobals
|
|
||||||
type HNSVersion = hns.HNSVersion
|
|
||||||
|
|
||||||
var (
|
|
||||||
HNSVersion1803 = hns.HNSVersion1803
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetHNSGlobals() (*HNSGlobals, error) {
|
|
||||||
return hns.GetHNSGlobals()
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Subnet is associated with a network and represents a list
|
|
||||||
// of subnets available to the network
|
|
||||||
type Subnet = hns.Subnet
|
|
||||||
|
|
||||||
// MacPool is associated with a network and represents a list
|
|
||||||
// of macaddresses available to the network
|
|
||||||
type MacPool = hns.MacPool
|
|
||||||
|
|
||||||
// HNSNetwork represents a network in HNS
|
|
||||||
type HNSNetwork = hns.HNSNetwork
|
|
||||||
|
|
||||||
// HNSNetworkRequest makes a call into HNS to update/query a single network
|
|
||||||
func HNSNetworkRequest(method, path, request string) (*HNSNetwork, error) {
|
|
||||||
return hns.HNSNetworkRequest(method, path, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HNSListNetworkRequest makes a HNS call to query the list of available networks
|
|
||||||
func HNSListNetworkRequest(method, path, request string) ([]HNSNetwork, error) {
|
|
||||||
return hns.HNSListNetworkRequest(method, path, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHNSNetworkByID
|
|
||||||
func GetHNSNetworkByID(networkID string) (*HNSNetwork, error) {
|
|
||||||
return hns.GetHNSNetworkByID(networkID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHNSNetworkName filtered by Name
|
|
||||||
func GetHNSNetworkByName(networkName string) (*HNSNetwork, error) {
|
|
||||||
return hns.GetHNSNetworkByName(networkName)
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Type of Request Support in ModifySystem
|
|
||||||
type PolicyType = hns.PolicyType
|
|
||||||
|
|
||||||
// RequestType const
|
|
||||||
const (
|
|
||||||
Nat = hns.Nat
|
|
||||||
ACL = hns.ACL
|
|
||||||
PA = hns.PA
|
|
||||||
VLAN = hns.VLAN
|
|
||||||
VSID = hns.VSID
|
|
||||||
VNet = hns.VNet
|
|
||||||
L2Driver = hns.L2Driver
|
|
||||||
Isolation = hns.Isolation
|
|
||||||
QOS = hns.QOS
|
|
||||||
OutboundNat = hns.OutboundNat
|
|
||||||
ExternalLoadBalancer = hns.ExternalLoadBalancer
|
|
||||||
Route = hns.Route
|
|
||||||
Proxy = hns.Proxy
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProxyPolicy = hns.ProxyPolicy
|
|
||||||
|
|
||||||
type NatPolicy = hns.NatPolicy
|
|
||||||
|
|
||||||
type QosPolicy = hns.QosPolicy
|
|
||||||
|
|
||||||
type IsolationPolicy = hns.IsolationPolicy
|
|
||||||
|
|
||||||
type VlanPolicy = hns.VlanPolicy
|
|
||||||
|
|
||||||
type VsidPolicy = hns.VsidPolicy
|
|
||||||
|
|
||||||
type PaPolicy = hns.PaPolicy
|
|
||||||
|
|
||||||
type OutboundNatPolicy = hns.OutboundNatPolicy
|
|
||||||
|
|
||||||
type ActionType = hns.ActionType
|
|
||||||
type DirectionType = hns.DirectionType
|
|
||||||
type RuleType = hns.RuleType
|
|
||||||
|
|
||||||
const (
|
|
||||||
Allow = hns.Allow
|
|
||||||
Block = hns.Block
|
|
||||||
|
|
||||||
In = hns.In
|
|
||||||
Out = hns.Out
|
|
||||||
|
|
||||||
Host = hns.Host
|
|
||||||
Switch = hns.Switch
|
|
||||||
)
|
|
||||||
|
|
||||||
type ACLPolicy = hns.ACLPolicy
|
|
||||||
|
|
||||||
type Policy = hns.Policy
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RoutePolicy is a structure defining schema for Route based Policy
|
|
||||||
type RoutePolicy = hns.RoutePolicy
|
|
||||||
|
|
||||||
// ELBPolicy is a structure defining schema for ELB LoadBalancing based Policy
|
|
||||||
type ELBPolicy = hns.ELBPolicy
|
|
||||||
|
|
||||||
// LBPolicy is a structure defining schema for LoadBalancing based Policy
|
|
||||||
type LBPolicy = hns.LBPolicy
|
|
||||||
|
|
||||||
// PolicyList is a structure defining schema for Policy list request
|
|
||||||
type PolicyList = hns.PolicyList
|
|
||||||
|
|
||||||
// HNSPolicyListRequest makes a call into HNS to update/query a single network
|
|
||||||
func HNSPolicyListRequest(method, path, request string) (*PolicyList, error) {
|
|
||||||
return hns.HNSPolicyListRequest(method, path, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HNSListPolicyListRequest gets all the policy list
|
|
||||||
func HNSListPolicyListRequest() ([]PolicyList, error) {
|
|
||||||
return hns.HNSListPolicyListRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PolicyListRequest makes a HNS call to modify/query a network policy list
|
|
||||||
func PolicyListRequest(method, path, request string) (*PolicyList, error) {
|
|
||||||
return hns.PolicyListRequest(method, path, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPolicyListByID get the policy list by ID
|
|
||||||
func GetPolicyListByID(policyListID string) (*PolicyList, error) {
|
|
||||||
return hns.GetPolicyListByID(policyListID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddLoadBalancer policy list for the specified endpoints
|
|
||||||
func AddLoadBalancer(endpoints []HNSEndpoint, isILB bool, sourceVIP, vip string, protocol uint16, internalPort uint16, externalPort uint16) (*PolicyList, error) {
|
|
||||||
return hns.AddLoadBalancer(endpoints, isILB, sourceVIP, vip, protocol, internalPort, externalPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddRoute adds route policy list for the specified endpoints
|
|
||||||
func AddRoute(endpoints []HNSEndpoint, destinationPrefix string, nextHop string, encapEnabled bool) (*PolicyList, error) {
|
|
||||||
return hns.AddRoute(endpoints, destinationPrefix, nextHop, encapEnabled)
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hns"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HNSSupportedFeatures = hns.HNSSupportedFeatures
|
|
||||||
|
|
||||||
type HNSAclFeatures = hns.HNSAclFeatures
|
|
||||||
|
|
||||||
func GetHNSSupportedFeatures() HNSSupportedFeatures {
|
|
||||||
return hns.GetHNSSupportedFeatures()
|
|
||||||
}
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcsshim
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hcs/schema1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProcessConfig is used as both the input of Container.CreateProcess
|
|
||||||
// and to convert the parameters to JSON for passing onto the HCS
|
|
||||||
type ProcessConfig = schema1.ProcessConfig
|
|
||||||
|
|
||||||
type Layer = schema1.Layer
|
|
||||||
type MappedDir = schema1.MappedDir
|
|
||||||
type MappedPipe = schema1.MappedPipe
|
|
||||||
type HvRuntime = schema1.HvRuntime
|
|
||||||
type MappedVirtualDisk = schema1.MappedVirtualDisk
|
|
||||||
|
|
||||||
// AssignedDevice represents a device that has been directly assigned to a container
|
|
||||||
//
|
|
||||||
// NOTE: Support added in RS5
|
|
||||||
type AssignedDevice = schema1.AssignedDevice
|
|
||||||
|
|
||||||
// ContainerConfig is used as both the input of CreateContainer
|
|
||||||
// and to convert the parameters to JSON for passing onto the HCS
|
|
||||||
type ContainerConfig = schema1.ContainerConfig
|
|
||||||
|
|
||||||
type ComputeSystemQuery = schema1.ComputeSystemQuery
|
|
||||||
|
|
||||||
// Container represents a created (but not necessarily running) container.
|
|
||||||
type Container interface {
|
|
||||||
// Start synchronously starts the container.
|
|
||||||
Start() error
|
|
||||||
|
|
||||||
// Shutdown requests a container shutdown, but it may not actually be shutdown until Wait() succeeds.
|
|
||||||
Shutdown() error
|
|
||||||
|
|
||||||
// Terminate requests a container terminate, but it may not actually be terminated until Wait() succeeds.
|
|
||||||
Terminate() error
|
|
||||||
|
|
||||||
// Waits synchronously waits for the container to shutdown or terminate.
|
|
||||||
Wait() error
|
|
||||||
|
|
||||||
// WaitTimeout synchronously waits for the container to terminate or the duration to elapse. It
|
|
||||||
// returns false if timeout occurs.
|
|
||||||
WaitTimeout(time.Duration) error
|
|
||||||
|
|
||||||
// Pause pauses the execution of a container.
|
|
||||||
Pause() error
|
|
||||||
|
|
||||||
// Resume resumes the execution of a container.
|
|
||||||
Resume() error
|
|
||||||
|
|
||||||
// HasPendingUpdates returns true if the container has updates pending to install.
|
|
||||||
HasPendingUpdates() (bool, error)
|
|
||||||
|
|
||||||
// Statistics returns statistics for a container.
|
|
||||||
Statistics() (Statistics, error)
|
|
||||||
|
|
||||||
// ProcessList returns details for the processes in a container.
|
|
||||||
ProcessList() ([]ProcessListItem, error)
|
|
||||||
|
|
||||||
// MappedVirtualDisks returns virtual disks mapped to a utility VM, indexed by controller
|
|
||||||
MappedVirtualDisks() (map[int]MappedVirtualDiskController, error)
|
|
||||||
|
|
||||||
// CreateProcess launches a new process within the container.
|
|
||||||
CreateProcess(c *ProcessConfig) (Process, error)
|
|
||||||
|
|
||||||
// OpenProcess gets an interface to an existing process within the container.
|
|
||||||
OpenProcess(pid int) (Process, error)
|
|
||||||
|
|
||||||
// Close cleans up any state associated with the container but does not terminate or wait for it.
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// Modify the System
|
|
||||||
Modify(config *ResourceModificationRequestResponse) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process represents a running or exited process.
|
|
||||||
type Process interface {
|
|
||||||
// Pid returns the process ID of the process within the container.
|
|
||||||
Pid() int
|
|
||||||
|
|
||||||
// Kill signals the process to terminate but does not wait for it to finish terminating.
|
|
||||||
Kill() error
|
|
||||||
|
|
||||||
// Wait waits for the process to exit.
|
|
||||||
Wait() error
|
|
||||||
|
|
||||||
// WaitTimeout waits for the process to exit or the duration to elapse. It returns
|
|
||||||
// false if timeout occurs.
|
|
||||||
WaitTimeout(time.Duration) error
|
|
||||||
|
|
||||||
// ExitCode returns the exit code of the process. The process must have
|
|
||||||
// already terminated.
|
|
||||||
ExitCode() (int, error)
|
|
||||||
|
|
||||||
// ResizeConsole resizes the console of the process.
|
|
||||||
ResizeConsole(width, height uint16) error
|
|
||||||
|
|
||||||
// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
|
|
||||||
// these pipes does not close the underlying pipes; it should be possible to
|
|
||||||
// call this multiple times to get multiple interfaces.
|
|
||||||
Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error)
|
|
||||||
|
|
||||||
// CloseStdin closes the write side of the stdin pipe so that the process is
|
|
||||||
// notified on the read side that there is no more data in stdin.
|
|
||||||
CloseStdin() error
|
|
||||||
|
|
||||||
// Close cleans up any state associated with the process but does not kill
|
|
||||||
// or wait on it.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package cow
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/hcs/schema1"
|
|
||||||
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Process is the interface for an OS process running in a container or utility VM.
|
|
||||||
type Process interface {
|
|
||||||
// Close releases resources associated with the process and closes the
|
|
||||||
// writer and readers returned by Stdio. Depending on the implementation,
|
|
||||||
// this may also terminate the process.
|
|
||||||
Close() error
|
|
||||||
// CloseStdin causes the process's stdin handle to receive EOF/EPIPE/whatever
|
|
||||||
// is appropriate to indicate that no more data is available.
|
|
||||||
CloseStdin(ctx context.Context) error
|
|
||||||
// CloseStdout closes the stdout connection to the process. It is used to indicate
|
|
||||||
// that we are done receiving output on the shim side.
|
|
||||||
CloseStdout(ctx context.Context) error
|
|
||||||
// CloseStderr closes the stderr connection to the process. It is used to indicate
|
|
||||||
// that we are done receiving output on the shim side.
|
|
||||||
CloseStderr(ctx context.Context) error
|
|
||||||
// Pid returns the process ID.
|
|
||||||
Pid() int
|
|
||||||
// Stdio returns the stdio streams for a process. These may be nil if a stream
|
|
||||||
// was not requested during CreateProcess.
|
|
||||||
Stdio() (_ io.Writer, _ io.Reader, _ io.Reader)
|
|
||||||
// ResizeConsole resizes the virtual terminal associated with the process.
|
|
||||||
ResizeConsole(ctx context.Context, width, height uint16) error
|
|
||||||
// Kill sends a SIGKILL or equivalent signal to the process and returns whether
|
|
||||||
// the signal was delivered. It does not wait for the process to terminate.
|
|
||||||
Kill(ctx context.Context) (bool, error)
|
|
||||||
// Signal sends a signal to the process and returns whether the signal was
|
|
||||||
// delivered. The input is OS specific (either
|
|
||||||
// guestrequest.SignalProcessOptionsWCOW or
|
|
||||||
// guestrequest.SignalProcessOptionsLCOW). It does not wait for the process
|
|
||||||
// to terminate.
|
|
||||||
Signal(ctx context.Context, options interface{}) (bool, error)
|
|
||||||
// Wait waits for the process to complete, or for a connection to the process to be
|
|
||||||
// terminated by some error condition (including calling Close).
|
|
||||||
Wait() error
|
|
||||||
// ExitCode returns the exit code of the process. Returns an error if the process is
|
|
||||||
// not running.
|
|
||||||
ExitCode() (int, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessHost is the interface for creating processes.
|
|
||||||
type ProcessHost interface {
|
|
||||||
// CreateProcess creates a process. The configuration is host specific
|
|
||||||
// (either hcsschema.ProcessParameters or lcow.ProcessParameters).
|
|
||||||
CreateProcess(ctx context.Context, config interface{}) (Process, error)
|
|
||||||
// OS returns the host's operating system, "linux" or "windows".
|
|
||||||
OS() string
|
|
||||||
// IsOCI specifies whether this is an OCI-compliant process host. If true,
|
|
||||||
// then the configuration passed to CreateProcess should have an OCI process
|
|
||||||
// spec (or nil if this is the initial process in an OCI container).
|
|
||||||
// Otherwise, it should have the HCS-specific process parameters.
|
|
||||||
IsOCI() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container is the interface for container objects, either running on the host or
|
|
||||||
// in a utility VM.
|
|
||||||
type Container interface {
|
|
||||||
ProcessHost
|
|
||||||
// Close releases the resources associated with the container. Depending on
|
|
||||||
// the implementation, this may also terminate the container.
|
|
||||||
Close() error
|
|
||||||
// ID returns the container ID.
|
|
||||||
ID() string
|
|
||||||
// Properties returns the requested container properties targeting a V1 schema container.
|
|
||||||
Properties(ctx context.Context, types ...schema1.PropertyType) (*schema1.ContainerProperties, error)
|
|
||||||
// PropertiesV2 returns the requested container properties targeting a V2 schema container.
|
|
||||||
PropertiesV2(ctx context.Context, types ...hcsschema.PropertyType) (*hcsschema.Properties, error)
|
|
||||||
// Start starts a container.
|
|
||||||
Start(ctx context.Context) error
|
|
||||||
// Shutdown sends a shutdown request to the container (but does not wait for
|
|
||||||
// the shutdown to complete).
|
|
||||||
Shutdown(ctx context.Context) error
|
|
||||||
// Terminate sends a terminate request to the container (but does not wait
|
|
||||||
// for the terminate to complete).
|
|
||||||
Terminate(ctx context.Context) error
|
|
||||||
// Wait waits for the container to terminate, or for the connection to the
|
|
||||||
// container to be terminated by some error condition (including calling
|
|
||||||
// Close).
|
|
||||||
Wait() error
|
|
||||||
// WaitChannel returns the wait channel of the container
|
|
||||||
WaitChannel() <-chan struct{}
|
|
||||||
// WaitError returns the container termination error.
|
|
||||||
// This function should only be called after the channel in WaitChannel()
|
|
||||||
// is closed. Otherwise it is not thread safe.
|
|
||||||
WaitError() error
|
|
||||||
// Modify sends a request to modify container resources
|
|
||||||
Modify(ctx context.Context, config interface{}) error
|
|
||||||
}
|
|
||||||
|
|
@ -1,163 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/interop"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/logfields"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/vmcompute"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
nextCallback uintptr
|
|
||||||
callbackMap = map[uintptr]*notificationWatcherContext{}
|
|
||||||
callbackMapLock = sync.RWMutex{}
|
|
||||||
|
|
||||||
notificationWatcherCallback = syscall.NewCallback(notificationWatcher)
|
|
||||||
|
|
||||||
// Notifications for HCS_SYSTEM handles
|
|
||||||
hcsNotificationSystemExited hcsNotification = 0x00000001
|
|
||||||
hcsNotificationSystemCreateCompleted hcsNotification = 0x00000002
|
|
||||||
hcsNotificationSystemStartCompleted hcsNotification = 0x00000003
|
|
||||||
hcsNotificationSystemPauseCompleted hcsNotification = 0x00000004
|
|
||||||
hcsNotificationSystemResumeCompleted hcsNotification = 0x00000005
|
|
||||||
hcsNotificationSystemCrashReport hcsNotification = 0x00000006
|
|
||||||
hcsNotificationSystemSiloJobCreated hcsNotification = 0x00000007
|
|
||||||
hcsNotificationSystemSaveCompleted hcsNotification = 0x00000008
|
|
||||||
hcsNotificationSystemRdpEnhancedModeStateChanged hcsNotification = 0x00000009
|
|
||||||
hcsNotificationSystemShutdownFailed hcsNotification = 0x0000000A
|
|
||||||
hcsNotificationSystemGetPropertiesCompleted hcsNotification = 0x0000000B
|
|
||||||
hcsNotificationSystemModifyCompleted hcsNotification = 0x0000000C
|
|
||||||
hcsNotificationSystemCrashInitiated hcsNotification = 0x0000000D
|
|
||||||
hcsNotificationSystemGuestConnectionClosed hcsNotification = 0x0000000E
|
|
||||||
|
|
||||||
// Notifications for HCS_PROCESS handles
|
|
||||||
hcsNotificationProcessExited hcsNotification = 0x00010000
|
|
||||||
|
|
||||||
// Common notifications
|
|
||||||
hcsNotificationInvalid hcsNotification = 0x00000000
|
|
||||||
hcsNotificationServiceDisconnect hcsNotification = 0x01000000
|
|
||||||
)
|
|
||||||
|
|
||||||
type hcsNotification uint32
|
|
||||||
|
|
||||||
func (hn hcsNotification) String() string {
|
|
||||||
switch hn {
|
|
||||||
case hcsNotificationSystemExited:
|
|
||||||
return "SystemExited"
|
|
||||||
case hcsNotificationSystemCreateCompleted:
|
|
||||||
return "SystemCreateCompleted"
|
|
||||||
case hcsNotificationSystemStartCompleted:
|
|
||||||
return "SystemStartCompleted"
|
|
||||||
case hcsNotificationSystemPauseCompleted:
|
|
||||||
return "SystemPauseCompleted"
|
|
||||||
case hcsNotificationSystemResumeCompleted:
|
|
||||||
return "SystemResumeCompleted"
|
|
||||||
case hcsNotificationSystemCrashReport:
|
|
||||||
return "SystemCrashReport"
|
|
||||||
case hcsNotificationSystemSiloJobCreated:
|
|
||||||
return "SystemSiloJobCreated"
|
|
||||||
case hcsNotificationSystemSaveCompleted:
|
|
||||||
return "SystemSaveCompleted"
|
|
||||||
case hcsNotificationSystemRdpEnhancedModeStateChanged:
|
|
||||||
return "SystemRdpEnhancedModeStateChanged"
|
|
||||||
case hcsNotificationSystemShutdownFailed:
|
|
||||||
return "SystemShutdownFailed"
|
|
||||||
case hcsNotificationSystemGetPropertiesCompleted:
|
|
||||||
return "SystemGetPropertiesCompleted"
|
|
||||||
case hcsNotificationSystemModifyCompleted:
|
|
||||||
return "SystemModifyCompleted"
|
|
||||||
case hcsNotificationSystemCrashInitiated:
|
|
||||||
return "SystemCrashInitiated"
|
|
||||||
case hcsNotificationSystemGuestConnectionClosed:
|
|
||||||
return "SystemGuestConnectionClosed"
|
|
||||||
case hcsNotificationProcessExited:
|
|
||||||
return "ProcessExited"
|
|
||||||
case hcsNotificationInvalid:
|
|
||||||
return "Invalid"
|
|
||||||
case hcsNotificationServiceDisconnect:
|
|
||||||
return "ServiceDisconnect"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("Unknown: %d", hn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type notificationChannel chan error
|
|
||||||
|
|
||||||
type notificationWatcherContext struct {
|
|
||||||
channels notificationChannels
|
|
||||||
handle vmcompute.HcsCallback
|
|
||||||
|
|
||||||
systemID string
|
|
||||||
processID int
|
|
||||||
}
|
|
||||||
|
|
||||||
type notificationChannels map[hcsNotification]notificationChannel
|
|
||||||
|
|
||||||
func newSystemChannels() notificationChannels {
|
|
||||||
channels := make(notificationChannels)
|
|
||||||
for _, notif := range []hcsNotification{
|
|
||||||
hcsNotificationServiceDisconnect,
|
|
||||||
hcsNotificationSystemExited,
|
|
||||||
hcsNotificationSystemCreateCompleted,
|
|
||||||
hcsNotificationSystemStartCompleted,
|
|
||||||
hcsNotificationSystemPauseCompleted,
|
|
||||||
hcsNotificationSystemResumeCompleted,
|
|
||||||
hcsNotificationSystemSaveCompleted,
|
|
||||||
} {
|
|
||||||
channels[notif] = make(notificationChannel, 1)
|
|
||||||
}
|
|
||||||
return channels
|
|
||||||
}
|
|
||||||
|
|
||||||
func newProcessChannels() notificationChannels {
|
|
||||||
channels := make(notificationChannels)
|
|
||||||
for _, notif := range []hcsNotification{
|
|
||||||
hcsNotificationServiceDisconnect,
|
|
||||||
hcsNotificationProcessExited,
|
|
||||||
} {
|
|
||||||
channels[notif] = make(notificationChannel, 1)
|
|
||||||
}
|
|
||||||
return channels
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeChannels(channels notificationChannels) {
|
|
||||||
for _, c := range channels {
|
|
||||||
close(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func notificationWatcher(notificationType hcsNotification, callbackNumber uintptr, notificationStatus uintptr, notificationData *uint16) uintptr {
|
|
||||||
var result error
|
|
||||||
if int32(notificationStatus) < 0 {
|
|
||||||
result = interop.Win32FromHresult(notificationStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
callbackMapLock.RLock()
|
|
||||||
context := callbackMap[callbackNumber]
|
|
||||||
callbackMapLock.RUnlock()
|
|
||||||
|
|
||||||
if context == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
log := logrus.WithFields(logrus.Fields{
|
|
||||||
"notification-type": notificationType.String(),
|
|
||||||
"system-id": context.systemID,
|
|
||||||
})
|
|
||||||
if context.processID != 0 {
|
|
||||||
log.Data[logfields.ProcessID] = context.processID
|
|
||||||
}
|
|
||||||
log.Debug("HCS notification")
|
|
||||||
|
|
||||||
if channel, ok := context.channels[notificationType]; ok {
|
|
||||||
channel <- result
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package hcs
|
|
||||||
|
|
@ -1,336 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrComputeSystemDoesNotExist is an error encountered when the container being operated on no longer exists
|
|
||||||
ErrComputeSystemDoesNotExist = syscall.Errno(0xc037010e)
|
|
||||||
|
|
||||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
|
||||||
ErrElementNotFound = syscall.Errno(0x490)
|
|
||||||
|
|
||||||
// ErrElementNotFound is an error encountered when the object being referenced does not exist
|
|
||||||
ErrNotSupported = syscall.Errno(0x32)
|
|
||||||
|
|
||||||
// ErrInvalidData is an error encountered when the request being sent to hcs is invalid/unsupported
|
|
||||||
// decimal -2147024883 / hex 0x8007000d
|
|
||||||
ErrInvalidData = syscall.Errno(0xd)
|
|
||||||
|
|
||||||
// ErrHandleClose is an error encountered when the handle generating the notification being waited on has been closed
|
|
||||||
ErrHandleClose = errors.New("hcsshim: the handle generating this notification has been closed")
|
|
||||||
|
|
||||||
// ErrAlreadyClosed is an error encountered when using a handle that has been closed by the Close method
|
|
||||||
ErrAlreadyClosed = errors.New("hcsshim: the handle has already been closed")
|
|
||||||
|
|
||||||
// ErrInvalidNotificationType is an error encountered when an invalid notification type is used
|
|
||||||
ErrInvalidNotificationType = errors.New("hcsshim: invalid notification type")
|
|
||||||
|
|
||||||
// ErrInvalidProcessState is an error encountered when the process is not in a valid state for the requested operation
|
|
||||||
ErrInvalidProcessState = errors.New("the process is in an invalid state for the attempted operation")
|
|
||||||
|
|
||||||
// ErrTimeout is an error encountered when waiting on a notification times out
|
|
||||||
ErrTimeout = errors.New("hcsshim: timeout waiting for notification")
|
|
||||||
|
|
||||||
// ErrUnexpectedContainerExit is the error encountered when a container exits while waiting for
|
|
||||||
// a different expected notification
|
|
||||||
ErrUnexpectedContainerExit = errors.New("unexpected container exit")
|
|
||||||
|
|
||||||
// ErrUnexpectedProcessAbort is the error encountered when communication with the compute service
|
|
||||||
// is lost while waiting for a notification
|
|
||||||
ErrUnexpectedProcessAbort = errors.New("lost communication with compute service")
|
|
||||||
|
|
||||||
// ErrUnexpectedValue is an error encountered when hcs returns an invalid value
|
|
||||||
ErrUnexpectedValue = errors.New("unexpected value returned from hcs")
|
|
||||||
|
|
||||||
// ErrOperationDenied is an error when hcs attempts an operation that is explicitly denied
|
|
||||||
ErrOperationDenied = errors.New("operation denied")
|
|
||||||
|
|
||||||
// ErrVmcomputeAlreadyStopped is an error encountered when a shutdown or terminate request is made on a stopped container
|
|
||||||
ErrVmcomputeAlreadyStopped = syscall.Errno(0xc0370110)
|
|
||||||
|
|
||||||
// ErrVmcomputeOperationPending is an error encountered when the operation is being completed asynchronously
|
|
||||||
ErrVmcomputeOperationPending = syscall.Errno(0xC0370103)
|
|
||||||
|
|
||||||
// ErrVmcomputeOperationInvalidState is an error encountered when the compute system is not in a valid state for the requested operation
|
|
||||||
ErrVmcomputeOperationInvalidState = syscall.Errno(0xc0370105)
|
|
||||||
|
|
||||||
// ErrProcNotFound is an error encountered when a procedure look up fails.
|
|
||||||
ErrProcNotFound = syscall.Errno(0x7f)
|
|
||||||
|
|
||||||
// ErrVmcomputeOperationAccessIsDenied is an error which can be encountered when enumerating compute systems in RS1/RS2
|
|
||||||
// builds when the underlying silo might be in the process of terminating. HCS was fixed in RS3.
|
|
||||||
ErrVmcomputeOperationAccessIsDenied = syscall.Errno(0x5)
|
|
||||||
|
|
||||||
// ErrVmcomputeInvalidJSON is an error encountered when the compute system does not support/understand the messages sent by management
|
|
||||||
ErrVmcomputeInvalidJSON = syscall.Errno(0xc037010d)
|
|
||||||
|
|
||||||
// ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message
|
|
||||||
ErrVmcomputeUnknownMessage = syscall.Errno(0xc037010b)
|
|
||||||
|
|
||||||
// ErrVmcomputeUnexpectedExit is an error encountered when the compute system terminates unexpectedly
|
|
||||||
ErrVmcomputeUnexpectedExit = syscall.Errno(0xC0370106)
|
|
||||||
|
|
||||||
// ErrNotSupported is an error encountered when hcs doesn't support the request
|
|
||||||
ErrPlatformNotSupported = errors.New("unsupported platform request")
|
|
||||||
|
|
||||||
// ErrProcessAlreadyStopped is returned by hcs if the process we're trying to kill has already been stopped.
|
|
||||||
ErrProcessAlreadyStopped = syscall.Errno(0x8037011f)
|
|
||||||
|
|
||||||
// ErrInvalidHandle is an error that can be encountered when querying the properties of a compute system when the handle to that
|
|
||||||
// compute system has already been closed.
|
|
||||||
ErrInvalidHandle = syscall.Errno(0x6)
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrorEvent struct {
|
|
||||||
Message string `json:"Message,omitempty"` // Fully formated error message
|
|
||||||
StackTrace string `json:"StackTrace,omitempty"` // Stack trace in string form
|
|
||||||
Provider string `json:"Provider,omitempty"`
|
|
||||||
EventID uint16 `json:"EventId,omitempty"`
|
|
||||||
Flags uint32 `json:"Flags,omitempty"`
|
|
||||||
Source string `json:"Source,omitempty"`
|
|
||||||
//Data []EventData `json:"Data,omitempty"` // Omit this as HCS doesn't encode this well. It's more confusing to include. It is however logged in debug mode (see processHcsResult function)
|
|
||||||
}
|
|
||||||
|
|
||||||
type hcsResult struct {
|
|
||||||
Error int32
|
|
||||||
ErrorMessage string
|
|
||||||
ErrorEvents []ErrorEvent `json:"ErrorEvents,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ev *ErrorEvent) String() string {
|
|
||||||
evs := "[Event Detail: " + ev.Message
|
|
||||||
if ev.StackTrace != "" {
|
|
||||||
evs += " Stack Trace: " + ev.StackTrace
|
|
||||||
}
|
|
||||||
if ev.Provider != "" {
|
|
||||||
evs += " Provider: " + ev.Provider
|
|
||||||
}
|
|
||||||
if ev.EventID != 0 {
|
|
||||||
evs = fmt.Sprintf("%s EventID: %d", evs, ev.EventID)
|
|
||||||
}
|
|
||||||
if ev.Flags != 0 {
|
|
||||||
evs = fmt.Sprintf("%s flags: %d", evs, ev.Flags)
|
|
||||||
}
|
|
||||||
if ev.Source != "" {
|
|
||||||
evs += " Source: " + ev.Source
|
|
||||||
}
|
|
||||||
evs += "]"
|
|
||||||
return evs
|
|
||||||
}
|
|
||||||
|
|
||||||
func processHcsResult(ctx context.Context, resultJSON string) []ErrorEvent {
|
|
||||||
if resultJSON != "" {
|
|
||||||
result := &hcsResult{}
|
|
||||||
if err := json.Unmarshal([]byte(resultJSON), result); err != nil {
|
|
||||||
log.G(ctx).WithError(err).Warning("Could not unmarshal HCS result")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return result.ErrorEvents
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HcsError struct {
|
|
||||||
Op string
|
|
||||||
Err error
|
|
||||||
Events []ErrorEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Error = &HcsError{}
|
|
||||||
|
|
||||||
func (e *HcsError) Error() string {
|
|
||||||
s := e.Op + ": " + e.Err.Error()
|
|
||||||
for _, ev := range e.Events {
|
|
||||||
s += "\n" + ev.String()
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *HcsError) Is(target error) bool {
|
|
||||||
return errors.Is(e.Err, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unwrap isnt really needed, but helpful convince function
|
|
||||||
|
|
||||||
func (e *HcsError) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: net.Error.Temporary is deprecated.
|
|
||||||
func (e *HcsError) Temporary() bool {
|
|
||||||
err := e.netError()
|
|
||||||
return (err != nil) && err.Temporary()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *HcsError) Timeout() bool {
|
|
||||||
err := e.netError()
|
|
||||||
return (err != nil) && err.Timeout()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *HcsError) netError() (err net.Error) {
|
|
||||||
if errors.As(e.Unwrap(), &err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemError is an error encountered in HCS during an operation on a Container object
|
|
||||||
type SystemError struct {
|
|
||||||
HcsError
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Error = &SystemError{}
|
|
||||||
|
|
||||||
func (e *SystemError) Error() string {
|
|
||||||
s := e.Op + " " + e.ID + ": " + e.Err.Error()
|
|
||||||
for _, ev := range e.Events {
|
|
||||||
s += "\n" + ev.String()
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeSystemError(system *System, op string, err error, events []ErrorEvent) error {
|
|
||||||
// Don't double wrap errors
|
|
||||||
var e *SystemError
|
|
||||||
if errors.As(err, &e) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &SystemError{
|
|
||||||
ID: system.ID(),
|
|
||||||
HcsError: HcsError{
|
|
||||||
Op: op,
|
|
||||||
Err: err,
|
|
||||||
Events: events,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessError is an error encountered in HCS during an operation on a Process object
|
|
||||||
type ProcessError struct {
|
|
||||||
HcsError
|
|
||||||
SystemID string
|
|
||||||
Pid int
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ net.Error = &ProcessError{}
|
|
||||||
|
|
||||||
func (e *ProcessError) Error() string {
|
|
||||||
s := fmt.Sprintf("%s %s:%d: %s", e.Op, e.SystemID, e.Pid, e.Err.Error())
|
|
||||||
for _, ev := range e.Events {
|
|
||||||
s += "\n" + ev.String()
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeProcessError(process *Process, op string, err error, events []ErrorEvent) error {
|
|
||||||
// Don't double wrap errors
|
|
||||||
var e *ProcessError
|
|
||||||
if errors.As(err, &e) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return &ProcessError{
|
|
||||||
Pid: process.Pid(),
|
|
||||||
SystemID: process.SystemID(),
|
|
||||||
HcsError: HcsError{
|
|
||||||
Op: op,
|
|
||||||
Err: err,
|
|
||||||
Events: events,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNotExist checks if an error is caused by the Container or Process not existing.
|
|
||||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
|
||||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
|
||||||
// will currently return true when the error is ErrElementNotFound.
|
|
||||||
func IsNotExist(err error) bool {
|
|
||||||
return IsAny(err, ErrComputeSystemDoesNotExist, ErrElementNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrorInvalidHandle checks whether the error is the result of an operation carried
|
|
||||||
// out on a handle that is invalid/closed. This error popped up while trying to query
|
|
||||||
// stats on a container in the process of being stopped.
|
|
||||||
func IsErrorInvalidHandle(err error) bool {
|
|
||||||
return errors.Is(err, ErrInvalidHandle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAlreadyClosed checks if an error is caused by the Container or Process having been
|
|
||||||
// already closed by a call to the Close() method.
|
|
||||||
func IsAlreadyClosed(err error) bool {
|
|
||||||
return errors.Is(err, ErrAlreadyClosed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPending returns a boolean indicating whether the error is that
|
|
||||||
// the requested operation is being completed in the background.
|
|
||||||
func IsPending(err error) bool {
|
|
||||||
return errors.Is(err, ErrVmcomputeOperationPending)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsTimeout returns a boolean indicating whether the error is caused by
|
|
||||||
// a timeout waiting for the operation to complete.
|
|
||||||
func IsTimeout(err error) bool {
|
|
||||||
// HcsError and co. implement Timeout regardless of whether the errors they wrap do,
|
|
||||||
// so `errors.As(err, net.Error)`` will always be true.
|
|
||||||
// Using `errors.As(err.Unwrap(), net.Err)` wont work for general errors.
|
|
||||||
// So first check if there an `ErrTimeout` in the chain, then convert to a net error.
|
|
||||||
if errors.Is(err, ErrTimeout) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var nerr net.Error
|
|
||||||
return errors.As(err, &nerr) && nerr.Timeout()
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAlreadyStopped returns a boolean indicating whether the error is caused by
|
|
||||||
// a Container or Process being already stopped.
|
|
||||||
// Note: Currently, ErrElementNotFound can mean that a Process has either
|
|
||||||
// already exited, or does not exist. Both IsAlreadyStopped and IsNotExist
|
|
||||||
// will currently return true when the error is ErrElementNotFound.
|
|
||||||
func IsAlreadyStopped(err error) bool {
|
|
||||||
return IsAny(err, ErrVmcomputeAlreadyStopped, ErrProcessAlreadyStopped, ErrElementNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsNotSupported returns a boolean indicating whether the error is caused by
|
|
||||||
// unsupported platform requests
|
|
||||||
// Note: Currently Unsupported platform requests can be mean either
|
|
||||||
// ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported or ErrVmcomputeUnknownMessage
|
|
||||||
// is thrown from the Platform
|
|
||||||
func IsNotSupported(err error) bool {
|
|
||||||
// If Platform doesn't recognize or support the request sent, below errors are seen
|
|
||||||
return IsAny(err, ErrVmcomputeInvalidJSON, ErrInvalidData, ErrNotSupported, ErrVmcomputeUnknownMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsOperationInvalidState returns true when err is caused by
|
|
||||||
// `ErrVmcomputeOperationInvalidState`.
|
|
||||||
func IsOperationInvalidState(err error) bool {
|
|
||||||
return errors.Is(err, ErrVmcomputeOperationInvalidState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAccessIsDenied returns true when err is caused by
|
|
||||||
// `ErrVmcomputeOperationAccessIsDenied`.
|
|
||||||
func IsAccessIsDenied(err error) bool {
|
|
||||||
return errors.Is(err, ErrVmcomputeOperationAccessIsDenied)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAny is a vectorized version of [errors.Is], it returns true if err is one of targets.
|
|
||||||
func IsAny(err error, targets ...error) bool {
|
|
||||||
for _, e := range targets {
|
|
||||||
if errors.Is(err, e) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
@ -1,582 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package hcs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.opencensus.io/trace"
|
|
||||||
|
|
||||||
"github.com/Microsoft/hcsshim/internal/cow"
|
|
||||||
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/log"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/oc"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
|
|
||||||
"github.com/Microsoft/hcsshim/internal/vmcompute"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Process struct {
|
|
||||||
handleLock sync.RWMutex
|
|
||||||
handle vmcompute.HcsProcess
|
|
||||||
processID int
|
|
||||||
system *System
|
|
||||||
hasCachedStdio bool
|
|
||||||
stdioLock sync.Mutex
|
|
||||||
stdin io.WriteCloser
|
|
||||||
stdout io.ReadCloser
|
|
||||||
stderr io.ReadCloser
|
|
||||||
callbackNumber uintptr
|
|
||||||
killSignalDelivered bool
|
|
||||||
|
|
||||||
closedWaitOnce sync.Once
|
|
||||||
waitBlock chan struct{}
|
|
||||||
exitCode int
|
|
||||||
waitError error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ cow.Process = &Process{}
|
|
||||||
|
|
||||||
func newProcess(process vmcompute.HcsProcess, processID int, computeSystem *System) *Process {
|
|
||||||
return &Process{
|
|
||||||
handle: process,
|
|
||||||
processID: processID,
|
|
||||||
system: computeSystem,
|
|
||||||
waitBlock: make(chan struct{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pid returns the process ID of the process within the container.
|
|
||||||
func (process *Process) Pid() int {
|
|
||||||
return process.processID
|
|
||||||
}
|
|
||||||
|
|
||||||
// SystemID returns the ID of the process's compute system.
|
|
||||||
func (process *Process) SystemID() string {
|
|
||||||
return process.system.ID()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (process *Process) processSignalResult(ctx context.Context, err error) (bool, error) {
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if errors.Is(err, ErrVmcomputeOperationInvalidState) || errors.Is(err, ErrComputeSystemDoesNotExist) || errors.Is(err, ErrElementNotFound) {
|
|
||||||
if !process.stopped() {
|
|
||||||
// The process should be gone, but we have not received the notification.
|
|
||||||
// After a second, force unblock the process wait to work around a possible
|
|
||||||
// deadlock in the HCS.
|
|
||||||
go func() {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
process.closedWaitOnce.Do(func() {
|
|
||||||
log.G(ctx).WithError(err).Warn("force unblocking process waits")
|
|
||||||
process.exitCode = -1
|
|
||||||
process.waitError = err
|
|
||||||
close(process.waitBlock)
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal signals the process with `options`.
|
|
||||||
//
|
|
||||||
// For LCOW `guestresource.SignalProcessOptionsLCOW`.
|
|
||||||
//
|
|
||||||
// For WCOW `guestresource.SignalProcessOptionsWCOW`.
|
|
||||||
func (process *Process) Signal(ctx context.Context, options interface{}) (bool, error) {
|
|
||||||
process.handleLock.RLock()
|
|
||||||
defer process.handleLock.RUnlock()
|
|
||||||
|
|
||||||
operation := "hcs::Process::Signal"
|
|
||||||
|
|
||||||
if process.handle == 0 {
|
|
||||||
return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
optionsb, err := json.Marshal(options)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
resultJSON, err := vmcompute.HcsSignalProcess(ctx, process.handle, string(optionsb))
|
|
||||||
events := processHcsResult(ctx, resultJSON)
|
|
||||||
delivered, err := process.processSignalResult(ctx, err)
|
|
||||||
if err != nil {
|
|
||||||
err = makeProcessError(process, operation, err, events)
|
|
||||||
}
|
|
||||||
return delivered, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kill signals the process to terminate but does not wait for it to finish terminating.
|
|
||||||
func (process *Process) Kill(ctx context.Context) (bool, error) {
|
|
||||||
process.handleLock.RLock()
|
|
||||||
defer process.handleLock.RUnlock()
|
|
||||||
|
|
||||||
operation := "hcs::Process::Kill"
|
|
||||||
|
|
||||||
if process.handle == 0 {
|
|
||||||
return false, makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if process.stopped() {
|
|
||||||
return false, makeProcessError(process, operation, ErrProcessAlreadyStopped, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if process.killSignalDelivered {
|
|
||||||
// A kill signal has already been sent to this process. Sending a second
|
|
||||||
// one offers no real benefit, as processes cannot stop themselves from
|
|
||||||
// being terminated, once a TerminateProcess has been issued. Sending a
|
|
||||||
// second kill may result in a number of errors (two of which detailed bellow)
|
|
||||||
// and which we can avoid handling.
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HCS serializes the signals sent to a target pid per compute system handle.
|
|
||||||
// To avoid SIGKILL being serialized behind other signals, we open a new compute
|
|
||||||
// system handle to deliver the kill signal.
|
|
||||||
// If the calls to opening a new compute system handle fail, we forcefully
|
|
||||||
// terminate the container itself so that no container is left behind
|
|
||||||
hcsSystem, err := OpenComputeSystem(ctx, process.system.id)
|
|
||||||
if err != nil {
|
|
||||||
// log error and force termination of container
|
|
||||||
log.G(ctx).WithField("err", err).Error("OpenComputeSystem() call failed")
|
|
||||||
err = process.system.Terminate(ctx)
|
|
||||||
// if the Terminate() call itself ever failed, log and return error
|
|
||||||
if err != nil {
|
|
||||||
log.G(ctx).WithField("err", err).Error("Terminate() call failed")
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
process.system.Close()
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
defer hcsSystem.Close()
|
|
||||||
|
|
||||||
newProcessHandle, err := hcsSystem.OpenProcess(ctx, process.Pid())
|
|
||||||
if err != nil {
|
|
||||||
// Return true only if the target process has either already
|
|
||||||
// exited, or does not exist.
|
|
||||||
if IsAlreadyStopped(err) {
|
|
||||||
return true, nil
|
|
||||||
} else {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defer newProcessHandle.Close()
|
|
||||||
|
|
||||||
resultJSON, err := vmcompute.HcsTerminateProcess(ctx, newProcessHandle.handle)
|
|
||||||
if err != nil {
|
|
||||||
// We still need to check these two cases, as processes may still be killed by an
|
|
||||||
// external actor (human operator, OOM, random script etc).
|
|
||||||
if errors.Is(err, os.ErrPermission) || IsAlreadyStopped(err) {
|
|
||||||
// There are two cases where it should be safe to ignore an error returned
|
|
||||||
// by HcsTerminateProcess. The first one is cause by the fact that
|
|
||||||
// HcsTerminateProcess ends up calling TerminateProcess in the context
|
|
||||||
// of a container. According to the TerminateProcess documentation:
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess#remarks
|
|
||||||
// After a process has terminated, call to TerminateProcess with open
|
|
||||||
// handles to the process fails with ERROR_ACCESS_DENIED (5) error code.
|
|
||||||
// It's safe to ignore this error here. HCS should always have permissions
|
|
||||||
// to kill processes inside any container. So an ERROR_ACCESS_DENIED
|
|
||||||
// is unlikely to be anything else than what the ending remarks in the
|
|
||||||
// documentation states.
|
|
||||||
//
|
|
||||||
// The second case is generated by hcs itself, if for any reason HcsTerminateProcess
|
|
||||||
// is called twice in a very short amount of time. In such cases, hcs may return
|
|
||||||
// HCS_E_PROCESS_ALREADY_STOPPED.
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
events := processHcsResult(ctx, resultJSON)
|
|
||||||
delivered, err := newProcessHandle.processSignalResult(ctx, err)
|
|
||||||
if err != nil {
|
|
||||||
err = makeProcessError(newProcessHandle, operation, err, events)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.killSignalDelivered = delivered
|
|
||||||
return delivered, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitBackground waits for the process exit notification. Once received sets
|
|
||||||
// `process.waitError` (if any) and unblocks all `Wait` calls.
|
|
||||||
//
|
|
||||||
// This MUST be called exactly once per `process.handle` but `Wait` is safe to
|
|
||||||
// call multiple times.
|
|
||||||
func (process *Process) waitBackground() {
|
|
||||||
operation := "hcs::Process::waitBackground"
|
|
||||||
ctx, span := oc.StartSpan(context.Background(), operation)
|
|
||||||
defer span.End()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("cid", process.SystemID()),
|
|
||||||
trace.Int64Attribute("pid", int64(process.processID)))
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
exitCode = -1
|
|
||||||
propertiesJSON string
|
|
||||||
resultJSON string
|
|
||||||
)
|
|
||||||
|
|
||||||
err = waitForNotification(ctx, process.callbackNumber, hcsNotificationProcessExited, nil)
|
|
||||||
if err != nil {
|
|
||||||
err = makeProcessError(process, operation, err, nil)
|
|
||||||
log.G(ctx).WithError(err).Error("failed wait")
|
|
||||||
} else {
|
|
||||||
process.handleLock.RLock()
|
|
||||||
defer process.handleLock.RUnlock()
|
|
||||||
|
|
||||||
// Make sure we didn't race with Close() here
|
|
||||||
if process.handle != 0 {
|
|
||||||
propertiesJSON, resultJSON, err = vmcompute.HcsGetProcessProperties(ctx, process.handle)
|
|
||||||
events := processHcsResult(ctx, resultJSON)
|
|
||||||
if err != nil {
|
|
||||||
err = makeProcessError(process, operation, err, events)
|
|
||||||
} else {
|
|
||||||
properties := &hcsschema.ProcessStatus{}
|
|
||||||
err = json.Unmarshal([]byte(propertiesJSON), properties)
|
|
||||||
if err != nil {
|
|
||||||
err = makeProcessError(process, operation, err, nil)
|
|
||||||
} else {
|
|
||||||
if properties.LastWaitResult != 0 {
|
|
||||||
log.G(ctx).WithField("wait-result", properties.LastWaitResult).Warning("non-zero last wait result")
|
|
||||||
} else {
|
|
||||||
exitCode = int(properties.ExitCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.G(ctx).WithField("exitCode", exitCode).Debug("process exited")
|
|
||||||
|
|
||||||
process.closedWaitOnce.Do(func() {
|
|
||||||
process.exitCode = exitCode
|
|
||||||
process.waitError = err
|
|
||||||
close(process.waitBlock)
|
|
||||||
})
|
|
||||||
oc.SetSpanStatus(span, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait waits for the process to exit. If the process has already exited returns
|
|
||||||
// the previous error (if any).
|
|
||||||
func (process *Process) Wait() error {
|
|
||||||
<-process.waitBlock
|
|
||||||
return process.waitError
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exited returns if the process has stopped
|
|
||||||
func (process *Process) stopped() bool {
|
|
||||||
select {
|
|
||||||
case <-process.waitBlock:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResizeConsole resizes the console of the process.
|
|
||||||
func (process *Process) ResizeConsole(ctx context.Context, width, height uint16) error {
|
|
||||||
process.handleLock.RLock()
|
|
||||||
defer process.handleLock.RUnlock()
|
|
||||||
|
|
||||||
operation := "hcs::Process::ResizeConsole"
|
|
||||||
|
|
||||||
if process.handle == 0 {
|
|
||||||
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
|
||||||
}
|
|
||||||
modifyRequest := hcsschema.ProcessModifyRequest{
|
|
||||||
Operation: guestrequest.ModifyProcessConsoleSize,
|
|
||||||
ConsoleSize: &hcsschema.ConsoleSize{
|
|
||||||
Height: height,
|
|
||||||
Width: width,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
modifyRequestb, err := json.Marshal(modifyRequest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
|
|
||||||
events := processHcsResult(ctx, resultJSON)
|
|
||||||
if err != nil {
|
|
||||||
return makeProcessError(process, operation, err, events)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCode returns the exit code of the process. The process must have
|
|
||||||
// already terminated.
|
|
||||||
func (process *Process) ExitCode() (int, error) {
|
|
||||||
if !process.stopped() {
|
|
||||||
return -1, makeProcessError(process, "hcs::Process::ExitCode", ErrInvalidProcessState, nil)
|
|
||||||
}
|
|
||||||
if process.waitError != nil {
|
|
||||||
return -1, process.waitError
|
|
||||||
}
|
|
||||||
return process.exitCode, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StdioLegacy returns the stdin, stdout, and stderr pipes, respectively. Closing
|
|
||||||
// these pipes does not close the underlying pipes. Once returned, these pipes
|
|
||||||
// are the responsibility of the caller to close.
|
|
||||||
func (process *Process) StdioLegacy() (_ io.WriteCloser, _ io.ReadCloser, _ io.ReadCloser, err error) {
|
|
||||||
operation := "hcs::Process::StdioLegacy"
|
|
||||||
ctx, span := oc.StartSpan(context.Background(), operation)
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("cid", process.SystemID()),
|
|
||||||
trace.Int64Attribute("pid", int64(process.processID)))
|
|
||||||
|
|
||||||
process.handleLock.RLock()
|
|
||||||
defer process.handleLock.RUnlock()
|
|
||||||
|
|
||||||
if process.handle == 0 {
|
|
||||||
return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.stdioLock.Lock()
|
|
||||||
defer process.stdioLock.Unlock()
|
|
||||||
if process.hasCachedStdio {
|
|
||||||
stdin, stdout, stderr := process.stdin, process.stdout, process.stderr
|
|
||||||
process.stdin, process.stdout, process.stderr = nil, nil, nil
|
|
||||||
process.hasCachedStdio = false
|
|
||||||
return stdin, stdout, stderr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
processInfo, resultJSON, err := vmcompute.HcsGetProcessInfo(ctx, process.handle)
|
|
||||||
events := processHcsResult(ctx, resultJSON)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, makeProcessError(process, operation, err, events)
|
|
||||||
}
|
|
||||||
|
|
||||||
pipes, err := makeOpenFiles([]syscall.Handle{processInfo.StdInput, processInfo.StdOutput, processInfo.StdError})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, makeProcessError(process, operation, err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return pipes[0], pipes[1], pipes[2], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stdio returns the stdin, stdout, and stderr pipes, respectively.
|
|
||||||
// To close them, close the process handle, or use the `CloseStd*` functions.
|
|
||||||
func (process *Process) Stdio() (stdin io.Writer, stdout, stderr io.Reader) {
|
|
||||||
process.stdioLock.Lock()
|
|
||||||
defer process.stdioLock.Unlock()
|
|
||||||
return process.stdin, process.stdout, process.stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseStdin closes the write side of the stdin pipe so that the process is
|
|
||||||
// notified on the read side that there is no more data in stdin.
|
|
||||||
func (process *Process) CloseStdin(ctx context.Context) (err error) {
|
|
||||||
operation := "hcs::Process::CloseStdin"
|
|
||||||
ctx, span := trace.StartSpan(ctx, operation)
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("cid", process.SystemID()),
|
|
||||||
trace.Int64Attribute("pid", int64(process.processID)))
|
|
||||||
|
|
||||||
process.handleLock.RLock()
|
|
||||||
defer process.handleLock.RUnlock()
|
|
||||||
|
|
||||||
if process.handle == 0 {
|
|
||||||
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
//HcsModifyProcess request to close stdin will fail if the process has already exited
|
|
||||||
if !process.stopped() {
|
|
||||||
modifyRequest := hcsschema.ProcessModifyRequest{
|
|
||||||
Operation: guestrequest.CloseProcessHandle,
|
|
||||||
CloseHandle: &hcsschema.CloseHandle{
|
|
||||||
Handle: guestrequest.STDInHandle,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
modifyRequestb, err := json.Marshal(modifyRequest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resultJSON, err := vmcompute.HcsModifyProcess(ctx, process.handle, string(modifyRequestb))
|
|
||||||
events := processHcsResult(ctx, resultJSON)
|
|
||||||
if err != nil {
|
|
||||||
return makeProcessError(process, operation, err, events)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
process.stdioLock.Lock()
|
|
||||||
defer process.stdioLock.Unlock()
|
|
||||||
if process.stdin != nil {
|
|
||||||
process.stdin.Close()
|
|
||||||
process.stdin = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (process *Process) CloseStdout(ctx context.Context) (err error) {
|
|
||||||
ctx, span := oc.StartSpan(ctx, "hcs::Process::CloseStdout") //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("cid", process.SystemID()),
|
|
||||||
trace.Int64Attribute("pid", int64(process.processID)))
|
|
||||||
|
|
||||||
process.handleLock.Lock()
|
|
||||||
defer process.handleLock.Unlock()
|
|
||||||
|
|
||||||
if process.handle == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
process.stdioLock.Lock()
|
|
||||||
defer process.stdioLock.Unlock()
|
|
||||||
if process.stdout != nil {
|
|
||||||
process.stdout.Close()
|
|
||||||
process.stdout = nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (process *Process) CloseStderr(ctx context.Context) (err error) {
|
|
||||||
ctx, span := oc.StartSpan(ctx, "hcs::Process::CloseStderr") //nolint:ineffassign,staticcheck
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("cid", process.SystemID()),
|
|
||||||
trace.Int64Attribute("pid", int64(process.processID)))
|
|
||||||
|
|
||||||
process.handleLock.Lock()
|
|
||||||
defer process.handleLock.Unlock()
|
|
||||||
|
|
||||||
if process.handle == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
process.stdioLock.Lock()
|
|
||||||
defer process.stdioLock.Unlock()
|
|
||||||
if process.stderr != nil {
|
|
||||||
process.stderr.Close()
|
|
||||||
process.stderr = nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close cleans up any state associated with the process but does not kill
|
|
||||||
// or wait on it.
|
|
||||||
func (process *Process) Close() (err error) {
|
|
||||||
operation := "hcs::Process::Close"
|
|
||||||
ctx, span := oc.StartSpan(context.Background(), operation)
|
|
||||||
defer span.End()
|
|
||||||
defer func() { oc.SetSpanStatus(span, err) }()
|
|
||||||
span.AddAttributes(
|
|
||||||
trace.StringAttribute("cid", process.SystemID()),
|
|
||||||
trace.Int64Attribute("pid", int64(process.processID)))
|
|
||||||
|
|
||||||
process.handleLock.Lock()
|
|
||||||
defer process.handleLock.Unlock()
|
|
||||||
|
|
||||||
// Don't double free this
|
|
||||||
if process.handle == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
process.stdioLock.Lock()
|
|
||||||
if process.stdin != nil {
|
|
||||||
process.stdin.Close()
|
|
||||||
process.stdin = nil
|
|
||||||
}
|
|
||||||
if process.stdout != nil {
|
|
||||||
process.stdout.Close()
|
|
||||||
process.stdout = nil
|
|
||||||
}
|
|
||||||
if process.stderr != nil {
|
|
||||||
process.stderr.Close()
|
|
||||||
process.stderr = nil
|
|
||||||
}
|
|
||||||
process.stdioLock.Unlock()
|
|
||||||
|
|
||||||
if err = process.unregisterCallback(ctx); err != nil {
|
|
||||||
return makeProcessError(process, operation, err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = vmcompute.HcsCloseProcess(ctx, process.handle); err != nil {
|
|
||||||
return makeProcessError(process, operation, err, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.handle = 0
|
|
||||||
process.closedWaitOnce.Do(func() {
|
|
||||||
process.exitCode = -1
|
|
||||||
process.waitError = ErrAlreadyClosed
|
|
||||||
close(process.waitBlock)
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (process *Process) registerCallback(ctx context.Context) error {
|
|
||||||
callbackContext := ¬ificationWatcherContext{
|
|
||||||
channels: newProcessChannels(),
|
|
||||||
systemID: process.SystemID(),
|
|
||||||
processID: process.processID,
|
|
||||||
}
|
|
||||||
|
|
||||||
callbackMapLock.Lock()
|
|
||||||
callbackNumber := nextCallback
|
|
||||||
nextCallback++
|
|
||||||
callbackMap[callbackNumber] = callbackContext
|
|
||||||
callbackMapLock.Unlock()
|
|
||||||
|
|
||||||
callbackHandle, err := vmcompute.HcsRegisterProcessCallback(ctx, process.handle, notificationWatcherCallback, callbackNumber)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
callbackContext.handle = callbackHandle
|
|
||||||
process.callbackNumber = callbackNumber
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (process *Process) unregisterCallback(ctx context.Context) error {
|
|
||||||
callbackNumber := process.callbackNumber
|
|
||||||
|
|
||||||
callbackMapLock.RLock()
|
|
||||||
callbackContext := callbackMap[callbackNumber]
|
|
||||||
callbackMapLock.RUnlock()
|
|
||||||
|
|
||||||
if callbackContext == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
handle := callbackContext.handle
|
|
||||||
|
|
||||||
if handle == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// vmcompute.HcsUnregisterProcessCallback has its own synchronization to
|
|
||||||
// wait for all callbacks to complete. We must NOT hold the callbackMapLock.
|
|
||||||
err := vmcompute.HcsUnregisterProcessCallback(ctx, handle)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
closeChannels(callbackContext.channels)
|
|
||||||
|
|
||||||
callbackMapLock.Lock()
|
|
||||||
delete(callbackMap, callbackNumber)
|
|
||||||
callbackMapLock.Unlock()
|
|
||||||
|
|
||||||
handle = 0 //nolint:ineffassign
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,252 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package schema1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Microsoft/go-winio/pkg/guid"
|
|
||||||
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ProcessConfig is used as both the input of Container.CreateProcess
|
|
||||||
// and to convert the parameters to JSON for passing onto the HCS
|
|
||||||
type ProcessConfig struct {
|
|
||||||
ApplicationName string `json:",omitempty"`
|
|
||||||
CommandLine string `json:",omitempty"`
|
|
||||||
CommandArgs []string `json:",omitempty"` // Used by Linux Containers on Windows
|
|
||||||
User string `json:",omitempty"`
|
|
||||||
WorkingDirectory string `json:",omitempty"`
|
|
||||||
Environment map[string]string `json:",omitempty"`
|
|
||||||
EmulateConsole bool `json:",omitempty"`
|
|
||||||
CreateStdInPipe bool `json:",omitempty"`
|
|
||||||
CreateStdOutPipe bool `json:",omitempty"`
|
|
||||||
CreateStdErrPipe bool `json:",omitempty"`
|
|
||||||
ConsoleSize [2]uint `json:",omitempty"`
|
|
||||||
CreateInUtilityVm bool `json:",omitempty"` // Used by Linux Containers on Windows
|
|
||||||
OCISpecification *json.RawMessage `json:",omitempty"` // Used by Linux Containers on Windows
|
|
||||||
}
|
|
||||||
|
|
||||||
type Layer struct {
|
|
||||||
ID string
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
type MappedDir struct {
|
|
||||||
HostPath string
|
|
||||||
ContainerPath string
|
|
||||||
ReadOnly bool
|
|
||||||
BandwidthMaximum uint64
|
|
||||||
IOPSMaximum uint64
|
|
||||||
CreateInUtilityVM bool
|
|
||||||
// LinuxMetadata - Support added in 1803/RS4+.
|
|
||||||
LinuxMetadata bool `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MappedPipe struct {
|
|
||||||
HostPath string
|
|
||||||
ContainerPipeName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type HvRuntime struct {
|
|
||||||
ImagePath string `json:",omitempty"`
|
|
||||||
SkipTemplate bool `json:",omitempty"`
|
|
||||||
LinuxInitrdFile string `json:",omitempty"` // File under ImagePath on host containing an initrd image for starting a Linux utility VM
|
|
||||||
LinuxKernelFile string `json:",omitempty"` // File under ImagePath on host containing a kernel for starting a Linux utility VM
|
|
||||||
LinuxBootParameters string `json:",omitempty"` // Additional boot parameters for starting a Linux Utility VM in initrd mode
|
|
||||||
BootSource string `json:",omitempty"` // "Vhd" for Linux Utility VM booting from VHD
|
|
||||||
WritableBootSource bool `json:",omitempty"` // Linux Utility VM booting from VHD
|
|
||||||
}
|
|
||||||
|
|
||||||
type MappedVirtualDisk struct {
|
|
||||||
HostPath string `json:",omitempty"` // Path to VHD on the host
|
|
||||||
ContainerPath string // Platform-specific mount point path in the container
|
|
||||||
CreateInUtilityVM bool `json:",omitempty"`
|
|
||||||
ReadOnly bool `json:",omitempty"`
|
|
||||||
Cache string `json:",omitempty"` // "" (Unspecified); "Disabled"; "Enabled"; "Private"; "PrivateAllowSharing"
|
|
||||||
AttachOnly bool `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AssignedDevice represents a device that has been directly assigned to a container
|
|
||||||
//
|
|
||||||
// NOTE: Support added in RS5
|
|
||||||
type AssignedDevice struct {
|
|
||||||
// InterfaceClassGUID of the device to assign to container.
|
|
||||||
InterfaceClassGUID string `json:"InterfaceClassGuid,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerConfig is used as both the input of CreateContainer
|
|
||||||
// and to convert the parameters to JSON for passing onto the HCS
|
|
||||||
type ContainerConfig struct {
|
|
||||||
SystemType string // HCS requires this to be hard-coded to "Container"
|
|
||||||
Name string // Name of the container. We use the docker ID.
|
|
||||||
Owner string `json:",omitempty"` // The management platform that created this container
|
|
||||||
VolumePath string `json:",omitempty"` // Windows volume path for scratch space. Used by Windows Server Containers only. Format \\?\\Volume{GUID}
|
|
||||||
IgnoreFlushesDuringBoot bool `json:",omitempty"` // Optimization hint for container startup in Windows
|
|
||||||
LayerFolderPath string `json:",omitempty"` // Where the layer folders are located. Used by Windows Server Containers only. Format %root%\windowsfilter\containerID
|
|
||||||
Layers []Layer // List of storage layers. Required for Windows Server and Hyper-V Containers. Format ID=GUID;Path=%root%\windowsfilter\layerID
|
|
||||||
Credentials string `json:",omitempty"` // Credentials information
|
|
||||||
ProcessorCount uint32 `json:",omitempty"` // Number of processors to assign to the container.
|
|
||||||
ProcessorWeight uint64 `json:",omitempty"` // CPU shares (relative weight to other containers with cpu shares). Range is from 1 to 10000. A value of 0 results in default shares.
|
|
||||||
ProcessorMaximum int64 `json:",omitempty"` // Specifies the portion of processor cycles that this container can use as a percentage times 100. Range is from 1 to 10000. A value of 0 results in no limit.
|
|
||||||
StorageIOPSMaximum uint64 `json:",omitempty"` // Maximum Storage IOPS
|
|
||||||
StorageBandwidthMaximum uint64 `json:",omitempty"` // Maximum Storage Bandwidth in bytes per second
|
|
||||||
StorageSandboxSize uint64 `json:",omitempty"` // Size in bytes that the container system drive should be expanded to if smaller
|
|
||||||
MemoryMaximumInMB int64 `json:",omitempty"` // Maximum memory available to the container in Megabytes
|
|
||||||
HostName string `json:",omitempty"` // Hostname
|
|
||||||
MappedDirectories []MappedDir `json:",omitempty"` // List of mapped directories (volumes/mounts)
|
|
||||||
MappedPipes []MappedPipe `json:",omitempty"` // List of mapped Windows named pipes
|
|
||||||
HvPartition bool // True if it a Hyper-V Container
|
|
||||||
NetworkSharedContainerName string `json:",omitempty"` // Name (ID) of the container that we will share the network stack with.
|
|
||||||
EndpointList []string `json:",omitempty"` // List of networking endpoints to be attached to container
|
|
||||||
HvRuntime *HvRuntime `json:",omitempty"` // Hyper-V container settings. Used by Hyper-V containers only. Format ImagePath=%root%\BaseLayerID\UtilityVM
|
|
||||||
Servicing bool `json:",omitempty"` // True if this container is for servicing
|
|
||||||
AllowUnqualifiedDNSQuery bool `json:",omitempty"` // True to allow unqualified DNS name resolution
|
|
||||||
DNSSearchList string `json:",omitempty"` // Comma separated list of DNS suffixes to use for name resolution
|
|
||||||
ContainerType string `json:",omitempty"` // "Linux" for Linux containers on Windows. Omitted otherwise.
|
|
||||||
TerminateOnLastHandleClosed bool `json:",omitempty"` // Should HCS terminate the container once all handles have been closed
|
|
||||||
MappedVirtualDisks []MappedVirtualDisk `json:",omitempty"` // Array of virtual disks to mount at start
|
|
||||||
AssignedDevices []AssignedDevice `json:",omitempty"` // Array of devices to assign. NOTE: Support added in RS5
|
|
||||||
}
|
|
||||||
|
|
||||||
type ComputeSystemQuery struct {
|
|
||||||
IDs []string `json:"Ids,omitempty"`
|
|
||||||
Types []string `json:",omitempty"`
|
|
||||||
Names []string `json:",omitempty"`
|
|
||||||
Owners []string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PropertyType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
PropertyTypeStatistics PropertyType = "Statistics" // V1 and V2
|
|
||||||
PropertyTypeProcessList PropertyType = "ProcessList" // V1 and V2
|
|
||||||
PropertyTypeMappedVirtualDisk PropertyType = "MappedVirtualDisk" // Not supported in V2 schema call
|
|
||||||
PropertyTypeGuestConnection PropertyType = "GuestConnection" // V1 and V2. Nil return from HCS before RS5
|
|
||||||
)
|
|
||||||
|
|
||||||
type PropertyQuery struct {
|
|
||||||
PropertyTypes []PropertyType `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerProperties holds the properties for a container and the processes running in that container
|
|
||||||
type ContainerProperties struct {
|
|
||||||
ID string `json:"Id"`
|
|
||||||
State string
|
|
||||||
Name string
|
|
||||||
SystemType string
|
|
||||||
RuntimeOSType string `json:"RuntimeOsType,omitempty"`
|
|
||||||
Owner string
|
|
||||||
SiloGUID string `json:"SiloGuid,omitempty"`
|
|
||||||
RuntimeID guid.GUID `json:"RuntimeId,omitempty"`
|
|
||||||
IsRuntimeTemplate bool `json:",omitempty"`
|
|
||||||
RuntimeImagePath string `json:",omitempty"`
|
|
||||||
Stopped bool `json:",omitempty"`
|
|
||||||
ExitType string `json:",omitempty"`
|
|
||||||
AreUpdatesPending bool `json:",omitempty"`
|
|
||||||
ObRoot string `json:",omitempty"`
|
|
||||||
Statistics Statistics `json:",omitempty"`
|
|
||||||
ProcessList []ProcessListItem `json:",omitempty"`
|
|
||||||
MappedVirtualDiskControllers map[int]MappedVirtualDiskController `json:",omitempty"`
|
|
||||||
GuestConnectionInfo GuestConnectionInfo `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MemoryStats holds the memory statistics for a container
|
|
||||||
type MemoryStats struct {
|
|
||||||
UsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"`
|
|
||||||
UsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"`
|
|
||||||
UsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessorStats holds the processor statistics for a container
|
|
||||||
type ProcessorStats struct {
|
|
||||||
TotalRuntime100ns uint64 `json:",omitempty"`
|
|
||||||
RuntimeUser100ns uint64 `json:",omitempty"`
|
|
||||||
RuntimeKernel100ns uint64 `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// StorageStats holds the storage statistics for a container
|
|
||||||
type StorageStats struct {
|
|
||||||
ReadCountNormalized uint64 `json:",omitempty"`
|
|
||||||
ReadSizeBytes uint64 `json:",omitempty"`
|
|
||||||
WriteCountNormalized uint64 `json:",omitempty"`
|
|
||||||
WriteSizeBytes uint64 `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkStats holds the network statistics for a container
|
|
||||||
type NetworkStats struct {
|
|
||||||
BytesReceived uint64 `json:",omitempty"`
|
|
||||||
BytesSent uint64 `json:",omitempty"`
|
|
||||||
PacketsReceived uint64 `json:",omitempty"`
|
|
||||||
PacketsSent uint64 `json:",omitempty"`
|
|
||||||
DroppedPacketsIncoming uint64 `json:",omitempty"`
|
|
||||||
DroppedPacketsOutgoing uint64 `json:",omitempty"`
|
|
||||||
EndpointId string `json:",omitempty"`
|
|
||||||
InstanceId string `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Statistics is the structure returned by a statistics call on a container
|
|
||||||
type Statistics struct {
|
|
||||||
Timestamp time.Time `json:",omitempty"`
|
|
||||||
ContainerStartTime time.Time `json:",omitempty"`
|
|
||||||
Uptime100ns uint64 `json:",omitempty"`
|
|
||||||
Memory MemoryStats `json:",omitempty"`
|
|
||||||
Processor ProcessorStats `json:",omitempty"`
|
|
||||||
Storage StorageStats `json:",omitempty"`
|
|
||||||
Network []NetworkStats `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessList is the structure of an item returned by a ProcessList call on a container
|
|
||||||
type ProcessListItem struct {
|
|
||||||
CreateTimestamp time.Time `json:",omitempty"`
|
|
||||||
ImageName string `json:",omitempty"`
|
|
||||||
KernelTime100ns uint64 `json:",omitempty"`
|
|
||||||
MemoryCommitBytes uint64 `json:",omitempty"`
|
|
||||||
MemoryWorkingSetPrivateBytes uint64 `json:",omitempty"`
|
|
||||||
MemoryWorkingSetSharedBytes uint64 `json:",omitempty"`
|
|
||||||
ProcessId uint32 `json:",omitempty"`
|
|
||||||
UserTime100ns uint64 `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MappedVirtualDiskController is the structure of an item returned by a MappedVirtualDiskList call on a container
|
|
||||||
type MappedVirtualDiskController struct {
|
|
||||||
MappedVirtualDisks map[int]MappedVirtualDisk `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GuestDefinedCapabilities is part of the GuestConnectionInfo returned by a GuestConnection call on a utility VM
|
|
||||||
type GuestDefinedCapabilities struct {
|
|
||||||
NamespaceAddRequestSupported bool `json:",omitempty"`
|
|
||||||
SignalProcessSupported bool `json:",omitempty"`
|
|
||||||
DumpStacksSupported bool `json:",omitempty"`
|
|
||||||
DeleteContainerStateSupported bool `json:",omitempty"`
|
|
||||||
UpdateContainerSupported bool `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GuestConnectionInfo is the structure of an iterm return by a GuestConnection call on a utility VM
|
|
||||||
type GuestConnectionInfo struct {
|
|
||||||
SupportedSchemaVersions []hcsschema.Version `json:",omitempty"`
|
|
||||||
ProtocolVersion uint32 `json:",omitempty"`
|
|
||||||
GuestDefinedCapabilities GuestDefinedCapabilities `json:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type of Request Support in ModifySystem
|
|
||||||
type RequestType string
|
|
||||||
|
|
||||||
// Type of Resource Support in ModifySystem
|
|
||||||
type ResourceType string
|
|
||||||
|
|
||||||
// RequestType const
|
|
||||||
const (
|
|
||||||
Add RequestType = "Add"
|
|
||||||
Remove RequestType = "Remove"
|
|
||||||
Network ResourceType = "Network"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ResourceModificationRequestResponse is the structure used to send request to the container to modify the system
|
|
||||||
// Supported resource types are Network and Request Types are Add/Remove
|
|
||||||
type ResourceModificationRequestResponse struct {
|
|
||||||
Resource ResourceType `json:"ResourceType"`
|
|
||||||
Data interface{} `json:"Settings"`
|
|
||||||
Request RequestType `json:"RequestType,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type Attachment struct {
|
|
||||||
Type_ string `json:"Type,omitempty"`
|
|
||||||
|
|
||||||
Path string `json:"Path,omitempty"`
|
|
||||||
|
|
||||||
IgnoreFlushes bool `json:"IgnoreFlushes,omitempty"`
|
|
||||||
|
|
||||||
CachingMode string `json:"CachingMode,omitempty"`
|
|
||||||
|
|
||||||
NoWriteHardening bool `json:"NoWriteHardening,omitempty"`
|
|
||||||
|
|
||||||
DisableExpansionOptimization bool `json:"DisableExpansionOptimization,omitempty"`
|
|
||||||
|
|
||||||
IgnoreRelativeLocator bool `json:"IgnoreRelativeLocator,omitempty"`
|
|
||||||
|
|
||||||
CaptureIoAttributionContext bool `json:"CaptureIoAttributionContext,omitempty"`
|
|
||||||
|
|
||||||
ReadOnly bool `json:"ReadOnly,omitempty"`
|
|
||||||
|
|
||||||
SupportCompressedVolumes bool `json:"SupportCompressedVolumes,omitempty"`
|
|
||||||
|
|
||||||
AlwaysAllowSparseFiles bool `json:"AlwaysAllowSparseFiles,omitempty"`
|
|
||||||
|
|
||||||
ExtensibleVirtualDiskType string `json:"ExtensibleVirtualDiskType,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type Battery struct {
|
|
||||||
}
|
|
||||||
18
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/cache_query_stats_response.go
generated
vendored
18
vendor/github.com/Microsoft/hcsshim/internal/hcs/schema2/cache_query_stats_response.go
generated
vendored
|
|
@ -1,18 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type CacheQueryStatsResponse struct {
|
|
||||||
L3OccupancyBytes int32 `json:"L3OccupancyBytes,omitempty"`
|
|
||||||
|
|
||||||
L3TotalBwBytes int32 `json:"L3TotalBwBytes,omitempty"`
|
|
||||||
|
|
||||||
L3LocalBwBytes int32 `json:"L3LocalBwBytes,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type Chipset struct {
|
|
||||||
Uefi *Uefi `json:"Uefi,omitempty"`
|
|
||||||
|
|
||||||
IsNumLockDisabled bool `json:"IsNumLockDisabled,omitempty"`
|
|
||||||
|
|
||||||
BaseBoardSerialNumber string `json:"BaseBoardSerialNumber,omitempty"`
|
|
||||||
|
|
||||||
ChassisSerialNumber string `json:"ChassisSerialNumber,omitempty"`
|
|
||||||
|
|
||||||
ChassisAssetTag string `json:"ChassisAssetTag,omitempty"`
|
|
||||||
|
|
||||||
UseUtc bool `json:"UseUtc,omitempty"`
|
|
||||||
|
|
||||||
// LinuxKernelDirect - Added in v2.2 Builds >=181117
|
|
||||||
LinuxKernelDirect *LinuxKernelDirect `json:"LinuxKernelDirect,omitempty"`
|
|
||||||
|
|
||||||
FirmwareFile *FirmwareFile `json:"FirmwareFile,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.5
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type CimMount struct {
|
|
||||||
ImagePath string `json:"ImagePath,omitempty"`
|
|
||||||
FileSystemName string `json:"FileSystemName,omitempty"`
|
|
||||||
VolumeGuid string `json:"VolumeGuid,omitempty"`
|
|
||||||
MountFlags uint32 `json:"MountFlags,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
import "github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
|
|
||||||
|
|
||||||
type CloseHandle struct {
|
|
||||||
Handle guestrequest.STDIOHandle `json:"Handle,omitempty"` // NOTE: Swagger generated as string. Locally updated.
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
// ComPort specifies the named pipe that will be used for the port, with empty string indicating a disconnected port.
|
|
||||||
type ComPort struct {
|
|
||||||
NamedPipe string `json:"NamedPipe,omitempty"`
|
|
||||||
|
|
||||||
OptimizeForDebugger bool `json:"OptimizeForDebugger,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type ComputeSystem struct {
|
|
||||||
Owner string `json:"Owner,omitempty"`
|
|
||||||
|
|
||||||
SchemaVersion *Version `json:"SchemaVersion,omitempty"`
|
|
||||||
|
|
||||||
HostingSystemId string `json:"HostingSystemId,omitempty"`
|
|
||||||
|
|
||||||
HostedSystem interface{} `json:"HostedSystem,omitempty"`
|
|
||||||
|
|
||||||
Container *Container `json:"Container,omitempty"`
|
|
||||||
|
|
||||||
VirtualMachine *VirtualMachine `json:"VirtualMachine,omitempty"`
|
|
||||||
|
|
||||||
ShouldTerminateOnLastHandleClosed bool `json:"ShouldTerminateOnLastHandleClosed,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// contextKeys are used to identify the type of value in the context.
|
|
||||||
// Since these are string, it is possible to get a short description of the
|
|
||||||
// context key for logging and debugging using key.String().
|
|
||||||
|
|
||||||
type contextKey string
|
|
||||||
|
|
||||||
func (c contextKey) String() string {
|
|
||||||
return "auth " + string(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ContextOAuth2 takes a oauth2.TokenSource as authentication for the request.
|
|
||||||
ContextOAuth2 = contextKey("token")
|
|
||||||
|
|
||||||
// ContextBasicAuth takes BasicAuth as authentication for the request.
|
|
||||||
ContextBasicAuth = contextKey("basic")
|
|
||||||
|
|
||||||
// ContextAccessToken takes a string oauth2 access token as authentication for the request.
|
|
||||||
ContextAccessToken = contextKey("accesstoken")
|
|
||||||
|
|
||||||
// ContextAPIKey takes an APIKey as authentication for the request
|
|
||||||
ContextAPIKey = contextKey("apikey")
|
|
||||||
)
|
|
||||||
|
|
||||||
// BasicAuth provides basic http authentication to a request passed via context using ContextBasicAuth
|
|
||||||
type BasicAuth struct {
|
|
||||||
UserName string `json:"userName,omitempty"`
|
|
||||||
Password string `json:"password,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIKey provides API key based authentication to a request passed via context using ContextAPIKey
|
|
||||||
type APIKey struct {
|
|
||||||
Key string
|
|
||||||
Prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Configuration struct {
|
|
||||||
BasePath string `json:"basePath,omitempty"`
|
|
||||||
Host string `json:"host,omitempty"`
|
|
||||||
Scheme string `json:"scheme,omitempty"`
|
|
||||||
DefaultHeader map[string]string `json:"defaultHeader,omitempty"`
|
|
||||||
UserAgent string `json:"userAgent,omitempty"`
|
|
||||||
HTTPClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfiguration() *Configuration {
|
|
||||||
cfg := &Configuration{
|
|
||||||
BasePath: "https://localhost",
|
|
||||||
DefaultHeader: make(map[string]string),
|
|
||||||
UserAgent: "Swagger-Codegen/2.1.0/go",
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Configuration) AddDefaultHeader(key string, value string) {
|
|
||||||
c.DefaultHeader[key] = value
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
// NOTE: Swagger generated fields as int32. Locally updated to uint16 to match documentation.
|
|
||||||
// https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#ConsoleSize
|
|
||||||
|
|
||||||
type ConsoleSize struct {
|
|
||||||
Height uint16 `json:"Height,omitempty"`
|
|
||||||
|
|
||||||
Width uint16 `json:"Width,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.1
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type Container struct {
|
|
||||||
GuestOs *GuestOs `json:"GuestOs,omitempty"`
|
|
||||||
|
|
||||||
Storage *Storage `json:"Storage,omitempty"`
|
|
||||||
|
|
||||||
MappedDirectories []MappedDirectory `json:"MappedDirectories,omitempty"`
|
|
||||||
|
|
||||||
MappedPipes []MappedPipe `json:"MappedPipes,omitempty"`
|
|
||||||
|
|
||||||
Memory *Memory `json:"Memory,omitempty"`
|
|
||||||
|
|
||||||
Processor *Processor `json:"Processor,omitempty"`
|
|
||||||
|
|
||||||
Networking *Networking `json:"Networking,omitempty"`
|
|
||||||
|
|
||||||
HvSocket *HvSocket `json:"HvSocket,omitempty"`
|
|
||||||
|
|
||||||
ContainerCredentialGuard *ContainerCredentialGuardState `json:"ContainerCredentialGuard,omitempty"`
|
|
||||||
|
|
||||||
RegistryChanges *RegistryChanges `json:"RegistryChanges,omitempty"`
|
|
||||||
|
|
||||||
AssignedDevices []Device `json:"AssignedDevices,omitempty"`
|
|
||||||
|
|
||||||
AdditionalDeviceNamespace *ContainerDefinitionDevice `json:"AdditionalDeviceNamespace,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.4
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type ContainerCredentialGuardAddInstanceRequest struct {
|
|
||||||
Id string `json:"Id,omitempty"`
|
|
||||||
CredentialSpec string `json:"CredentialSpec,omitempty"`
|
|
||||||
Transport string `json:"Transport,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.4
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type ContainerCredentialGuardHvSocketServiceConfig struct {
|
|
||||||
ServiceId string `json:"ServiceId,omitempty"`
|
|
||||||
ServiceConfig *HvSocketServiceConfig `json:"ServiceConfig,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.4
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type ContainerCredentialGuardInstance struct {
|
|
||||||
Id string `json:"Id,omitempty"`
|
|
||||||
CredentialGuard *ContainerCredentialGuardState `json:"CredentialGuard,omitempty"`
|
|
||||||
HvSocketConfig *ContainerCredentialGuardHvSocketServiceConfig `json:"HvSocketConfig,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.4
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type ContainerCredentialGuardModifyOperation string
|
|
||||||
|
|
||||||
const (
|
|
||||||
AddInstance ContainerCredentialGuardModifyOperation = "AddInstance"
|
|
||||||
RemoveInstance ContainerCredentialGuardModifyOperation = "RemoveInstance"
|
|
||||||
)
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* HCS API
|
|
||||||
*
|
|
||||||
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
|
|
||||||
*
|
|
||||||
* API version: 2.4
|
|
||||||
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
|
|
||||||
*/
|
|
||||||
|
|
||||||
package hcsschema
|
|
||||||
|
|
||||||
type ContainerCredentialGuardOperationRequest struct {
|
|
||||||
Operation ContainerCredentialGuardModifyOperation `json:"Operation,omitempty"`
|
|
||||||
OperationDetails interface{} `json:"OperationDetails,omitempty"`
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue