mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-15 12:23:42 +00:00
chore(deps): cleanup go.mod a little bit (#136)
* Upgrade docker to v19.03.7 * go mod vendor * golangci-lint 1.23.8
This commit is contained in:
1 parent
f46768cf90
commit
ca648a37c8
462 files changed
+32641
-3452
No files matched your search
@@ -6,7 +6,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: docker://golangci/golangci-lint:v1.23.6
|
- uses: docker://golangci/golangci-lint:v1.23.8
|
||||||
with:
|
with:
|
||||||
args: golangci-lint run
|
args: golangci-lint run
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -5,17 +5,14 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
|
||||||
github.com/MichaelTJones/walk v0.0.0-20161122175330-4748e29d5718 // indirect
|
github.com/MichaelTJones/walk v0.0.0-20161122175330-4748e29d5718 // indirect
|
||||||
github.com/Microsoft/go-winio v0.4.11 // indirect
|
github.com/Microsoft/hcsshim v0.8.7 // indirect
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
|
|
||||||
github.com/andreaskoch/go-fswatch v1.0.0
|
github.com/andreaskoch/go-fswatch v1.0.0
|
||||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 // indirect
|
github.com/containerd/containerd v1.3.3 // indirect
|
||||||
github.com/docker/distribution v2.7.1+incompatible // indirect
|
github.com/docker/distribution v2.7.1+incompatible // indirect
|
||||||
github.com/docker/docker v1.13.1
|
github.com/docker/docker v1.4.2-0.20200229013735-71373c6105e3
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-units v0.3.3 // indirect
|
|
||||||
github.com/emirpasic/gods v1.12.0 // indirect
|
github.com/emirpasic/gods v1.12.0 // indirect
|
||||||
github.com/go-ini/ini v1.41.0
|
github.com/go-ini/ini v1.41.0
|
||||||
github.com/gogo/protobuf v1.2.0 // indirect
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
|
||||||
github.com/gorilla/mux v1.7.0 // indirect
|
github.com/gorilla/mux v1.7.0 // indirect
|
||||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
|
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
|
||||||
@@ -24,13 +21,14 @@ require (
|
|||||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||||
github.com/mgutz/str v1.2.0 // indirect
|
github.com/mgutz/str v1.2.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
|
||||||
github.com/opencontainers/image-spec v1.0.1 // indirect
|
github.com/opencontainers/image-spec v1.0.1 // indirect
|
||||||
github.com/opencontainers/runc v0.1.1 // indirect
|
github.com/opencontainers/runc v0.1.1 // indirect
|
||||||
github.com/pkg/errors v0.8.1
|
github.com/pkg/errors v0.8.1
|
||||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
|
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94
|
||||||
github.com/sirupsen/logrus v1.3.0
|
github.com/sirupsen/logrus v1.4.1
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||||
github.com/spf13/cobra v0.0.3
|
github.com/spf13/cobra v0.0.3
|
||||||
@@ -40,10 +38,7 @@ require (
|
|||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 // indirect
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 // indirect
|
||||||
golang.org/x/text v0.3.2 // indirect
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
|
||||||
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca // indirect
|
|
||||||
google.golang.org/grpc v1.18.0 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
gopkg.in/godo.v2 v2.0.9
|
gopkg.in/godo.v2 v2.0.9
|
||||||
gopkg.in/ini.v1 v1.41.0 // indirect
|
gopkg.in/ini.v1 v1.41.0 // indirect
|
||||||
@@ -54,7 +49,3 @@ require (
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71
|
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71
|
||||||
gotest.tools v2.2.0+incompatible
|
gotest.tools v2.2.0+incompatible
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/docker/docker => github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb
|
|
||||||
|
|
||||||
replace golang.org/x/sys => golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
|
||||||
@@ -1,32 +1,44 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/MichaelTJones/walk v0.0.0-20161122175330-4748e29d5718 h1:FSsoaa1q4jAaeiAUxf9H0PgFP7eA/UL6c3PdJH+nMN4=
|
github.com/MichaelTJones/walk v0.0.0-20161122175330-4748e29d5718 h1:FSsoaa1q4jAaeiAUxf9H0PgFP7eA/UL6c3PdJH+nMN4=
|
||||||
github.com/MichaelTJones/walk v0.0.0-20161122175330-4748e29d5718/go.mod h1:VVwKsx9Dc8rNG55BWqogoJzGubjKnRoXdUvpGbWqeCc=
|
github.com/MichaelTJones/walk v0.0.0-20161122175330-4748e29d5718/go.mod h1:VVwKsx9Dc8rNG55BWqogoJzGubjKnRoXdUvpGbWqeCc=
|
||||||
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
|
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5 h1:ygIc8M6trr62pF5DucadTWGdEB4mEyvzi0e2nbcmcyA=
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
|
github.com/Microsoft/hcsshim v0.8.7 h1:ptnOoufxGSzauVTsdE+wMYnCWA301PdoN4xg5oRdZpg=
|
||||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||||
github.com/andreaskoch/go-fswatch v1.0.0 h1:la8nP/HiaFCxP2IM6NZNUCoxgLWuyNFgH0RligBbnJU=
|
github.com/andreaskoch/go-fswatch v1.0.0 h1:la8nP/HiaFCxP2IM6NZNUCoxgLWuyNFgH0RligBbnJU=
|
||||||
github.com/andreaskoch/go-fswatch v1.0.0/go.mod h1:r5/iV+4jfwoY2sYqBkg8vpF04ehOvEl4qPptVGdxmqo=
|
github.com/andreaskoch/go-fswatch v1.0.0/go.mod h1:r5/iV+4jfwoY2sYqBkg8vpF04ehOvEl4qPptVGdxmqo=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
|
github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
|
||||||
github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw=
|
||||||
|
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/containerd/containerd v1.3.3 h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
|
||||||
|
github.com/containerd/containerd v1.3.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
|
||||||
|
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
|
||||||
|
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
|
||||||
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||||
|
github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0=
|
||||||
|
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||||
|
github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
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=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb h1:PyjxRdW1mqCmSoxy/6uP01P7CGbsD+woX+oOWbaUPwQ=
|
github.com/docker/docker v1.4.2-0.20200229013735-71373c6105e3 h1:rQeprWgRziyxTHx4/mEhwjbAiFUbrk0Kqe9KlcWBnZU=
|
||||||
github.com/docker/engine v0.0.0-20181106193140-f5749085e9cb/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
|
github.com/docker/docker v1.4.2-0.20200229013735-71373c6105e3/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
|
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
|
||||||
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||||
@@ -37,20 +49,27 @@ github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
|
|||||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-ini/ini v1.41.0 h1:526aoxDtxRHFQKMZfcX2OG9oOI8TJ5yPLM0Mkno/uTY=
|
github.com/go-ini/ini v1.41.0 h1:526aoxDtxRHFQKMZfcX2OG9oOI8TJ5yPLM0Mkno/uTY=
|
||||||
github.com/go-ini/ini v1.41.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
github.com/go-ini/ini v1.41.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||||
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
|
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||||
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
|
||||||
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk=
|
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk=
|
||||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||||
@@ -64,6 +83,7 @@ github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpR
|
|||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8=
|
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8=
|
||||||
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
@@ -79,12 +99,18 @@ github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnG
|
|||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
|
github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
|
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
|
||||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
|
github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
|
||||||
|
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
||||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
@@ -93,14 +119,15 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
|||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
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/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
|
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA=
|
||||||
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0=
|
||||||
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ=
|
||||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
|
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
|
||||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
|
||||||
@@ -118,46 +145,70 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
|
|||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
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/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
|
github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
|
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
|
||||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
||||||
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||||
|
github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e h1:egKlR8l7Nu9vHGWbcUV8lqR4987UfUbBd7GbhqGzNYU=
|
||||||
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
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/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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181106065722-10aee1819953/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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk=
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933 h1:e6HwijUxhDe+hPNjZQQn9bA5PW3vNmnN64U2ZW759Lk=
|
||||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
|
||||||
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
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 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca h1:L1odPN6KVjhk0Lbg41BKcjGjP7ELTvh/qDcyh6hEfv0=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb h1:i1Ppqkc3WQXikh8bXiwHqAN5Rv3/qDCcRk0/Otx73BY=
|
||||||
google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.18.0 h1:IZl7mfBGfbhYx2p2rKRtYgDFw6SBz+kclmxYrCksPPA=
|
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
||||||
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -185,4 +236,5 @@ gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71 h1:Xe2gvTZUJpsvOWUnvmL/tmhVB
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
|
||||||
+17
-1
@@ -16,6 +16,7 @@ import (
|
|||||||
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||||
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||||
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||||
|
//sys wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||||
|
|
||||||
type atomicBool int32
|
type atomicBool int32
|
||||||
|
|
||||||
@@ -79,6 +80,7 @@ type win32File struct {
|
|||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
wgLock sync.RWMutex
|
wgLock sync.RWMutex
|
||||||
closing atomicBool
|
closing atomicBool
|
||||||
|
socket bool
|
||||||
readDeadline deadlineHandler
|
readDeadline deadlineHandler
|
||||||
writeDeadline deadlineHandler
|
writeDeadline deadlineHandler
|
||||||
}
|
}
|
||||||
@@ -109,7 +111,13 @@ func makeWin32File(h syscall.Handle) (*win32File, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||||
return makeWin32File(h)
|
// 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
|
// closeHandle closes the resources associated with a Win32 handle
|
||||||
@@ -190,6 +198,10 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
|
|||||||
if f.closing.isSet() {
|
if f.closing.isSet() {
|
||||||
err = ErrFileClosed
|
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:
|
case <-timeout:
|
||||||
cancelIoEx(f.handle, &c.o)
|
cancelIoEx(f.handle, &c.o)
|
||||||
@@ -265,6 +277,10 @@ func (f *win32File) Flush() error {
|
|||||||
return syscall.FlushFileBuffers(f.handle)
|
return syscall.FlushFileBuffers(f.handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *win32File) Fd() uintptr {
|
||||||
|
return uintptr(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
func (d *deadlineHandler) set(deadline time.Time) error {
|
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||||
d.setLock.Lock()
|
d.setLock.Lock()
|
||||||
defer d.setLock.Unlock()
|
defer d.setLock.Unlock()
|
||||||
|
|||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
module github.com/Microsoft/go-winio
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/pkg/errors v0.8.1
|
||||||
|
github.com/sirupsen/logrus v1.4.1
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3
|
||||||
|
)
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
+305
@@ -0,0 +1,305 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/pkg/guid"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||||
|
|
||||||
|
const (
|
||||||
|
afHvSock = 34 // AF_HYPERV
|
||||||
|
|
||||||
|
socketError = ^uintptr(0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Network returns the address's network name, "hvsock".
|
||||||
|
func (addr *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, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3")
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||||
|
type HvsockListener struct {
|
||||||
|
sock *win32File
|
||||||
|
addr HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||||
|
type HvsockConn struct {
|
||||||
|
sock *win32File
|
||||||
|
local, remote HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHvSocket() (*win32File, error) {
|
||||||
|
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("socket", err)
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(fd)
|
||||||
|
if err != nil {
|
||||||
|
syscall.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}
|
||||||
|
sock, err := newHvSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", err)
|
||||||
|
}
|
||||||
|
sa := addr.raw()
|
||||||
|
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
||||||
|
}
|
||||||
|
err = syscall.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.
|
||||||
|
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
||||||
|
var addrbuf [addrlen * 2]byte
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o)
|
||||||
|
_, err = l.sock.asyncIo(c, nil, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||||
|
}
|
||||||
|
conn := &HvsockConn{
|
||||||
|
sock: sock,
|
||||||
|
}
|
||||||
|
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
||||||
|
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Need to finish ConnectEx handling
|
||||||
|
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) {
|
||||||
|
sock, err := newHvSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if sock != nil {
|
||||||
|
sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c, err := sock.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer sock.wg.Done()
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o)
|
||||||
|
_, err = sock.asyncIo(ctx, c, nil, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
conn := &HvsockConn{
|
||||||
|
sock: sock,
|
||||||
|
remote: *addr,
|
||||||
|
}
|
||||||
|
sock = nil
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||||
|
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 := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var flags, bytes uint32
|
||||||
|
err = syscall.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 {
|
||||||
|
if _, ok := err.(syscall.Errno); ok {
|
||||||
|
err = os.NewSyscallError("wsarecv", err)
|
||||||
|
}
|
||||||
|
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 := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.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 {
|
||||||
|
if _, ok := err.(syscall.Errno); ok {
|
||||||
|
err = os.NewSyscallError("wsasend", err)
|
||||||
|
}
|
||||||
|
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) shutdown(how int) error {
|
||||||
|
err := syscall.Shutdown(conn.sock.handle, syscall.SHUT_RD)
|
||||||
|
if err != nil {
|
||||||
|
return os.NewSyscallError("shutdown", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseRead shuts down the read end of the socket.
|
||||||
|
func (conn *HvsockConn) CloseRead() error {
|
||||||
|
err := conn.shutdown(syscall.SHUT_RD)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("close", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite shuts down the write end of the socket, notifying the other endpoint that
|
||||||
|
// no more data will be written.
|
||||||
|
func (conn *HvsockConn) CloseWrite() error {
|
||||||
|
err := conn.shutdown(syscall.SHUT_WR)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("close", 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 {
|
||||||
|
conn.SetReadDeadline(t)
|
||||||
|
conn.SetWriteDeadline(t)
|
||||||
|
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)
|
||||||
|
}
|
||||||
+153
-64
@@ -3,10 +3,13 @@
|
|||||||
package winio
|
package winio
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -18,6 +21,48 @@ import (
|
|||||||
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||||
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||||
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
|
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
|
||||||
|
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, 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 ioStatusBlock struct {
|
||||||
|
Status, Information uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectAttributes struct {
|
||||||
|
Length uintptr
|
||||||
|
RootDirectory uintptr
|
||||||
|
ObjectName *unicodeString
|
||||||
|
Attributes uintptr
|
||||||
|
SecurityDescriptor *securityDescriptor
|
||||||
|
SecurityQoS uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type unicodeString struct {
|
||||||
|
Length uint16
|
||||||
|
MaximumLength uint16
|
||||||
|
Buffer uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type securityDescriptor struct {
|
||||||
|
Revision byte
|
||||||
|
Sbz1 byte
|
||||||
|
Control uint16
|
||||||
|
Owner uintptr
|
||||||
|
Group uintptr
|
||||||
|
Sacl uintptr
|
||||||
|
Dacl uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type ntstatus int32
|
||||||
|
|
||||||
|
func (status ntstatus) Err() error {
|
||||||
|
if status >= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rtlNtStatusToDosError(status)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cERROR_PIPE_BUSY = syscall.Errno(231)
|
cERROR_PIPE_BUSY = syscall.Errno(231)
|
||||||
@@ -25,21 +70,20 @@ const (
|
|||||||
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
||||||
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
||||||
|
|
||||||
cPIPE_ACCESS_DUPLEX = 0x3
|
|
||||||
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
|
|
||||||
cSECURITY_SQOS_PRESENT = 0x100000
|
cSECURITY_SQOS_PRESENT = 0x100000
|
||||||
cSECURITY_ANONYMOUS = 0
|
cSECURITY_ANONYMOUS = 0
|
||||||
|
|
||||||
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
|
|
||||||
|
|
||||||
cPIPE_UNLIMITED_INSTANCES = 255
|
|
||||||
|
|
||||||
cNMPWAIT_USE_DEFAULT_WAIT = 0
|
|
||||||
cNMPWAIT_NOWAIT = 1
|
|
||||||
|
|
||||||
cPIPE_TYPE_MESSAGE = 4
|
cPIPE_TYPE_MESSAGE = 4
|
||||||
|
|
||||||
cPIPE_READMODE_MESSAGE = 2
|
cPIPE_READMODE_MESSAGE = 2
|
||||||
|
|
||||||
|
cFILE_OPEN = 1
|
||||||
|
cFILE_CREATE = 2
|
||||||
|
|
||||||
|
cFILE_PIPE_MESSAGE_TYPE = 1
|
||||||
|
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
|
||||||
|
|
||||||
|
cSE_DACL_PRESENT = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -137,9 +181,30 @@ func (s pipeAddress) String() string {
|
|||||||
return string(s)
|
return string(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
||||||
|
func tryDialPipe(ctx context.Context, path *string) (syscall.Handle, error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return syscall.Handle(0), ctx.Err()
|
||||||
|
default:
|
||||||
|
h, err := createFile(*path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err == nil {
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
if err != cERROR_PIPE_BUSY {
|
||||||
|
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(time.Millisecond * 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DialPipe connects to a named pipe by path, timing out if the connection
|
// 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
|
// takes longer than the specified duration. If timeout is nil, then we use
|
||||||
// a default timeout of 5 seconds. (We do not use WaitNamedPipe.)
|
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
|
||||||
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||||
var absTimeout time.Time
|
var absTimeout time.Time
|
||||||
if timeout != nil {
|
if timeout != nil {
|
||||||
@@ -147,23 +212,22 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
|||||||
} else {
|
} else {
|
||||||
absTimeout = time.Now().Add(time.Second * 2)
|
absTimeout = time.Now().Add(time.Second * 2)
|
||||||
}
|
}
|
||||||
var err error
|
ctx, _ := context.WithDeadline(context.Background(), absTimeout)
|
||||||
var h syscall.Handle
|
conn, err := DialPipeContext(ctx, path)
|
||||||
for {
|
if err == context.DeadlineExceeded {
|
||||||
h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
|
||||||
if err != cERROR_PIPE_BUSY {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if time.Now().After(absTimeout) {
|
|
||||||
return nil, ErrTimeout
|
return nil, ErrTimeout
|
||||||
}
|
}
|
||||||
|
return conn, err
|
||||||
// Wait 10 msec and try again. This is a rather simplistic
|
|
||||||
// view, as we always try each 10 milliseconds.
|
|
||||||
time.Sleep(time.Millisecond * 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
var err error
|
||||||
|
var h syscall.Handle
|
||||||
|
h, err = tryDialPipe(ctx, &path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &os.PathError{Op: "open", Path: path, Err: err}
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags uint32
|
var flags uint32
|
||||||
@@ -196,41 +260,85 @@ type acceptResponse struct {
|
|||||||
type win32PipeListener struct {
|
type win32PipeListener struct {
|
||||||
firstHandle syscall.Handle
|
firstHandle syscall.Handle
|
||||||
path string
|
path string
|
||||||
securityDescriptor []byte
|
|
||||||
config PipeConfig
|
config PipeConfig
|
||||||
acceptCh chan (chan acceptResponse)
|
acceptCh chan (chan acceptResponse)
|
||||||
closeCh chan int
|
closeCh chan int
|
||||||
doneCh chan int
|
doneCh chan int
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
||||||
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
|
path16, err := syscall.UTF16FromString(path)
|
||||||
if first {
|
|
||||||
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
|
|
||||||
}
|
|
||||||
|
|
||||||
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
|
|
||||||
if c.MessageMode {
|
|
||||||
mode |= cPIPE_TYPE_MESSAGE
|
|
||||||
}
|
|
||||||
|
|
||||||
sa := &syscall.SecurityAttributes{}
|
|
||||||
sa.Length = uint32(unsafe.Sizeof(*sa))
|
|
||||||
if securityDescriptor != nil {
|
|
||||||
len := uint32(len(securityDescriptor))
|
|
||||||
sa.SecurityDescriptor = localAlloc(0, len)
|
|
||||||
defer localFree(sa.SecurityDescriptor)
|
|
||||||
copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor)
|
|
||||||
}
|
|
||||||
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
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 localFree(ntPath.Buffer)
|
||||||
|
oa.ObjectName = &ntPath
|
||||||
|
|
||||||
|
// The security descriptor is only needed for the first pipe.
|
||||||
|
if first {
|
||||||
|
if sd != nil {
|
||||||
|
len := uint32(len(sd))
|
||||||
|
sdb := localAlloc(0, len)
|
||||||
|
defer localFree(sdb)
|
||||||
|
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: %s", err)
|
||||||
|
}
|
||||||
|
defer localFree(dacl)
|
||||||
|
|
||||||
|
sdb := &securityDescriptor{
|
||||||
|
Revision: 1,
|
||||||
|
Control: cSE_DACL_PRESENT,
|
||||||
|
Dacl: dacl,
|
||||||
|
}
|
||||||
|
oa.SecurityDescriptor = sdb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS)
|
||||||
|
if c.MessageMode {
|
||||||
|
typ |= cFILE_PIPE_MESSAGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
disposition := uint32(cFILE_OPEN)
|
||||||
|
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
|
||||||
|
if first {
|
||||||
|
disposition = cFILE_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 = syscall.SYNCHRONIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := int64(-50 * 10000) // 50ms
|
||||||
|
|
||||||
|
var (
|
||||||
|
h syscall.Handle
|
||||||
|
iosb ioStatusBlock
|
||||||
|
)
|
||||||
|
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.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
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||||
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
|
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -341,28 +449,9 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Create a client handle and connect it. This results in the pipe
|
|
||||||
// instance always existing, so that clients see ERROR_PIPE_BUSY
|
|
||||||
// rather than ERROR_FILE_NOT_FOUND. This ties the first instance
|
|
||||||
// up so that no other instances can be used. This would have been
|
|
||||||
// cleaner if the Win32 API matched CreateFile with ConnectNamedPipe
|
|
||||||
// instead of CreateNamedPipe. (Apparently created named pipes are
|
|
||||||
// considered to be in listening state regardless of whether any
|
|
||||||
// active calls to ConnectNamedPipe are outstanding.)
|
|
||||||
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
|
||||||
if err != nil {
|
|
||||||
syscall.Close(h)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Close the client handle. The server side of the instance will
|
|
||||||
// still be busy, leading to ERROR_PIPE_BUSY instead of
|
|
||||||
// ERROR_NOT_FOUND, as long as we don't close the server handle,
|
|
||||||
// or disconnect the client with DisconnectNamedPipe.
|
|
||||||
syscall.Close(h2)
|
|
||||||
l := &win32PipeListener{
|
l := &win32PipeListener{
|
||||||
firstHandle: h,
|
firstHandle: h,
|
||||||
path: path,
|
path: path,
|
||||||
securityDescriptor: sd,
|
|
||||||
config: *c,
|
config: *c,
|
||||||
acceptCh: make(chan (chan acceptResponse)),
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
closeCh: make(chan int),
|
closeCh: make(chan int),
|
||||||
|
|||||||
+235
@@ -0,0 +1,235 @@
|
|||||||
|
// 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"
|
||||||
|
"encoding"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
var _ = (encoding.TextMarshaler)(GUID{})
|
||||||
|
var _ = (encoding.TextUnmarshaler)(&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 so that stringification and
|
||||||
|
// marshaling can be supported. The representation matches that used by native
|
||||||
|
// Windows code.
|
||||||
|
type GUID windows.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()
|
||||||
|
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
-1
@@ -1,3 +1,3 @@
|
|||||||
package winio
|
package winio
|
||||||
|
|
||||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
|
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go
|
||||||
+65
-23
@@ -1,4 +1,4 @@
|
|||||||
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
// Code generated by 'go generate'; DO NOT EDIT.
|
||||||
|
|
||||||
package winio
|
package winio
|
||||||
|
|
||||||
@@ -38,19 +38,25 @@ func errnoErr(e syscall.Errno) error {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||||
|
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
|
||||||
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||||
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||||
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||||
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||||
|
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
||||||
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||||
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||||
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||||
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
|
|
||||||
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||||
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||||
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
|
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
|
||||||
|
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
|
||||||
|
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
|
||||||
|
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
|
||||||
|
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
|
||||||
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||||
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||||
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
|
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
|
||||||
@@ -69,6 +75,7 @@ var (
|
|||||||
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||||
procBackupRead = modkernel32.NewProc("BackupRead")
|
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||||
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||||
|
procbind = modws2_32.NewProc("bind")
|
||||||
)
|
)
|
||||||
|
|
||||||
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
@@ -120,6 +127,24 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if wait {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall6(procWSAGetOverlappedResult.Addr(), 5, uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
||||||
if r1 == 0 {
|
if r1 == 0 {
|
||||||
@@ -176,27 +201,6 @@ func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityA
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitNamedPipe(name string, timeout uint32) (err error) {
|
|
||||||
var _p0 *uint16
|
|
||||||
_p0, err = syscall.UTF16PtrFromString(name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return _waitNamedPipe(_p0, timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _waitNamedPipe(name *uint16, timeout uint32) (err error) {
|
|
||||||
r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
|
|
||||||
if r1 == 0 {
|
|
||||||
if e1 != 0 {
|
|
||||||
err = errnoErr(e1)
|
|
||||||
} else {
|
|
||||||
err = syscall.EINVAL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||||
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
|
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
|
||||||
if r1 == 0 {
|
if r1 == 0 {
|
||||||
@@ -227,6 +231,32 @@ func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) {
|
||||||
|
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, 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)), 0)
|
||||||
|
status = ntstatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlNtStatusToDosError(status ntstatus) (winerr error) {
|
||||||
|
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
|
||||||
|
if r0 != 0 {
|
||||||
|
winerr = syscall.Errno(r0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) {
|
||||||
|
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
|
||||||
|
status = ntstatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) {
|
||||||
|
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
|
||||||
|
status = ntstatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
var _p0 *uint16
|
var _p0 *uint16
|
||||||
_p0, err = syscall.UTF16PtrFromString(accountName)
|
_p0, err = syscall.UTF16PtrFromString(accountName)
|
||||||
@@ -518,3 +548,15 @@ func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, p
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
|
||||||
|
if r1 == socketError {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
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.
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
package osversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OSVersion is a wrapper for Windows version information
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||||
|
type OSVersion struct {
|
||||||
|
Version uint32
|
||||||
|
MajorVersion uint8
|
||||||
|
MinorVersion uint8
|
||||||
|
Build uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
|
||||||
|
type osVersionInfoEx struct {
|
||||||
|
OSVersionInfoSize uint32
|
||||||
|
MajorVersion uint32
|
||||||
|
MinorVersion uint32
|
||||||
|
BuildNumber uint32
|
||||||
|
PlatformID uint32
|
||||||
|
CSDVersion [128]uint16
|
||||||
|
ServicePackMajor uint16
|
||||||
|
ServicePackMinor uint16
|
||||||
|
SuiteMask uint16
|
||||||
|
ProductType byte
|
||||||
|
Reserve byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the operating system version on Windows.
|
||||||
|
// The calling application must be manifested to get the correct version information.
|
||||||
|
func Get() OSVersion {
|
||||||
|
var err error
|
||||||
|
osv := OSVersion{}
|
||||||
|
osv.Version, err = windows.GetVersion()
|
||||||
|
if err != nil {
|
||||||
|
// GetVersion never fails.
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
osv.MajorVersion = uint8(osv.Version & 0xFF)
|
||||||
|
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
|
||||||
|
osv.Build = uint16(osv.Version >> 16)
|
||||||
|
return osv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build gets the build-number on Windows
|
||||||
|
// The calling application must be manifested to get the correct version information.
|
||||||
|
func Build() uint16 {
|
||||||
|
return Get().Build
|
||||||
|
}
|
||||||
|
|
||||||
|
func (osv OSVersion) ToString() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package osversion
|
||||||
|
|
||||||
|
const (
|
||||||
|
// RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server
|
||||||
|
// 2016 (ltsc2016) and Windows 10 (Anniversary Update).
|
||||||
|
RS1 = 14393
|
||||||
|
|
||||||
|
// RS2 (version 1703, codename "Redstone 2") was a client-only update, and
|
||||||
|
// corresponds to Windows 10 (Creators Update).
|
||||||
|
RS2 = 15063
|
||||||
|
|
||||||
|
// RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server
|
||||||
|
// 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update).
|
||||||
|
RS3 = 16299
|
||||||
|
|
||||||
|
// RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server
|
||||||
|
// 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update).
|
||||||
|
RS4 = 17134
|
||||||
|
|
||||||
|
// RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server
|
||||||
|
// 2019 (ltsc2019), and Windows 10 (October 2018 Update).
|
||||||
|
RS5 = 17763
|
||||||
|
|
||||||
|
// V19H1 (version 1903) corresponds to Windows Server 1903 (semi-annual
|
||||||
|
// channel).
|
||||||
|
V19H1 = 18362
|
||||||
|
)
|
||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
https://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright The containerd Authors
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
Docker
|
||||||
|
Copyright 2012-2015 Docker, Inc.
|
||||||
|
|
||||||
|
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||||
|
|
||||||
|
The following is courtesy of our legal counsel:
|
||||||
|
|
||||||
|
|
||||||
|
Use and transfer of Docker may be subject to certain restrictions by the
|
||||||
|
United States and other governments.
|
||||||
|
It is your responsibility to ensure that your use and/or transfer does not
|
||||||
|
violate applicable laws.
|
||||||
|
|
||||||
|
For more information, please see https://www.bis.doc.gov
|
||||||
|
|
||||||
|
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package errdefs defines the common errors used throughout containerd
|
||||||
|
// packages.
|
||||||
|
//
|
||||||
|
// Use with errors.Wrap and error.Wrapf to add context to an error.
|
||||||
|
//
|
||||||
|
// To detect an error class, use the IsXXX functions to tell whether an error
|
||||||
|
// is of a certain type.
|
||||||
|
//
|
||||||
|
// The functions ToGRPC and FromGRPC can be used to map server-side and
|
||||||
|
// client-side errors to the correct types.
|
||||||
|
package errdefs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Definitions of common error types used throughout containerd. All containerd
|
||||||
|
// errors returned by most packages will map into one of these errors classes.
|
||||||
|
// Packages should return errors of these types when they want to instruct a
|
||||||
|
// client to take a particular action.
|
||||||
|
//
|
||||||
|
// For the most part, we just try to provide local grpc errors. Most conditions
|
||||||
|
// map very well to those defined by grpc.
|
||||||
|
var (
|
||||||
|
ErrUnknown = errors.New("unknown") // used internally to represent a missed mapping.
|
||||||
|
ErrInvalidArgument = errors.New("invalid argument")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
ErrAlreadyExists = errors.New("already exists")
|
||||||
|
ErrFailedPrecondition = errors.New("failed precondition")
|
||||||
|
ErrUnavailable = errors.New("unavailable")
|
||||||
|
ErrNotImplemented = errors.New("not implemented") // represents not supported and unimplemented
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsInvalidArgument returns true if the error is due to an invalid argument
|
||||||
|
func IsInvalidArgument(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrInvalidArgument
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotFound returns true if the error is due to a missing object
|
||||||
|
func IsNotFound(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAlreadyExists returns true if the error is due to an already existing
|
||||||
|
// metadata item
|
||||||
|
func IsAlreadyExists(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrAlreadyExists
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFailedPrecondition returns true if an operation could not proceed to the
|
||||||
|
// lack of a particular condition
|
||||||
|
func IsFailedPrecondition(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrFailedPrecondition
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUnavailable returns true if the error is due to a resource being unavailable
|
||||||
|
func IsUnavailable(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrUnavailable
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotImplemented returns true if the error is due to not being implemented
|
||||||
|
func IsNotImplemented(err error) bool {
|
||||||
|
return errors.Cause(err) == ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsCanceled returns true if the error is due to `context.Canceled`.
|
||||||
|
func IsCanceled(err error) bool {
|
||||||
|
return errors.Cause(err) == context.Canceled
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDeadlineExceeded returns true if the error is due to
|
||||||
|
// `context.DeadlineExceeded`.
|
||||||
|
func IsDeadlineExceeded(err error) bool {
|
||||||
|
return errors.Cause(err) == context.DeadlineExceeded
|
||||||
|
}
|
||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package errdefs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToGRPC will attempt to map the backend containerd error into a grpc error,
|
||||||
|
// using the original error message as a description.
|
||||||
|
//
|
||||||
|
// Further information may be extracted from certain errors depending on their
|
||||||
|
// type.
|
||||||
|
//
|
||||||
|
// If the error is unmapped, the original error will be returned to be handled
|
||||||
|
// by the regular grpc error handling stack.
|
||||||
|
func ToGRPC(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if isGRPCError(err) {
|
||||||
|
// error has already been mapped to grpc
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case IsInvalidArgument(err):
|
||||||
|
return status.Errorf(codes.InvalidArgument, err.Error())
|
||||||
|
case IsNotFound(err):
|
||||||
|
return status.Errorf(codes.NotFound, err.Error())
|
||||||
|
case IsAlreadyExists(err):
|
||||||
|
return status.Errorf(codes.AlreadyExists, err.Error())
|
||||||
|
case IsFailedPrecondition(err):
|
||||||
|
return status.Errorf(codes.FailedPrecondition, err.Error())
|
||||||
|
case IsUnavailable(err):
|
||||||
|
return status.Errorf(codes.Unavailable, err.Error())
|
||||||
|
case IsNotImplemented(err):
|
||||||
|
return status.Errorf(codes.Unimplemented, err.Error())
|
||||||
|
case IsCanceled(err):
|
||||||
|
return status.Errorf(codes.Canceled, err.Error())
|
||||||
|
case IsDeadlineExceeded(err):
|
||||||
|
return status.Errorf(codes.DeadlineExceeded, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToGRPCf maps the error to grpc error codes, assembling the formatting string
|
||||||
|
// and combining it with the target error string.
|
||||||
|
//
|
||||||
|
// This is equivalent to errors.ToGRPC(errors.Wrapf(err, format, args...))
|
||||||
|
func ToGRPCf(err error, format string, args ...interface{}) error {
|
||||||
|
return ToGRPC(errors.Wrapf(err, format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromGRPC returns the underlying error from a grpc service based on the grpc error code
|
||||||
|
func FromGRPC(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cls error // divide these into error classes, becomes the cause
|
||||||
|
|
||||||
|
switch code(err) {
|
||||||
|
case codes.InvalidArgument:
|
||||||
|
cls = ErrInvalidArgument
|
||||||
|
case codes.AlreadyExists:
|
||||||
|
cls = ErrAlreadyExists
|
||||||
|
case codes.NotFound:
|
||||||
|
cls = ErrNotFound
|
||||||
|
case codes.Unavailable:
|
||||||
|
cls = ErrUnavailable
|
||||||
|
case codes.FailedPrecondition:
|
||||||
|
cls = ErrFailedPrecondition
|
||||||
|
case codes.Unimplemented:
|
||||||
|
cls = ErrNotImplemented
|
||||||
|
case codes.Canceled:
|
||||||
|
cls = context.Canceled
|
||||||
|
case codes.DeadlineExceeded:
|
||||||
|
cls = context.DeadlineExceeded
|
||||||
|
default:
|
||||||
|
cls = ErrUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := rebaseMessage(cls, err)
|
||||||
|
if msg != "" {
|
||||||
|
err = errors.Wrap(cls, msg)
|
||||||
|
} else {
|
||||||
|
err = errors.WithStack(cls)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// rebaseMessage removes the repeats for an error at the end of an error
|
||||||
|
// string. This will happen when taking an error over grpc then remapping it.
|
||||||
|
//
|
||||||
|
// Effectively, we just remove the string of cls from the end of err if it
|
||||||
|
// appears there.
|
||||||
|
func rebaseMessage(cls error, err error) string {
|
||||||
|
desc := errDesc(err)
|
||||||
|
clss := cls.Error()
|
||||||
|
if desc == clss {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.TrimSuffix(desc, ": "+clss)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGRPCError(err error) bool {
|
||||||
|
_, ok := status.FromError(err)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func code(err error) codes.Code {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return s.Code()
|
||||||
|
}
|
||||||
|
return codes.Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
func errDesc(err error) string {
|
||||||
|
if s, ok := status.FromError(err); ok {
|
||||||
|
return s.Message()
|
||||||
|
}
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufferPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
buffer := make([]byte, 32*1024)
|
||||||
|
return &buffer
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// XAttrErrorHandlers transform a non-nil xattr error.
|
||||||
|
// Return nil to ignore an error.
|
||||||
|
// xattrKey can be empty for listxattr operation.
|
||||||
|
type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
|
||||||
|
|
||||||
|
type copyDirOpts struct {
|
||||||
|
xeh XAttrErrorHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
type CopyDirOpt func(*copyDirOpts) error
|
||||||
|
|
||||||
|
// WithXAttrErrorHandler allows specifying XAttrErrorHandler
|
||||||
|
// If nil XAttrErrorHandler is specified (default), CopyDir stops
|
||||||
|
// on a non-nil xattr error.
|
||||||
|
func WithXAttrErrorHandler(xeh XAttrErrorHandler) CopyDirOpt {
|
||||||
|
return func(o *copyDirOpts) error {
|
||||||
|
o.xeh = xeh
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAllowXAttrErrors allows ignoring xattr errors.
|
||||||
|
func WithAllowXAttrErrors() CopyDirOpt {
|
||||||
|
xeh := func(dst, src, xattrKey string, err error) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return WithXAttrErrorHandler(xeh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDir copies the directory from src to dst.
|
||||||
|
// Most efficient copy of files is attempted.
|
||||||
|
func CopyDir(dst, src string, opts ...CopyDirOpt) error {
|
||||||
|
var o copyDirOpts
|
||||||
|
for _, opt := range opts {
|
||||||
|
if err := opt(&o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inodes := map[uint64]string{}
|
||||||
|
return copyDirectory(dst, src, inodes, &o)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
|
||||||
|
stat, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to stat %s", src)
|
||||||
|
}
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return errors.Errorf("source is not directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
if st, err := os.Stat(dst); err != nil {
|
||||||
|
if err := os.Mkdir(dst, stat.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to mkdir %s", dst)
|
||||||
|
}
|
||||||
|
} else if !st.IsDir() {
|
||||||
|
return errors.Errorf("cannot copy to non-directory: %s", dst)
|
||||||
|
} else {
|
||||||
|
if err := os.Chmod(dst, stat.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod on %s", dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fis, err := ioutil.ReadDir(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyFileInfo(stat, dst); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to copy file info for %s", dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fis {
|
||||||
|
source := filepath.Join(src, fi.Name())
|
||||||
|
target := filepath.Join(dst, fi.Name())
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fi.IsDir():
|
||||||
|
if err := copyDirectory(target, source, inodes, o); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case (fi.Mode() & os.ModeType) == 0:
|
||||||
|
link, err := getLinkSource(target, fi, inodes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get hardlink")
|
||||||
|
}
|
||||||
|
if link != "" {
|
||||||
|
if err := os.Link(link, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create hard link")
|
||||||
|
}
|
||||||
|
} else if err := CopyFile(target, source); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy files")
|
||||||
|
}
|
||||||
|
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
|
||||||
|
link, err := os.Readlink(source)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read link: %s", source)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(link, target); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create symlink: %s", target)
|
||||||
|
}
|
||||||
|
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
|
||||||
|
if err := copyDevice(target, fi); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create device")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: Support pipes and sockets
|
||||||
|
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
|
||||||
|
}
|
||||||
|
if err := copyFileInfo(fi, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy file info")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyXAttrs(target, source, o.xeh); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy xattrs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyFile copies the source file to the target.
|
||||||
|
// The most efficient means of copying is used for the platform.
|
||||||
|
func CopyFile(target, source string) error {
|
||||||
|
src, err := os.Open(source)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open source %s", source)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
tgt, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open target %s", target)
|
||||||
|
}
|
||||||
|
defer tgt.Close()
|
||||||
|
|
||||||
|
return copyFileContent(tgt, src)
|
||||||
|
}
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
// Normally if uid/gid are the same this would be a no-op, but some
|
||||||
|
// filesystems may still return EPERM... for instance NFS does this.
|
||||||
|
// In such a case, this is not an error.
|
||||||
|
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||||
|
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||||
|
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chown %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||||
|
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))}
|
||||||
|
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to utime %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxSSizeT = int64(^uint(0) >> 1)
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
st, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to stat source")
|
||||||
|
}
|
||||||
|
|
||||||
|
size := st.Size()
|
||||||
|
first := true
|
||||||
|
srcFd := int(src.Fd())
|
||||||
|
dstFd := int(dst.Fd())
|
||||||
|
|
||||||
|
for size > 0 {
|
||||||
|
// Ensure that we are never trying to copy more than SSIZE_MAX at a
|
||||||
|
// time and at the same time avoids overflows when the file is larger
|
||||||
|
// than 4GB on 32-bit systems.
|
||||||
|
var copySize int
|
||||||
|
if size > maxSSizeT {
|
||||||
|
copySize = int(maxSSizeT)
|
||||||
|
} else {
|
||||||
|
copySize = int(size)
|
||||||
|
}
|
||||||
|
n, err := unix.CopyFileRange(srcFd, nil, dstFd, nil, copySize, 0)
|
||||||
|
if err != nil {
|
||||||
|
if (err != unix.ENOSYS && err != unix.EXDEV) || !first {
|
||||||
|
return errors.Wrap(err, "copy file range failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err = io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
return errors.Wrap(err, "userspace copy failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
first = false
|
||||||
|
size -= int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
|
xattrKeys, err := sysx.LListxattr(src)
|
||||||
|
if err != nil {
|
||||||
|
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||||
|
if xeh != nil {
|
||||||
|
e = xeh(dst, src, "", e)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
for _, xattr := range xattrKeys {
|
||||||
|
data, err := sysx.LGetxattr(src, xattr)
|
||||||
|
if err != nil {
|
||||||
|
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||||
|
if xeh != nil {
|
||||||
|
if e = xeh(dst, src, xattr, e); e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||||
|
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||||
|
if xeh != nil {
|
||||||
|
if e = xeh(dst, src, xattr, e); e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unsupported stat type")
|
||||||
|
}
|
||||||
|
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
// +build solaris darwin freebsd
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
// Normally if uid/gid are the same this would be a no-op, but some
|
||||||
|
// filesystems may still return EPERM... for instance NFS does this.
|
||||||
|
// In such a case, this is not an error.
|
||||||
|
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||||
|
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||||
|
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chown %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||||
|
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timespec := []syscall.Timespec{StatAtime(st), StatMtime(st)}
|
||||||
|
if err := syscall.UtimesNano(name, timespec); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to utime %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err := io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
|
xattrKeys, err := sysx.LListxattr(src)
|
||||||
|
if err != nil {
|
||||||
|
e := errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||||
|
if xeh != nil {
|
||||||
|
e = xeh(dst, src, "", e)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
for _, xattr := range xattrKeys {
|
||||||
|
data, err := sysx.LGetxattr(src, xattr)
|
||||||
|
if err != nil {
|
||||||
|
e := errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||||
|
if xeh != nil {
|
||||||
|
if e = xeh(dst, src, xattr, e); e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||||
|
e := errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||||
|
if xeh != nil {
|
||||||
|
if e = xeh(dst, src, xattr, e); e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unsupported stat type")
|
||||||
|
}
|
||||||
|
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: copy windows specific metadata
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err := io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXAttrs(dst, src string, xeh XAttrErrorHandler) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
return errors.New("device copy not supported")
|
||||||
|
}
|
||||||
+326
@@ -0,0 +1,326 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChangeKind is the type of modification that
|
||||||
|
// a change is making.
|
||||||
|
type ChangeKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ChangeKindUnmodified represents an unmodified
|
||||||
|
// file
|
||||||
|
ChangeKindUnmodified = iota
|
||||||
|
|
||||||
|
// ChangeKindAdd represents an addition of
|
||||||
|
// a file
|
||||||
|
ChangeKindAdd
|
||||||
|
|
||||||
|
// ChangeKindModify represents a change to
|
||||||
|
// an existing file
|
||||||
|
ChangeKindModify
|
||||||
|
|
||||||
|
// ChangeKindDelete represents a delete of
|
||||||
|
// a file
|
||||||
|
ChangeKindDelete
|
||||||
|
)
|
||||||
|
|
||||||
|
func (k ChangeKind) String() string {
|
||||||
|
switch k {
|
||||||
|
case ChangeKindUnmodified:
|
||||||
|
return "unmodified"
|
||||||
|
case ChangeKindAdd:
|
||||||
|
return "add"
|
||||||
|
case ChangeKindModify:
|
||||||
|
return "modify"
|
||||||
|
case ChangeKindDelete:
|
||||||
|
return "delete"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change represents single change between a diff and its parent.
|
||||||
|
type Change struct {
|
||||||
|
Kind ChangeKind
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeFunc is the type of function called for each change
|
||||||
|
// computed during a directory changes calculation.
|
||||||
|
type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
|
||||||
|
|
||||||
|
// Changes computes changes between two directories calling the
|
||||||
|
// given change function for each computed change. The first
|
||||||
|
// directory is intended to the base directory and second
|
||||||
|
// directory the changed directory.
|
||||||
|
//
|
||||||
|
// The change callback is called by the order of path names and
|
||||||
|
// should be appliable in that order.
|
||||||
|
// Due to this apply ordering, the following is true
|
||||||
|
// - Removed directory trees only create a single change for the root
|
||||||
|
// directory removed. Remaining changes are implied.
|
||||||
|
// - A directory which is modified to become a file will not have
|
||||||
|
// delete entries for sub-path items, their removal is implied
|
||||||
|
// by the removal of the parent directory.
|
||||||
|
//
|
||||||
|
// Opaque directories will not be treated specially and each file
|
||||||
|
// removed from the base directory will show up as a removal.
|
||||||
|
//
|
||||||
|
// File content comparisons will be done on files which have timestamps
|
||||||
|
// which may have been truncated. If either of the files being compared
|
||||||
|
// has a zero value nanosecond value, each byte will be compared for
|
||||||
|
// differences. If 2 files have the same seconds value but different
|
||||||
|
// nanosecond values where one of those values is zero, the files will
|
||||||
|
// be considered unchanged if the content is the same. This behavior
|
||||||
|
// is to account for timestamp truncation during archiving.
|
||||||
|
func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error {
|
||||||
|
if a == "" {
|
||||||
|
logrus.Debugf("Using single walk diff for %s", b)
|
||||||
|
return addDirChanges(ctx, changeFn, b)
|
||||||
|
} else if diffOptions := detectDirDiff(b, a); diffOptions != nil {
|
||||||
|
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a)
|
||||||
|
return diffDirChanges(ctx, changeFn, a, diffOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("Using double walk diff for %s from %s", b, a)
|
||||||
|
return doubleWalkDiff(ctx, changeFn, a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error {
|
||||||
|
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebase path
|
||||||
|
path, err = filepath.Rel(root, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(string(os.PathSeparator), path)
|
||||||
|
|
||||||
|
// Skip root
|
||||||
|
if path == string(os.PathSeparator) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return changeFn(ChangeKindAdd, path, f, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// diffDirOptions is used when the diff can be directly calculated from
|
||||||
|
// a diff directory to its base, without walking both trees.
|
||||||
|
type diffDirOptions struct {
|
||||||
|
diffDir string
|
||||||
|
skipChange func(string) (bool, error)
|
||||||
|
deleteChange func(string, string, os.FileInfo) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// diffDirChanges walks the diff directory and compares changes against the base.
|
||||||
|
func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error {
|
||||||
|
changedDirs := make(map[string]struct{})
|
||||||
|
return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebase path
|
||||||
|
path, err = filepath.Rel(o.diffDir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(string(os.PathSeparator), path)
|
||||||
|
|
||||||
|
// Skip root
|
||||||
|
if path == string(os.PathSeparator) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle opaqueness, start new double walker at this
|
||||||
|
// location to get deletes, and skip tree in single walker
|
||||||
|
|
||||||
|
if o.skipChange != nil {
|
||||||
|
if skip, err := o.skipChange(path); skip {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var kind ChangeKind
|
||||||
|
|
||||||
|
deletedFile, err := o.deleteChange(o.diffDir, path, f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out what kind of modification happened
|
||||||
|
if deletedFile != "" {
|
||||||
|
path = deletedFile
|
||||||
|
kind = ChangeKindDelete
|
||||||
|
f = nil
|
||||||
|
} else {
|
||||||
|
// Otherwise, the file was added
|
||||||
|
kind = ChangeKindAdd
|
||||||
|
|
||||||
|
// ...Unless it already existed in a base, in which case, it's a modification
|
||||||
|
stat, err := os.Stat(filepath.Join(base, path))
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
// The file existed in the base, so that's a modification
|
||||||
|
|
||||||
|
// However, if it's a directory, maybe it wasn't actually modified.
|
||||||
|
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
||||||
|
if stat.IsDir() && f.IsDir() {
|
||||||
|
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
|
||||||
|
// Both directories are the same, don't record the change
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kind = ChangeKindModify
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
|
||||||
|
// This block is here to ensure the change is recorded even if the
|
||||||
|
// modify time, mode and size of the parent directory in the rw and ro layers are all equal.
|
||||||
|
// Check https://github.com/docker/docker/pull/13590 for details.
|
||||||
|
if f.IsDir() {
|
||||||
|
changedDirs[path] = struct{}{}
|
||||||
|
}
|
||||||
|
if kind == ChangeKindAdd || kind == ChangeKindDelete {
|
||||||
|
parent := filepath.Dir(path)
|
||||||
|
if _, ok := changedDirs[parent]; !ok && parent != "/" {
|
||||||
|
pi, err := os.Stat(filepath.Join(o.diffDir, parent))
|
||||||
|
if err := changeFn(ChangeKindModify, parent, pi, err); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
changedDirs[parent] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changeFn(kind, path, f, nil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// doubleWalkDiff walks both directories to create a diff
|
||||||
|
func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) {
|
||||||
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
var (
|
||||||
|
c1 = make(chan *currentPath)
|
||||||
|
c2 = make(chan *currentPath)
|
||||||
|
|
||||||
|
f1, f2 *currentPath
|
||||||
|
rmdir string
|
||||||
|
)
|
||||||
|
g.Go(func() error {
|
||||||
|
defer close(c1)
|
||||||
|
return pathWalk(ctx, a, c1)
|
||||||
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
defer close(c2)
|
||||||
|
return pathWalk(ctx, b, c2)
|
||||||
|
})
|
||||||
|
g.Go(func() error {
|
||||||
|
for c1 != nil || c2 != nil {
|
||||||
|
if f1 == nil && c1 != nil {
|
||||||
|
f1, err = nextPath(ctx, c1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f1 == nil {
|
||||||
|
c1 = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f2 == nil && c2 != nil {
|
||||||
|
f2, err = nextPath(ctx, c2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f2 == nil {
|
||||||
|
c2 = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f1 == nil && f2 == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var f os.FileInfo
|
||||||
|
k, p := pathChange(f1, f2)
|
||||||
|
switch k {
|
||||||
|
case ChangeKindAdd:
|
||||||
|
if rmdir != "" {
|
||||||
|
rmdir = ""
|
||||||
|
}
|
||||||
|
f = f2.f
|
||||||
|
f2 = nil
|
||||||
|
case ChangeKindDelete:
|
||||||
|
// Check if this file is already removed by being
|
||||||
|
// under of a removed directory
|
||||||
|
if rmdir != "" && strings.HasPrefix(f1.path, rmdir) {
|
||||||
|
f1 = nil
|
||||||
|
continue
|
||||||
|
} else if f1.f.IsDir() {
|
||||||
|
rmdir = f1.path + string(os.PathSeparator)
|
||||||
|
} else if rmdir != "" {
|
||||||
|
rmdir = ""
|
||||||
|
}
|
||||||
|
f1 = nil
|
||||||
|
case ChangeKindModify:
|
||||||
|
same, err := sameFile(f1, f2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f1.f.IsDir() && !f2.f.IsDir() {
|
||||||
|
rmdir = f1.path + string(os.PathSeparator)
|
||||||
|
} else if rmdir != "" {
|
||||||
|
rmdir = ""
|
||||||
|
}
|
||||||
|
f = f2.f
|
||||||
|
f1 = nil
|
||||||
|
f2 = nil
|
||||||
|
if same {
|
||||||
|
if !isLinked(f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k = ChangeKindUnmodified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := changeFn(k, p, f, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return g.Wait()
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// detectDirDiff returns diff dir options if a directory could
|
||||||
|
// be found in the mount info for upper which is the direct
|
||||||
|
// diff with the provided lower directory
|
||||||
|
func detectDirDiff(upper, lower string) *diffDirOptions {
|
||||||
|
// TODO: get mount options for upper
|
||||||
|
// TODO: detect AUFS
|
||||||
|
// TODO: detect overlay
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareSysStat returns whether the stats are equivalent,
|
||||||
|
// whether the files are considered the same file, and
|
||||||
|
// an error
|
||||||
|
func compareSysStat(s1, s2 interface{}) (bool, error) {
|
||||||
|
ls1, ok := s1.(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
ls2, ok := s2.(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ls1.Mode == ls2.Mode && ls1.Uid == ls2.Uid && ls1.Gid == ls2.Gid && ls1.Rdev == ls2.Rdev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareCapabilities(p1, p2 string) (bool, error) {
|
||||||
|
c1, err := sysx.LGetxattr(p1, "security.capability")
|
||||||
|
if err != nil && err != sysx.ENODATA {
|
||||||
|
return false, errors.Wrapf(err, "failed to get xattr for %s", p1)
|
||||||
|
}
|
||||||
|
c2, err := sysx.LGetxattr(p2, "security.capability")
|
||||||
|
if err != nil && err != sysx.ENODATA {
|
||||||
|
return false, errors.Wrapf(err, "failed to get xattr for %s", p2)
|
||||||
|
}
|
||||||
|
return bytes.Equal(c1, c2), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLinked(f os.FileInfo) bool {
|
||||||
|
s, ok := f.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !f.IsDir() && s.Nlink > 1
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func detectDirDiff(upper, lower string) *diffDirOptions {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareSysStat(s1, s2 interface{}) (bool, error) {
|
||||||
|
f1, ok := s1.(windows.Win32FileAttributeData)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
f2, ok := s2.(windows.Win32FileAttributeData)
|
||||||
|
if !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return f1.FileAttributes == f2.FileAttributes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareCapabilities(p1, p2 string) (bool, error) {
|
||||||
|
// TODO: Use windows equivalent
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLinked(os.FileInfo) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func locateDummyIfEmpty(path string) (string, error) {
|
||||||
|
children, err := ioutil.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(children) != 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
dummyFile, err := ioutil.TempFile(path, "fsutils-dummy")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
name := dummyFile.Name()
|
||||||
|
err = dummyFile.Close()
|
||||||
|
return name, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportsDType returns whether the filesystem mounted on path supports d_type
|
||||||
|
func SupportsDType(path string) (bool, error) {
|
||||||
|
// locate dummy so that we have at least one dirent
|
||||||
|
dummy, err := locateDummyIfEmpty(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if dummy != "" {
|
||||||
|
defer os.Remove(dummy)
|
||||||
|
}
|
||||||
|
|
||||||
|
visited := 0
|
||||||
|
supportsDType := true
|
||||||
|
fn := func(ent *syscall.Dirent) bool {
|
||||||
|
visited++
|
||||||
|
if ent.Type == syscall.DT_UNKNOWN {
|
||||||
|
supportsDType = false
|
||||||
|
// stop iteration
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// continue iteration
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if err = iterateReadDir(path, fn); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if visited == 0 {
|
||||||
|
return false, fmt.Errorf("did not hit any dirent during iteration %s", path)
|
||||||
|
}
|
||||||
|
return supportsDType, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterateReadDir(path string, fn func(*syscall.Dirent) bool) error {
|
||||||
|
d, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer d.Close()
|
||||||
|
fd := int(d.Fd())
|
||||||
|
buf := make([]byte, 4096)
|
||||||
|
for {
|
||||||
|
nbytes, err := syscall.ReadDirent(fd, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if nbytes == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for off := 0; off < nbytes; {
|
||||||
|
ent := (*syscall.Dirent)(unsafe.Pointer(&buf[off]))
|
||||||
|
if stop := fn(ent); stop {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
off += int(ent.Reclen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// Usage of disk information
|
||||||
|
type Usage struct {
|
||||||
|
Inodes int64
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiskUsage counts the number of inodes and disk usage for the resources under
|
||||||
|
// path.
|
||||||
|
func DiskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
||||||
|
return diskUsage(ctx, roots...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiffUsage counts the numbers of inodes and disk usage in the
|
||||||
|
// diff between the 2 directories. The first path is intended
|
||||||
|
// as the base directory and the second as the changed directory.
|
||||||
|
func DiffUsage(ctx context.Context, a, b string) (Usage, error) {
|
||||||
|
return diffUsage(ctx, a, b)
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
// TODO(stevvooe): Can probably reduce memory usage by not tracking
|
||||||
|
// device, but we can leave this right for now.
|
||||||
|
dev, ino uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInode(stat *syscall.Stat_t) inode {
|
||||||
|
return inode{
|
||||||
|
// Dev is uint32 on darwin/bsd, uint64 on linux/solaris
|
||||||
|
dev: uint64(stat.Dev), // nolint: unconvert
|
||||||
|
// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
|
||||||
|
ino: uint64(stat.Ino), // nolint: unconvert
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
size int64
|
||||||
|
inodes = map[inode]struct{}{} // expensive!
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, root := range roots {
|
||||||
|
if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
inoKey := newInode(fi.Sys().(*syscall.Stat_t))
|
||||||
|
if _, ok := inodes[inoKey]; !ok {
|
||||||
|
inodes[inoKey] = struct{}{}
|
||||||
|
size += fi.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return Usage{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Usage{
|
||||||
|
Inodes: int64(len(inodes)),
|
||||||
|
Size: size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffUsage(ctx context.Context, a, b string) (Usage, error) {
|
||||||
|
var (
|
||||||
|
size int64
|
||||||
|
inodes = map[inode]struct{}{} // expensive!
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == ChangeKindAdd || kind == ChangeKindModify {
|
||||||
|
inoKey := newInode(fi.Sys().(*syscall.Stat_t))
|
||||||
|
if _, ok := inodes[inoKey]; !ok {
|
||||||
|
inodes[inoKey] = struct{}{}
|
||||||
|
size += fi.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return Usage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Usage{
|
||||||
|
Inodes: int64(len(inodes)),
|
||||||
|
Size: size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func diskUsage(ctx context.Context, roots ...string) (Usage, error) {
|
||||||
|
var (
|
||||||
|
size int64
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(stevvooe): Support inodes (or equivalent) for windows.
|
||||||
|
|
||||||
|
for _, root := range roots {
|
||||||
|
if err := filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
size += fi.Size()
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return Usage{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Usage{
|
||||||
|
Size: size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func diffUsage(ctx context.Context, a, b string) (Usage, error) {
|
||||||
|
var (
|
||||||
|
size int64
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := Changes(ctx, a, b, func(kind ChangeKind, _ string, fi os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == ChangeKindAdd || kind == ChangeKindModify {
|
||||||
|
size += fi.Size()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return Usage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Usage{
|
||||||
|
Size: size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// GetLinkInfo returns an identifier representing the node a hardlink is pointing
|
||||||
|
// to. If the file is not hard linked then 0 will be returned.
|
||||||
|
func GetLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||||
|
return getLinkInfo(fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLinkSource returns a path for the given name and
|
||||||
|
// file info to its link source in the provided inode
|
||||||
|
// map. If the given file name is not in the map and
|
||||||
|
// has other links, it is added to the inode map
|
||||||
|
// to be a source for other link locations.
|
||||||
|
func getLinkSource(name string, fi os.FileInfo, inodes map[uint64]string) (string, error) {
|
||||||
|
inode, isHardlink := getLinkInfo(fi)
|
||||||
|
if !isHardlink {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
path, ok := inodes[inode]
|
||||||
|
if !ok {
|
||||||
|
inodes[inode] = name
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||||
|
s, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ino is uint32 on bsd, uint64 on darwin/linux/solaris
|
||||||
|
return uint64(s.Ino), !fi.IsDir() && s.Nlink > 1 // nolint: unconvert
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func getLinkInfo(fi os.FileInfo) (uint64, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
+313
@@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errTooManyLinks = errors.New("too many links")
|
||||||
|
)
|
||||||
|
|
||||||
|
type currentPath struct {
|
||||||
|
path string
|
||||||
|
f os.FileInfo
|
||||||
|
fullPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathChange(lower, upper *currentPath) (ChangeKind, string) {
|
||||||
|
if lower == nil {
|
||||||
|
if upper == nil {
|
||||||
|
panic("cannot compare nil paths")
|
||||||
|
}
|
||||||
|
return ChangeKindAdd, upper.path
|
||||||
|
}
|
||||||
|
if upper == nil {
|
||||||
|
return ChangeKindDelete, lower.path
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i := directoryCompare(lower.path, upper.path); {
|
||||||
|
case i < 0:
|
||||||
|
// File in lower that is not in upper
|
||||||
|
return ChangeKindDelete, lower.path
|
||||||
|
case i > 0:
|
||||||
|
// File in upper that is not in lower
|
||||||
|
return ChangeKindAdd, upper.path
|
||||||
|
default:
|
||||||
|
return ChangeKindModify, upper.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func directoryCompare(a, b string) int {
|
||||||
|
l := len(a)
|
||||||
|
if len(b) < l {
|
||||||
|
l = len(b)
|
||||||
|
}
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
c1, c2 := a[i], b[i]
|
||||||
|
if c1 == filepath.Separator {
|
||||||
|
c1 = byte(0)
|
||||||
|
}
|
||||||
|
if c2 == filepath.Separator {
|
||||||
|
c2 = byte(0)
|
||||||
|
}
|
||||||
|
if c1 < c2 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if c1 > c2 {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(a) < len(b) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if len(a) > len(b) {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameFile(f1, f2 *currentPath) (bool, error) {
|
||||||
|
if os.SameFile(f1.f, f2.f) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
equalStat, err := compareSysStat(f1.f.Sys(), f2.f.Sys())
|
||||||
|
if err != nil || !equalStat {
|
||||||
|
return equalStat, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if eq, err := compareCapabilities(f1.fullPath, f2.fullPath); err != nil || !eq {
|
||||||
|
return eq, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not a directory also check size, modtime, and content
|
||||||
|
if !f1.f.IsDir() {
|
||||||
|
if f1.f.Size() != f2.f.Size() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
t1 := f1.f.ModTime()
|
||||||
|
t2 := f2.f.ModTime()
|
||||||
|
|
||||||
|
if t1.Unix() != t2.Unix() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the timestamp may have been truncated in both of the
|
||||||
|
// files, check content of file to determine difference
|
||||||
|
if t1.Nanosecond() == 0 && t2.Nanosecond() == 0 {
|
||||||
|
var eq bool
|
||||||
|
if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
|
||||||
|
eq, err = compareSymlinkTarget(f1.fullPath, f2.fullPath)
|
||||||
|
} else if f1.f.Size() > 0 {
|
||||||
|
eq, err = compareFileContent(f1.fullPath, f2.fullPath)
|
||||||
|
}
|
||||||
|
if err != nil || !eq {
|
||||||
|
return eq, err
|
||||||
|
}
|
||||||
|
} else if t1.Nanosecond() != t2.Nanosecond() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareSymlinkTarget(p1, p2 string) (bool, error) {
|
||||||
|
t1, err := os.Readlink(p1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
t2, err := os.Readlink(p2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return t1 == t2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const compareChuckSize = 32 * 1024
|
||||||
|
|
||||||
|
// compareFileContent compares the content of 2 same sized files
|
||||||
|
// by comparing each byte.
|
||||||
|
func compareFileContent(p1, p2 string) (bool, error) {
|
||||||
|
f1, err := os.Open(p1)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f1.Close()
|
||||||
|
f2, err := os.Open(p2)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
|
||||||
|
b1 := make([]byte, compareChuckSize)
|
||||||
|
b2 := make([]byte, compareChuckSize)
|
||||||
|
for {
|
||||||
|
n1, err1 := f1.Read(b1)
|
||||||
|
if err1 != nil && err1 != io.EOF {
|
||||||
|
return false, err1
|
||||||
|
}
|
||||||
|
n2, err2 := f2.Read(b2)
|
||||||
|
if err2 != nil && err2 != io.EOF {
|
||||||
|
return false, err2
|
||||||
|
}
|
||||||
|
if n1 != n2 || !bytes.Equal(b1[:n1], b2[:n2]) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err1 == io.EOF && err2 == io.EOF {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathWalk(ctx context.Context, root string, pathC chan<- *currentPath) error {
|
||||||
|
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebase path
|
||||||
|
path, err = filepath.Rel(root, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(string(os.PathSeparator), path)
|
||||||
|
|
||||||
|
// Skip root
|
||||||
|
if path == string(os.PathSeparator) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p := ¤tPath{
|
||||||
|
path: path,
|
||||||
|
f: f,
|
||||||
|
fullPath: filepath.Join(root, path),
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case pathC <- p:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextPath(ctx context.Context, pathC <-chan *currentPath) (*currentPath, error) {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case p := <-pathC:
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootPath joins a path with a root, evaluating and bounding any
|
||||||
|
// symlink to the root directory.
|
||||||
|
func RootPath(root, path string) (string, error) {
|
||||||
|
if path == "" {
|
||||||
|
return root, nil
|
||||||
|
}
|
||||||
|
var linksWalked int // to protect against cycles
|
||||||
|
for {
|
||||||
|
i := linksWalked
|
||||||
|
newpath, err := walkLinks(root, path, &linksWalked)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
path = newpath
|
||||||
|
if i == linksWalked {
|
||||||
|
newpath = filepath.Join("/", newpath)
|
||||||
|
if path == newpath {
|
||||||
|
return filepath.Join(root, newpath), nil
|
||||||
|
}
|
||||||
|
path = newpath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
|
||||||
|
if *linksWalked > 255 {
|
||||||
|
return "", false, errTooManyLinks
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join("/", path)
|
||||||
|
if path == "/" {
|
||||||
|
return path, false, nil
|
||||||
|
}
|
||||||
|
realPath := filepath.Join(root, path)
|
||||||
|
|
||||||
|
fi, err := os.Lstat(realPath)
|
||||||
|
if err != nil {
|
||||||
|
// If path does not yet exist, treat as non-symlink
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return path, false, nil
|
||||||
|
}
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
if fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
return path, false, nil
|
||||||
|
}
|
||||||
|
newpath, err = os.Readlink(realPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
*linksWalked++
|
||||||
|
return newpath, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkLinks(root, path string, linksWalked *int) (string, error) {
|
||||||
|
switch dir, file := filepath.Split(path); {
|
||||||
|
case dir == "":
|
||||||
|
newpath, _, err := walkLink(root, file, linksWalked)
|
||||||
|
return newpath, err
|
||||||
|
case file == "":
|
||||||
|
if os.IsPathSeparator(dir[len(dir)-1]) {
|
||||||
|
if dir == "/" {
|
||||||
|
return dir, nil
|
||||||
|
}
|
||||||
|
return walkLinks(root, dir[:len(dir)-1], linksWalked)
|
||||||
|
}
|
||||||
|
newpath, _, err := walkLink(root, dir, linksWalked)
|
||||||
|
return newpath, err
|
||||||
|
default:
|
||||||
|
newdir, err := walkLinks(root, dir, linksWalked)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !islink {
|
||||||
|
return newpath, nil
|
||||||
|
}
|
||||||
|
if filepath.IsAbs(newpath) {
|
||||||
|
return newpath, nil
|
||||||
|
}
|
||||||
|
return filepath.Join(newdir, newpath), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
// +build darwin freebsd
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatAtime returns the access time from a stat struct
|
||||||
|
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
|
||||||
|
return st.Atimespec
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatCtime returns the created time from a stat struct
|
||||||
|
func StatCtime(st *syscall.Stat_t) syscall.Timespec {
|
||||||
|
return st.Ctimespec
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatMtime returns the modified time from a stat struct
|
||||||
|
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
|
||||||
|
return st.Mtimespec
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatATimeAsTime returns the access time as a time.Time
|
||||||
|
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(int64(st.Atimespec.Sec), int64(st.Atimespec.Nsec)) // nolint: unconvert
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StatAtime returns the Atim
|
||||||
|
func StatAtime(st *syscall.Stat_t) syscall.Timespec {
|
||||||
|
return st.Atim
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatCtime returns the Ctim
|
||||||
|
func StatCtime(st *syscall.Stat_t) syscall.Timespec {
|
||||||
|
return st.Ctim
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatMtime returns the Mtim
|
||||||
|
func StatMtime(st *syscall.Stat_t) syscall.Timespec {
|
||||||
|
return st.Mtim
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatATimeAsTime returns st.Atim as a time.Time
|
||||||
|
func StatATimeAsTime(st *syscall.Stat_t) time.Time {
|
||||||
|
// The int64 conversions ensure the line compiles for 32-bit systems as well.
|
||||||
|
return time.Unix(int64(st.Atim.Sec), int64(st.Atim.Nsec)) // nolint: unconvert
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Gnu tar and the go tar writer don't have sub-second mtime
|
||||||
|
// precision, which is problematic when we apply changes via tar
|
||||||
|
// files, we handle this by comparing for exact times, *or* same
|
||||||
|
// second count and either a or b having exactly 0 nanoseconds
|
||||||
|
func sameFsTime(a, b time.Time) bool {
|
||||||
|
return a == b ||
|
||||||
|
(a.Unix() == b.Unix() &&
|
||||||
|
(a.Nanosecond() == 0 || b.Nanosecond() == 0))
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package syscallx
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link.
|
||||||
|
func Readlink(path string, buf []byte) (n int, err error) {
|
||||||
|
return syscall.Readlink(path, buf)
|
||||||
|
}
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package syscallx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
|
||||||
|
// GenericReparseBuffer
|
||||||
|
reparseBuffer byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type mountPointReparseBuffer struct {
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
PathBuffer [1]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
type symbolicLinkReparseBuffer struct {
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
Flags uint32
|
||||||
|
PathBuffer [1]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
|
||||||
|
_SYMLINK_FLAG_RELATIVE = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link.
|
||||||
|
func Readlink(path string, buf []byte) (n int, err error) {
|
||||||
|
fd, err := syscall.CreateFile(syscall.StringToUTF16Ptr(path), syscall.GENERIC_READ, 0, nil, syscall.OPEN_EXISTING,
|
||||||
|
syscall.FILE_FLAG_OPEN_REPARSE_POINT|syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
defer syscall.CloseHandle(fd)
|
||||||
|
|
||||||
|
rdbbuf := make([]byte, syscall.MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
|
||||||
|
var bytesReturned uint32
|
||||||
|
err = syscall.DeviceIoControl(fd, syscall.FSCTL_GET_REPARSE_POINT, nil, 0, &rdbbuf[0], uint32(len(rdbbuf)), &bytesReturned, nil)
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rdb := (*reparseDataBuffer)(unsafe.Pointer(&rdbbuf[0]))
|
||||||
|
var s string
|
||||||
|
switch rdb.ReparseTag {
|
||||||
|
case syscall.IO_REPARSE_TAG_SYMLINK:
|
||||||
|
data := (*symbolicLinkReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
|
||||||
|
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
|
||||||
|
s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameOffset+data.SubstituteNameLength)/2])
|
||||||
|
if data.Flags&_SYMLINK_FLAG_RELATIVE == 0 {
|
||||||
|
if len(s) >= 4 && s[:4] == `\??\` {
|
||||||
|
s = s[4:]
|
||||||
|
switch {
|
||||||
|
case len(s) >= 2 && s[1] == ':': // \??\C:\foo\bar
|
||||||
|
// do nothing
|
||||||
|
case len(s) >= 4 && s[:4] == `UNC\`: // \??\UNC\foo\bar
|
||||||
|
s = `\\` + s[4:]
|
||||||
|
default:
|
||||||
|
// unexpected; do nothing
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// unexpected; do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case _IO_REPARSE_TAG_MOUNT_POINT:
|
||||||
|
data := (*mountPointReparseBuffer)(unsafe.Pointer(&rdb.reparseBuffer))
|
||||||
|
p := (*[0xffff]uint16)(unsafe.Pointer(&data.PathBuffer[0]))
|
||||||
|
s = syscall.UTF16ToString(p[data.SubstituteNameOffset/2 : (data.SubstituteNameOffset+data.SubstituteNameLength)/2])
|
||||||
|
if len(s) >= 4 && s[:4] == `\??\` { // \??\C:\foo\bar
|
||||||
|
if len(s) < 48 || s[:11] != `\??\Volume{` {
|
||||||
|
s = s[4:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// unexpected; do nothing
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// the path is not a symlink or junction but another type of reparse
|
||||||
|
// point
|
||||||
|
return -1, syscall.ENOENT
|
||||||
|
}
|
||||||
|
n = copy(buf, []byte(s))
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
This package is for internal use only. It is intended to only have
|
||||||
|
temporary changes before they are upstreamed to golang.org/x/sys/
|
||||||
|
(a.k.a. https://github.com/golang/sys).
|
||||||
+128
@@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/syscallx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link.
|
||||||
|
// If there is an error, it will be of type *PathError.
|
||||||
|
func Readlink(name string) (string, error) {
|
||||||
|
for len := 128; ; len *= 2 {
|
||||||
|
b := make([]byte, len)
|
||||||
|
n, e := fixCount(syscallx.Readlink(fixLongPath(name), b))
|
||||||
|
if e != nil {
|
||||||
|
return "", &os.PathError{Op: "readlink", Path: name, Err: e}
|
||||||
|
}
|
||||||
|
if n < len {
|
||||||
|
return string(b[0:n]), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Many functions in package syscall return a count of -1 instead of 0.
|
||||||
|
// Using fixCount(call()) instead of call() corrects the count.
|
||||||
|
func fixCount(n int, err error) (int, error) {
|
||||||
|
if n < 0 {
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// fixLongPath returns the extended-length (\\?\-prefixed) form of
|
||||||
|
// path when needed, in order to avoid the default 260 character file
|
||||||
|
// path limit imposed by Windows. If path is not easily converted to
|
||||||
|
// the extended-length form (for example, if path is a relative path
|
||||||
|
// or contains .. elements), or is short enough, fixLongPath returns
|
||||||
|
// path unmodified.
|
||||||
|
//
|
||||||
|
// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
|
||||||
|
func fixLongPath(path string) string {
|
||||||
|
// Do nothing (and don't allocate) if the path is "short".
|
||||||
|
// Empirically (at least on the Windows Server 2013 builder),
|
||||||
|
// the kernel is arbitrarily okay with < 248 bytes. That
|
||||||
|
// matches what the docs above say:
|
||||||
|
// "When using an API to create a directory, the specified
|
||||||
|
// path cannot be so long that you cannot append an 8.3 file
|
||||||
|
// name (that is, the directory name cannot exceed MAX_PATH
|
||||||
|
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
|
||||||
|
//
|
||||||
|
// The MSDN docs appear to say that a normal path that is 248 bytes long
|
||||||
|
// will work; empirically the path must be less then 248 bytes long.
|
||||||
|
if len(path) < 248 {
|
||||||
|
// Don't fix. (This is how Go 1.7 and earlier worked,
|
||||||
|
// not automatically generating the \\?\ form)
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// The extended form begins with \\?\, as in
|
||||||
|
// \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt.
|
||||||
|
// The extended form disables evaluation of . and .. path
|
||||||
|
// elements and disables the interpretation of / as equivalent
|
||||||
|
// to \. The conversion here rewrites / to \ and elides
|
||||||
|
// . elements as well as trailing or duplicate separators. For
|
||||||
|
// simplicity it avoids the conversion entirely for relative
|
||||||
|
// paths or paths containing .. elements. For now,
|
||||||
|
// \\server\share paths are not converted to
|
||||||
|
// \\?\UNC\server\share paths because the rules for doing so
|
||||||
|
// are less well-specified.
|
||||||
|
if len(path) >= 2 && path[:2] == `\\` {
|
||||||
|
// Don't canonicalize UNC paths.
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
// Relative path
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
const prefix = `\\?`
|
||||||
|
|
||||||
|
pathbuf := make([]byte, len(prefix)+len(path)+len(`\`))
|
||||||
|
copy(pathbuf, prefix)
|
||||||
|
n := len(path)
|
||||||
|
r, w := 0, len(prefix)
|
||||||
|
for r < n {
|
||||||
|
switch {
|
||||||
|
case os.IsPathSeparator(path[r]):
|
||||||
|
// empty block
|
||||||
|
r++
|
||||||
|
case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])):
|
||||||
|
// /./
|
||||||
|
r++
|
||||||
|
case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])):
|
||||||
|
// /../ is currently unhandled
|
||||||
|
return path
|
||||||
|
default:
|
||||||
|
pathbuf[w] = '\\'
|
||||||
|
w++
|
||||||
|
for ; r < n && !os.IsPathSeparator(path[r]); r++ {
|
||||||
|
pathbuf[w] = path[r]
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// A drive's root directory needs a trailing \
|
||||||
|
if w == len(`\\?\c:`) {
|
||||||
|
pathbuf[w] = '\\'
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
return string(pathbuf[:w])
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Copyright The containerd Authors.
|
||||||
|
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
mksyscall="$(go env GOROOT)/src/syscall/mksyscall.pl"
|
||||||
|
|
||||||
|
fix() {
|
||||||
|
sed 's,^package syscall$,package sysx,' \
|
||||||
|
| sed 's,^import "unsafe"$,import (\n\t"syscall"\n\t"unsafe"\n),' \
|
||||||
|
| gofmt -r='BytePtrFromString -> syscall.BytePtrFromString' \
|
||||||
|
| gofmt -r='Syscall6 -> syscall.Syscall6' \
|
||||||
|
| gofmt -r='Syscall -> syscall.Syscall' \
|
||||||
|
| gofmt -r='SYS_GETXATTR -> syscall.SYS_GETXATTR' \
|
||||||
|
| gofmt -r='SYS_LISTXATTR -> syscall.SYS_LISTXATTR' \
|
||||||
|
| gofmt -r='SYS_SETXATTR -> syscall.SYS_SETXATTR' \
|
||||||
|
| gofmt -r='SYS_REMOVEXATTR -> syscall.SYS_REMOVEXATTR' \
|
||||||
|
| gofmt -r='SYS_LGETXATTR -> syscall.SYS_LGETXATTR' \
|
||||||
|
| gofmt -r='SYS_LLISTXATTR -> syscall.SYS_LLISTXATTR' \
|
||||||
|
| gofmt -r='SYS_LSETXATTR -> syscall.SYS_LSETXATTR' \
|
||||||
|
| gofmt -r='SYS_LREMOVEXATTR -> syscall.SYS_LREMOVEXATTR'
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$GOARCH" == "" ] || [ "$GOOS" == "" ]; then
|
||||||
|
echo "Must specify \$GOARCH and \$GOOS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkargs=""
|
||||||
|
|
||||||
|
if [ "$GOARCH" == "386" ] || [ "$GOARCH" == "arm" ]; then
|
||||||
|
mkargs="-l32"
|
||||||
|
fi
|
||||||
|
|
||||||
|
for f in "$@"; do
|
||||||
|
$mksyscall $mkargs "${f}_${GOOS}.go" | fix > "${f}_${GOOS}_${GOARCH}.go"
|
||||||
|
done
|
||||||
|
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ENODATA = syscall.ENODATA
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This should actually be a set that contains ENOENT and EPERM
|
||||||
|
const ENODATA = syscall.ENOENT
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
// +build darwin freebsd
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ENODATA = syscall.ENOATTR
|
||||||
+125
@@ -0,0 +1,125 @@
|
|||||||
|
// +build linux darwin
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listxattr calls syscall listxattr and reads all content
|
||||||
|
// and returns a string array
|
||||||
|
func Listxattr(path string) ([]string, error) {
|
||||||
|
return listxattrAll(path, unix.Listxattr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removexattr calls syscall removexattr
|
||||||
|
func Removexattr(path string, attr string) (err error) {
|
||||||
|
return unix.Removexattr(path, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setxattr calls syscall setxattr
|
||||||
|
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||||
|
return unix.Setxattr(path, attr, data, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getxattr calls syscall getxattr
|
||||||
|
func Getxattr(path, attr string) ([]byte, error) {
|
||||||
|
return getxattrAll(path, attr, unix.Getxattr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LListxattr lists xattrs, not following symlinks
|
||||||
|
func LListxattr(path string) ([]string, error) {
|
||||||
|
return listxattrAll(path, unix.Llistxattr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LRemovexattr removes an xattr, not following symlinks
|
||||||
|
func LRemovexattr(path string, attr string) (err error) {
|
||||||
|
return unix.Lremovexattr(path, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LSetxattr sets an xattr, not following symlinks
|
||||||
|
func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||||
|
return unix.Lsetxattr(path, attr, data, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LGetxattr gets an xattr, not following symlinks
|
||||||
|
func LGetxattr(path, attr string) ([]byte, error) {
|
||||||
|
return getxattrAll(path, attr, unix.Lgetxattr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultXattrBufferSize = 5
|
||||||
|
|
||||||
|
type listxattrFunc func(path string, dest []byte) (int, error)
|
||||||
|
|
||||||
|
func listxattrAll(path string, listFunc listxattrFunc) ([]string, error) {
|
||||||
|
var p []byte // nil on first execution
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := listFunc(path, p) // first call gets buffer size.
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > len(p) {
|
||||||
|
p = make([]byte, n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
p = p[:n]
|
||||||
|
|
||||||
|
ps := bytes.Split(bytes.TrimSuffix(p, []byte{0}), []byte{0})
|
||||||
|
var entries []string
|
||||||
|
for _, p := range ps {
|
||||||
|
s := string(p)
|
||||||
|
if s != "" {
|
||||||
|
entries = append(entries, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type getxattrFunc func(string, string, []byte) (int, error)
|
||||||
|
|
||||||
|
func getxattrAll(path, attr string, getFunc getxattrFunc) ([]byte, error) {
|
||||||
|
p := make([]byte, defaultXattrBufferSize)
|
||||||
|
for {
|
||||||
|
n, err := getFunc(path, attr, p)
|
||||||
|
if err != nil {
|
||||||
|
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERANGE {
|
||||||
|
p = make([]byte, len(p)*2) // this can't be ideal.
|
||||||
|
continue // try again!
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// realloc to correct size and repeat
|
||||||
|
if n > len(p) {
|
||||||
|
p = make([]byte, n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return p[:n], nil
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
// +build !linux,!darwin
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sysx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var unsupported = errors.New("extended attributes unsupported on " + runtime.GOOS)
|
||||||
|
|
||||||
|
// Listxattr calls syscall listxattr and reads all content
|
||||||
|
// and returns a string array
|
||||||
|
func Listxattr(path string) ([]string, error) {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removexattr calls syscall removexattr
|
||||||
|
func Removexattr(path string, attr string) (err error) {
|
||||||
|
return unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setxattr calls syscall setxattr
|
||||||
|
func Setxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||||
|
return unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getxattr calls syscall getxattr
|
||||||
|
func Getxattr(path, attr string) ([]byte, error) {
|
||||||
|
return []byte{}, unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// LListxattr lists xattrs, not following symlinks
|
||||||
|
func LListxattr(path string) ([]string, error) {
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LRemovexattr removes an xattr, not following symlinks
|
||||||
|
func LRemovexattr(path string, attr string) (err error) {
|
||||||
|
return unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// LSetxattr sets an xattr, not following symlinks
|
||||||
|
func LSetxattr(path string, attr string, data []byte, flags int) (err error) {
|
||||||
|
return unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// LGetxattr gets an xattr, not following symlinks
|
||||||
|
func LGetxattr(path, attr string) ([]byte, error) {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
+267
@@ -0,0 +1,267 @@
|
|||||||
|
package errcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorCoder is the base interface for ErrorCode and Error allowing
|
||||||
|
// users of each to just call ErrorCode to get the real ID of each
|
||||||
|
type ErrorCoder interface {
|
||||||
|
ErrorCode() ErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorCode represents the error type. The errors are serialized via strings
|
||||||
|
// and the integer format may change and should *never* be exported.
|
||||||
|
type ErrorCode int
|
||||||
|
|
||||||
|
var _ error = ErrorCode(0)
|
||||||
|
|
||||||
|
// ErrorCode just returns itself
|
||||||
|
func (ec ErrorCode) ErrorCode() ErrorCode {
|
||||||
|
return ec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the ID/Value
|
||||||
|
func (ec ErrorCode) Error() string {
|
||||||
|
// NOTE(stevvooe): Cannot use message here since it may have unpopulated args.
|
||||||
|
return strings.ToLower(strings.Replace(ec.String(), "_", " ", -1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Descriptor returns the descriptor for the error code.
|
||||||
|
func (ec ErrorCode) Descriptor() ErrorDescriptor {
|
||||||
|
d, ok := errorCodeToDescriptors[ec]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return ErrorCodeUnknown.Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the canonical identifier for this error code.
|
||||||
|
func (ec ErrorCode) String() string {
|
||||||
|
return ec.Descriptor().Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message returned the human-readable error message for this error code.
|
||||||
|
func (ec ErrorCode) Message() string {
|
||||||
|
return ec.Descriptor().Message
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText encodes the receiver into UTF-8-encoded text and returns the
|
||||||
|
// result.
|
||||||
|
func (ec ErrorCode) MarshalText() (text []byte, err error) {
|
||||||
|
return []byte(ec.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText decodes the form generated by MarshalText.
|
||||||
|
func (ec *ErrorCode) UnmarshalText(text []byte) error {
|
||||||
|
desc, ok := idToDescriptors[string(text)]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
desc = ErrorCodeUnknown.Descriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
*ec = desc.Code
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessage creates a new Error struct based on the passed-in info and
|
||||||
|
// overrides the Message property.
|
||||||
|
func (ec ErrorCode) WithMessage(message string) Error {
|
||||||
|
return Error{
|
||||||
|
Code: ec,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDetail creates a new Error struct based on the passed-in info and
|
||||||
|
// set the Detail property appropriately
|
||||||
|
func (ec ErrorCode) WithDetail(detail interface{}) Error {
|
||||||
|
return Error{
|
||||||
|
Code: ec,
|
||||||
|
Message: ec.Message(),
|
||||||
|
}.WithDetail(detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithArgs creates a new Error struct and sets the Args slice
|
||||||
|
func (ec ErrorCode) WithArgs(args ...interface{}) Error {
|
||||||
|
return Error{
|
||||||
|
Code: ec,
|
||||||
|
Message: ec.Message(),
|
||||||
|
}.WithArgs(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error provides a wrapper around ErrorCode with extra Details provided.
|
||||||
|
type Error struct {
|
||||||
|
Code ErrorCode `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Detail interface{} `json:"detail,omitempty"`
|
||||||
|
|
||||||
|
// TODO(duglin): See if we need an "args" property so we can do the
|
||||||
|
// variable substitution right before showing the message to the user
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = Error{}
|
||||||
|
|
||||||
|
// ErrorCode returns the ID/Value of this Error
|
||||||
|
func (e Error) ErrorCode() ErrorCode {
|
||||||
|
return e.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns a human readable representation of the error.
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return fmt.Sprintf("%s: %s", e.Code.Error(), e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithDetail will return a new Error, based on the current one, but with
|
||||||
|
// some Detail info added
|
||||||
|
func (e Error) WithDetail(detail interface{}) Error {
|
||||||
|
return Error{
|
||||||
|
Code: e.Code,
|
||||||
|
Message: e.Message,
|
||||||
|
Detail: detail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithArgs uses the passed-in list of interface{} as the substitution
|
||||||
|
// variables in the Error's Message string, but returns a new Error
|
||||||
|
func (e Error) WithArgs(args ...interface{}) Error {
|
||||||
|
return Error{
|
||||||
|
Code: e.Code,
|
||||||
|
Message: fmt.Sprintf(e.Code.Message(), args...),
|
||||||
|
Detail: e.Detail,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorDescriptor provides relevant information about a given error code.
|
||||||
|
type ErrorDescriptor struct {
|
||||||
|
// Code is the error code that this descriptor describes.
|
||||||
|
Code ErrorCode
|
||||||
|
|
||||||
|
// Value provides a unique, string key, often captilized with
|
||||||
|
// underscores, to identify the error code. This value is used as the
|
||||||
|
// keyed value when serializing api errors.
|
||||||
|
Value string
|
||||||
|
|
||||||
|
// Message is a short, human readable decription of the error condition
|
||||||
|
// included in API responses.
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// Description provides a complete account of the errors purpose, suitable
|
||||||
|
// for use in documentation.
|
||||||
|
Description string
|
||||||
|
|
||||||
|
// HTTPStatusCode provides the http status code that is associated with
|
||||||
|
// this error condition.
|
||||||
|
HTTPStatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseErrorCode returns the value by the string error code.
|
||||||
|
// `ErrorCodeUnknown` will be returned if the error is not known.
|
||||||
|
func ParseErrorCode(value string) ErrorCode {
|
||||||
|
ed, ok := idToDescriptors[value]
|
||||||
|
if ok {
|
||||||
|
return ed.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorCodeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors provides the envelope for multiple errors and a few sugar methods
|
||||||
|
// for use within the application.
|
||||||
|
type Errors []error
|
||||||
|
|
||||||
|
var _ error = Errors{}
|
||||||
|
|
||||||
|
func (errs Errors) Error() string {
|
||||||
|
switch len(errs) {
|
||||||
|
case 0:
|
||||||
|
return "<nil>"
|
||||||
|
case 1:
|
||||||
|
return errs[0].Error()
|
||||||
|
default:
|
||||||
|
msg := "errors:\n"
|
||||||
|
for _, err := range errs {
|
||||||
|
msg += err.Error() + "\n"
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the current number of errors.
|
||||||
|
func (errs Errors) Len() int {
|
||||||
|
return len(errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON converts slice of error, ErrorCode or Error into a
|
||||||
|
// slice of Error - then serializes
|
||||||
|
func (errs Errors) MarshalJSON() ([]byte, error) {
|
||||||
|
var tmpErrs struct {
|
||||||
|
Errors []Error `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, daErr := range errs {
|
||||||
|
var err Error
|
||||||
|
|
||||||
|
switch daErr.(type) {
|
||||||
|
case ErrorCode:
|
||||||
|
err = daErr.(ErrorCode).WithDetail(nil)
|
||||||
|
case Error:
|
||||||
|
err = daErr.(Error)
|
||||||
|
default:
|
||||||
|
err = ErrorCodeUnknown.WithDetail(daErr)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the Error struct was setup and they forgot to set the
|
||||||
|
// Message field (meaning its "") then grab it from the ErrCode
|
||||||
|
msg := err.Message
|
||||||
|
if msg == "" {
|
||||||
|
msg = err.Code.Message()
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpErrs.Errors = append(tmpErrs.Errors, Error{
|
||||||
|
Code: err.Code,
|
||||||
|
Message: msg,
|
||||||
|
Detail: err.Detail,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(tmpErrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON deserializes []Error and then converts it into slice of
|
||||||
|
// Error or ErrorCode
|
||||||
|
func (errs *Errors) UnmarshalJSON(data []byte) error {
|
||||||
|
var tmpErrs struct {
|
||||||
|
Errors []Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(data, &tmpErrs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var newErrs Errors
|
||||||
|
for _, daErr := range tmpErrs.Errors {
|
||||||
|
// If Message is empty or exactly matches the Code's message string
|
||||||
|
// then just use the Code, no need for a full Error struct
|
||||||
|
if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
|
||||||
|
// Error's w/o details get converted to ErrorCode
|
||||||
|
newErrs = append(newErrs, daErr.Code)
|
||||||
|
} else {
|
||||||
|
// Error's w/ details are untouched
|
||||||
|
newErrs = append(newErrs, Error{
|
||||||
|
Code: daErr.Code,
|
||||||
|
Message: daErr.Message,
|
||||||
|
Detail: daErr.Detail,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*errs = newErrs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package errcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err
|
||||||
|
// and sets the content-type header to 'application/json'. It will handle
|
||||||
|
// ErrorCoder and Errors, and if necessary will create an envelope.
|
||||||
|
func ServeJSON(w http.ResponseWriter, err error) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
var sc int
|
||||||
|
|
||||||
|
switch errs := err.(type) {
|
||||||
|
case Errors:
|
||||||
|
if len(errs) < 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err, ok := errs[0].(ErrorCoder); ok {
|
||||||
|
sc = err.ErrorCode().Descriptor().HTTPStatusCode
|
||||||
|
}
|
||||||
|
case ErrorCoder:
|
||||||
|
sc = errs.ErrorCode().Descriptor().HTTPStatusCode
|
||||||
|
err = Errors{err} // create an envelope.
|
||||||
|
default:
|
||||||
|
// We just have an unhandled error type, so just place in an envelope
|
||||||
|
// and move along.
|
||||||
|
err = Errors{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc == 0 {
|
||||||
|
sc = http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(sc)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(err)
|
||||||
|
}
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
package errcode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{}
|
||||||
|
idToDescriptors = map[string]ErrorDescriptor{}
|
||||||
|
groupToDescriptors = map[string][]ErrorDescriptor{}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrorCodeUnknown is a generic error that can be used as a last
|
||||||
|
// resort if there is no situation-specific error message that can be used
|
||||||
|
ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "UNKNOWN",
|
||||||
|
Message: "unknown error",
|
||||||
|
Description: `Generic error returned when the error does not have an
|
||||||
|
API classification.`,
|
||||||
|
HTTPStatusCode: http.StatusInternalServerError,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ErrorCodeUnsupported is returned when an operation is not supported.
|
||||||
|
ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "UNSUPPORTED",
|
||||||
|
Message: "The operation is unsupported.",
|
||||||
|
Description: `The operation was unsupported due to a missing
|
||||||
|
implementation or invalid set of parameters.`,
|
||||||
|
HTTPStatusCode: http.StatusMethodNotAllowed,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ErrorCodeUnauthorized is returned if a request requires
|
||||||
|
// authentication.
|
||||||
|
ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "UNAUTHORIZED",
|
||||||
|
Message: "authentication required",
|
||||||
|
Description: `The access controller was unable to authenticate
|
||||||
|
the client. Often this will be accompanied by a
|
||||||
|
Www-Authenticate HTTP response header indicating how to
|
||||||
|
authenticate.`,
|
||||||
|
HTTPStatusCode: http.StatusUnauthorized,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ErrorCodeDenied is returned if a client does not have sufficient
|
||||||
|
// permission to perform an action.
|
||||||
|
ErrorCodeDenied = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "DENIED",
|
||||||
|
Message: "requested access to the resource is denied",
|
||||||
|
Description: `The access controller denied access for the
|
||||||
|
operation on a resource.`,
|
||||||
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ErrorCodeUnavailable provides a common error to report unavailability
|
||||||
|
// of a service or endpoint.
|
||||||
|
ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "UNAVAILABLE",
|
||||||
|
Message: "service unavailable",
|
||||||
|
Description: "Returned when a service is not available",
|
||||||
|
HTTPStatusCode: http.StatusServiceUnavailable,
|
||||||
|
})
|
||||||
|
|
||||||
|
// ErrorCodeTooManyRequests is returned if a client attempts too many
|
||||||
|
// times to contact a service endpoint.
|
||||||
|
ErrorCodeTooManyRequests = Register("errcode", ErrorDescriptor{
|
||||||
|
Value: "TOOMANYREQUESTS",
|
||||||
|
Message: "too many requests",
|
||||||
|
Description: `Returned when a client attempts to contact a
|
||||||
|
service too many times`,
|
||||||
|
HTTPStatusCode: http.StatusTooManyRequests,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
var nextCode = 1000
|
||||||
|
var registerLock sync.Mutex
|
||||||
|
|
||||||
|
// Register will make the passed-in error known to the environment and
|
||||||
|
// return a new ErrorCode
|
||||||
|
func Register(group string, descriptor ErrorDescriptor) ErrorCode {
|
||||||
|
registerLock.Lock()
|
||||||
|
defer registerLock.Unlock()
|
||||||
|
|
||||||
|
descriptor.Code = ErrorCode(nextCode)
|
||||||
|
|
||||||
|
if _, ok := idToDescriptors[descriptor.Value]; ok {
|
||||||
|
panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value))
|
||||||
|
}
|
||||||
|
if _, ok := errorCodeToDescriptors[descriptor.Code]; ok {
|
||||||
|
panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code))
|
||||||
|
}
|
||||||
|
|
||||||
|
groupToDescriptors[group] = append(groupToDescriptors[group], descriptor)
|
||||||
|
errorCodeToDescriptors[descriptor.Code] = descriptor
|
||||||
|
idToDescriptors[descriptor.Value] = descriptor
|
||||||
|
|
||||||
|
nextCode++
|
||||||
|
return descriptor.Code
|
||||||
|
}
|
||||||
|
|
||||||
|
type byValue []ErrorDescriptor
|
||||||
|
|
||||||
|
func (a byValue) Len() int { return len(a) }
|
||||||
|
func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||||
|
|
||||||
|
// GetGroupNames returns the list of Error group names that are registered
|
||||||
|
func GetGroupNames() []string {
|
||||||
|
keys := []string{}
|
||||||
|
|
||||||
|
for k := range groupToDescriptors {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorCodeGroup returns the named group of error descriptors
|
||||||
|
func GetErrorCodeGroup(name string) []ErrorDescriptor {
|
||||||
|
desc := groupToDescriptors[name]
|
||||||
|
sort.Sort(byValue(desc))
|
||||||
|
return desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are
|
||||||
|
// registered, irrespective of what group they're in
|
||||||
|
func GetErrorAllDescriptors() []ErrorDescriptor {
|
||||||
|
result := []ErrorDescriptor{}
|
||||||
|
|
||||||
|
for _, group := range GetGroupNames() {
|
||||||
|
result = append(result, GetErrorCodeGroup(group)...)
|
||||||
|
}
|
||||||
|
sort.Sort(byValue(result))
|
||||||
|
return result
|
||||||
|
}
|
||||||
+108
-12
File diff suppressed because it is too large.
Load diff
Loaded 50 of 462 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user