From 573c4bc20af0d24cf3cfedd2856e0f85df660156 Mon Sep 17 00:00:00 2001 From: Christophe Fergeau Date: Mon, 8 Apr 2024 15:20:50 +0200 Subject: [PATCH 1/6] daemon: Use net.JoinHostPort --- cmd/crc/cmd/daemon.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/crc/cmd/daemon.go b/cmd/crc/cmd/daemon.go index b3f6f94e71..ed83e08a1f 100644 --- a/cmd/crc/cmd/daemon.go +++ b/cmd/crc/cmd/daemon.go @@ -177,7 +177,7 @@ func run(configuration *types.Configuration) error { } }() - ln, err := vn.Listen("tcp", fmt.Sprintf("%s:80", configuration.GatewayIP)) + ln, err := vn.Listen("tcp", net.JoinHostPort(configuration.GatewayIP, "80")) if err != nil { return err } @@ -193,7 +193,7 @@ func run(configuration *types.Configuration) error { } }() - networkListener, err := vn.Listen("tcp", fmt.Sprintf("%s:80", hostVirtualIP)) + networkListener, err := vn.Listen("tcp", net.JoinHostPort(hostVirtualIP, "80")) if err != nil { return err } From 4d0431db76eb2aed92ca148ff96718be6dcb18fc Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Sat, 19 Apr 2025 16:56:30 +0200 Subject: [PATCH 2/6] 9p: added libraries and dependencies --- go.mod | 3 +- go.sum | 12 + vendor/github.com/DeedleFake/p9/LICENSE | 21 + vendor/github.com/DeedleFake/p9/README.md | 51 ++ vendor/github.com/DeedleFake/p9/addr.go | 78 ++ vendor/github.com/DeedleFake/p9/addr_other.go | 29 + vendor/github.com/DeedleFake/p9/addr_unix.go | 29 + vendor/github.com/DeedleFake/p9/client.go | 120 +++ vendor/github.com/DeedleFake/p9/dir.go | 260 +++++++ vendor/github.com/DeedleFake/p9/dir_darwin.go | 62 ++ vendor/github.com/DeedleFake/p9/dir_linux.go | 62 ++ vendor/github.com/DeedleFake/p9/dir_other.go | 14 + vendor/github.com/DeedleFake/p9/dir_plan9.go | 49 ++ .../github.com/DeedleFake/p9/dir_windows.go | 27 + vendor/github.com/DeedleFake/p9/doc.go | 63 ++ vendor/github.com/DeedleFake/p9/encoding.go | 42 + vendor/github.com/DeedleFake/p9/fs.go | 729 ++++++++++++++++++ .../DeedleFake/p9/internal/debug/debug.go | 12 + .../DeedleFake/p9/internal/debug/nodebug.go | 5 + .../DeedleFake/p9/internal/util/util.go | 52 ++ vendor/github.com/DeedleFake/p9/msg.go | 279 +++++++ vendor/github.com/DeedleFake/p9/p9.go | 71 ++ .../github.com/DeedleFake/p9/proto/client.go | 223 ++++++ .../DeedleFake/p9/proto/encoding.go | 193 +++++ .../github.com/DeedleFake/p9/proto/proto.go | 171 ++++ .../github.com/DeedleFake/p9/proto/server.go | 157 ++++ vendor/github.com/DeedleFake/p9/remote.go | 353 +++++++++ vendor/github.com/DeedleFake/p9/stat.go | 350 +++++++++ vendor/modules.txt | 6 + 29 files changed, 3522 insertions(+), 1 deletion(-) create mode 100644 vendor/github.com/DeedleFake/p9/LICENSE create mode 100644 vendor/github.com/DeedleFake/p9/README.md create mode 100644 vendor/github.com/DeedleFake/p9/addr.go create mode 100644 vendor/github.com/DeedleFake/p9/addr_other.go create mode 100644 vendor/github.com/DeedleFake/p9/addr_unix.go create mode 100644 vendor/github.com/DeedleFake/p9/client.go create mode 100644 vendor/github.com/DeedleFake/p9/dir.go create mode 100644 vendor/github.com/DeedleFake/p9/dir_darwin.go create mode 100644 vendor/github.com/DeedleFake/p9/dir_linux.go create mode 100644 vendor/github.com/DeedleFake/p9/dir_other.go create mode 100644 vendor/github.com/DeedleFake/p9/dir_plan9.go create mode 100644 vendor/github.com/DeedleFake/p9/dir_windows.go create mode 100644 vendor/github.com/DeedleFake/p9/doc.go create mode 100644 vendor/github.com/DeedleFake/p9/encoding.go create mode 100644 vendor/github.com/DeedleFake/p9/fs.go create mode 100644 vendor/github.com/DeedleFake/p9/internal/debug/debug.go create mode 100644 vendor/github.com/DeedleFake/p9/internal/debug/nodebug.go create mode 100644 vendor/github.com/DeedleFake/p9/internal/util/util.go create mode 100644 vendor/github.com/DeedleFake/p9/msg.go create mode 100644 vendor/github.com/DeedleFake/p9/p9.go create mode 100644 vendor/github.com/DeedleFake/p9/proto/client.go create mode 100644 vendor/github.com/DeedleFake/p9/proto/encoding.go create mode 100644 vendor/github.com/DeedleFake/p9/proto/proto.go create mode 100644 vendor/github.com/DeedleFake/p9/proto/server.go create mode 100644 vendor/github.com/DeedleFake/p9/remote.go create mode 100644 vendor/github.com/DeedleFake/p9/stat.go diff --git a/go.mod b/go.mod index 470d38d2ec..949aa0e09b 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.0 require ( github.com/AlecAivazis/survey/v2 v2.3.7 + github.com/DeedleFake/p9 v0.6.12 github.com/Masterminds/semver/v3 v3.4.0 github.com/Microsoft/go-winio v0.6.2 github.com/ProtonMail/go-crypto v1.3.0 @@ -30,6 +31,7 @@ require ( github.com/klauspost/compress v1.18.0 github.com/klauspost/cpuid/v2 v2.3.0 github.com/kofalt/go-memoize v0.0.0-20220914132407-0b5d6a304579 + github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 github.com/mattn/go-colorable v0.1.14 github.com/mdlayher/vsock v1.2.1 github.com/onsi/ginkgo/v2 v2.25.3 @@ -130,7 +132,6 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect - github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 // indirect github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index f00a7bb853..8c733a7fc5 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,15 @@ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +bazil.org/fuse v0.0.0-20200524192727-fb710f7dfd05/go.mod h1:h0h5FBYpXThbvSfTqthw+0I4nmHnhTHkO5BoOHsBWqg= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DeedleFake/p9 v0.6.12 h1:U5Qe5t2T3LKeHkDT6gDJO8pA2Gi55dXiQlrw298SzQg= +github.com/DeedleFake/p9 v0.6.12/go.mod h1:LcYdvTijmdXNKjxjAY1mwRaAZSLI8EiYtYCS8iLZnNc= +github.com/Julusian/godocdown v0.0.0-20170816220326-6d19f8ff2df8/go.mod h1:INZr5t32rG59/5xeltqoCJoNY7e5x/3xoY9WSWVWg74= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -106,8 +110,10 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= @@ -333,6 +339,7 @@ github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktE github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= @@ -381,6 +388,7 @@ github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw= github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= +github.com/stephens2424/writerset v1.0.2/go.mod h1:aS2JhsMn6eA7e82oNmW4rfsgAOp9COBTTl8mzkwADnc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -404,6 +412,7 @@ github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8O github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= +github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= @@ -496,9 +505,11 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -542,6 +553,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200423201157-2723c5de0d66/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/vendor/github.com/DeedleFake/p9/LICENSE b/vendor/github.com/DeedleFake/p9/LICENSE new file mode 100644 index 0000000000..4b8dbc8160 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 DeedleFake + +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. diff --git a/vendor/github.com/DeedleFake/p9/README.md b/vendor/github.com/DeedleFake/p9/README.md new file mode 100644 index 0000000000..4f392189c6 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/README.md @@ -0,0 +1,51 @@ +p9 +== + +[![Go Report Card](https://www.goreportcard.com/badge/github.com/DeedleFake/p9)](https://www.goreportcard.com/report/github.com/DeedleFake/p9) +[![GoDoc](http://www.godoc.org/github.com/DeedleFake/p9?status.svg)](http://www.godoc.org/github.com/DeedleFake/p9) + +An experimental Go package for dealing with 9P, the Plan 9 file protocol. The primary idea behind this package is to make building 9P servers and clients as simple as building HTTP servers and clients is in Go. Due to the complexity of the protocol compared to HTTP, this package is unlikely to reach that level of simplicity, but it's certainly simpler than many other existing packages. + +Example +------- + +### Server + +```go +err := p9.ListenAndServe("tcp", "localhost:5640", p9.FSConnHandler(fsImplementation)) +if err != nil { + log.Fatalf("Failed to start server: %v", err) +} +``` + +### Client + +```go +c, err := p9.Dial("tcp", "localhost:5640") +if err != nil { + log.Fatalf("Failed to dial address: %v", err) +} +defer c.Close() + +_, err := c.Handshake(2048) +if err != nil { + log.Fatalf("Failed to perform handshake: %v", err) +} + +root, err := c.Attach(nil, "anyone", "/") +if err != nil { + log.Fatalf("Failed to attach: %v", err) +} +defer root.Close() + +file, err := root.Open("path/to/a/file.txt", p9.OREAD) +if err != nil { + log.Fatalf("Failed to open file: %v", err) +} +defer file.Close() + +_, err = io.Copy(os.Stdout, file) +if err != nil { + log.Fatalf("Failed to read file: %v", err) +} +``` diff --git a/vendor/github.com/DeedleFake/p9/addr.go b/vendor/github.com/DeedleFake/p9/addr.go new file mode 100644 index 0000000000..c0b9eea7a0 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/addr.go @@ -0,0 +1,78 @@ +package p9 + +import ( + "path/filepath" + "strings" +) + +const standardPort = "564" + +// ParseAddr returns a network and address pair for a basic address +// string. The parsing is done like this: +// +// If the address starts with "$", it is assumed to be a namespace and +// is located using GetNamespace. +// +// If the address starts with "./" or "/", it is assumed to be a path +// to a Unix socket. +// +// If the address contains a ":", it is a assumed to be a TCP address +// and port combo. As a special case, the pseudo-ports 9p and 9fs map +// to the standard 9P port number. +// +// If the address contains a single "!", it is assumed to be a network +// and address combo. If the network type is TCP, the standard 9P port +// is assumed. +// +// If the address contains two "!", it is assumed to be a network, +// address, and port, in that order. +// +// In all other cases, it is assumed to be only the address +// specificiation of a TCP address and the standard port is assumed. +func ParseAddr(addr string) (network, address string) { + switch { + case IsNamespaceAddr(addr): + return GetNamespace(addr[1:]) + + case strings.HasPrefix(addr, "./"), strings.HasPrefix(addr, "/"): + return "unix", addr + } + + parts := strings.SplitN(addr, ":", 2) + if len(parts) == 2 { + if (parts[1] == "9p") || (parts[1] == "9fs") { + parts[1] = standardPort + } + + return "tcp", strings.Join(parts, ":") + } + + parts = strings.SplitN(addr, "!", 3) + switch len(parts) { + case 2: + if parts[0] == "tcp" { + parts[1] += ":" + standardPort + } + return parts[0], parts[1] + + case 3: + if (parts[2] == "9p") || (parts[2] == "9fs") { + parts[2] = standardPort + } + return parts[0], strings.Join(parts[1:], ":") + } + + return "tcp", addr + ":" + standardPort +} + +// GetNamespace returns the network and address that should be used to +// connect to the named program in the current namespace. +func GetNamespace(name string) (network, addr string) { + return "unix", filepath.Join(NamespaceDir(), name) +} + +// IsNamespaceAddr returns true if the given address would result in a +// namespace network/address pair if passed to ParseAddr. +func IsNamespaceAddr(addr string) bool { + return strings.HasPrefix(addr, "$") +} diff --git a/vendor/github.com/DeedleFake/p9/addr_other.go b/vendor/github.com/DeedleFake/p9/addr_other.go new file mode 100644 index 0000000000..afdcac24fe --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/addr_other.go @@ -0,0 +1,29 @@ +// +build !linux,!darwin + +package p9 + +import ( + "os" + "os/user" + "path/filepath" +) + +// NamespaceDir returns the path of the directory that is used for the +// current namespace. On Unix-like systems, this is +// /tmp/ns.$USER.$DISPLAY. +// +// If looking up the current user's name fails, this function will +// panic. +func NamespaceDir() string { + u, err := user.Current() + if err != nil { + panic(err) + } + + display, ok := os.LookupEnv("DISPLAY") + if !ok { + display = ":0" + } + + return filepath.Join(os.TempDir(), "ns."+u.Username+"."+display) +} diff --git a/vendor/github.com/DeedleFake/p9/addr_unix.go b/vendor/github.com/DeedleFake/p9/addr_unix.go new file mode 100644 index 0000000000..4123113194 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/addr_unix.go @@ -0,0 +1,29 @@ +// +build linux darwin + +package p9 + +import ( + "os" + "os/user" + "path/filepath" +) + +// NamespaceDir returns the path of the directory that is used for the +// current namespace. On Unix-like systems, this is +// /tmp/ns.$USER.$DISPLAY. +// +// If looking up the current user's name fails, this function will +// panic. +func NamespaceDir() string { + u, err := user.Current() + if err != nil { + panic(err) + } + + display, ok := os.LookupEnv("DISPLAY") + if !ok { + display = ":0" + } + + return filepath.Join("/", "tmp", "ns."+u.Username+"."+display) +} diff --git a/vendor/github.com/DeedleFake/p9/client.go b/vendor/github.com/DeedleFake/p9/client.go new file mode 100644 index 0000000000..7da4c37a15 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/client.go @@ -0,0 +1,120 @@ +package p9 + +import ( + "errors" + "net" + "sync/atomic" + + "github.com/DeedleFake/p9/proto" +) + +var ( + // ErrUnsupportedVersion is returned from a handshake attempt that + // fails due to a version mismatch. + ErrUnsupportedVersion = errors.New("unsupported version") +) + +// Client provides functionality for sending requests to and receiving +// responses from a 9P server. +// +// A Client must be closed when it is no longer going to be used in +// order to free up the related resources. +type Client struct { + *proto.Client + + fid uint32 +} + +// NewClient returns a client that communicates using c. The Client +// will close c when the Client is closed. +func NewClient(c net.Conn) *Client { + return &Client{Client: proto.NewClient(Proto(), c)} +} + +// Dial is a convenience function that dials and creates a client in +// the same step. +func Dial(network, addr string) (*Client, error) { + pc, err := proto.Dial(Proto(), network, addr) + if err != nil { + return nil, err + } + + return &Client{Client: pc}, nil +} + +func (c *Client) nextFID() uint32 { + return atomic.AddUint32(&c.fid, 1) - 1 +} + +// Handshake performs an initial handshake to establish the maximum +// allowed message size. A handshake must be performed before any +// other request types may be sent. +func (c *Client) Handshake(msize uint32) (uint32, error) { + rsp, err := c.Send(&Tversion{ + Msize: msize, + Version: Version, + }) + if err != nil { + return 0, err + } + + version := rsp.(*Rversion) + if version.Version != Version { + return 0, ErrUnsupportedVersion + } + + c.SetMsize(version.Msize) + + return version.Msize, nil +} + +// Auth requests an auth file from the server, returning a Remote +// representing it or an error if one occurred. +func (c *Client) Auth(user, aname string) (*Remote, error) { + fid := c.nextFID() + + rsp, err := c.Send(&Tauth{ + AFID: fid, + Uname: user, + Aname: aname, + }) + if err != nil { + return nil, err + } + rauth := rsp.(*Rauth) + + return &Remote{ + client: c, + fid: fid, + qid: rauth.AQID, + }, nil +} + +// Attach attaches to a filesystem provided by the connected server +// with the given attributes. If no authentication has been done, +// afile may be nil. +func (c *Client) Attach(afile *Remote, user, aname string) (*Remote, error) { + fid := c.nextFID() + + afid := NoFID + if afile != nil { + afid = afile.fid + } + + rsp, err := c.Send(&Tattach{ + FID: fid, + AFID: afid, + Uname: user, + Aname: aname, + }) + if err != nil { + return nil, err + } + attach := rsp.(*Rattach) + + return &Remote{ + client: c, + fid: fid, + qid: attach.QID, + }, nil +} diff --git a/vendor/github.com/DeedleFake/p9/dir.go b/vendor/github.com/DeedleFake/p9/dir.go new file mode 100644 index 0000000000..fca0101ae3 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/dir.go @@ -0,0 +1,260 @@ +package p9 + +import ( + "errors" + "os" + "path/filepath" +) + +// Dir is an implementation of FileSystem that serves from the local +// filesystem. It accepts attachments of either "" or "/", but rejects +// all others. +// +// Note that Dir does not support authentication, simply returning an +// error for any attempt to do so. If authentication is necessary, +// wrap a Dir in an AuthFS instance. +type Dir string + +func (d Dir) path(p string) string { + return filepath.Join(string(d), filepath.FromSlash(p)) +} + +// Stat implements Attachment.Stat. +func (d Dir) Stat(p string) (DirEntry, error) { + fi, err := os.Stat(d.path(p)) + if err != nil { + return DirEntry{}, err + } + + e := infoToEntry(fi) + if e.EntryName == "." { + e.EntryName = "" + } + + return e, nil +} + +// WriteStat implements Attachment.WriteStat. +func (d Dir) WriteStat(p string, changes StatChanges) error { + // TODO: Add support for other values. + + p = d.path(p) + base := filepath.Dir(p) + + mode, ok := changes.Mode() + if ok { + err := os.Chmod(p, mode.OS()) + if err != nil { + return err + } + } + + atime, ok1 := changes.ATime() + mtime, ok2 := changes.MTime() + if ok1 || ok2 { + err := os.Chtimes(p, atime, mtime) + if err != nil { + return err + } + } + + length, ok := changes.Length() + if ok { + err := os.Truncate(p, int64(length)) + if err != nil { + return err + } + } + + name, ok := changes.Name() + if ok { + err := os.Rename(p, filepath.Join(base, filepath.FromSlash(name))) + if err != nil { + return err + } + } + + return nil +} + +// Auth implements FileSystem.Auth. +func (d Dir) Auth(user, aname string) (File, error) { + return nil, errors.New("auth not supported") +} + +// Attach implements FileSystem.Attach. +func (d Dir) Attach(afile File, user, aname string) (Attachment, error) { + switch aname { + case "", "/": + return d, nil + } + + return nil, errors.New("unknown attachment") +} + +// Open implements Attachment.Open. +func (d Dir) Open(p string, mode uint8) (File, error) { + flag := toOSFlags(mode) + + file, err := os.OpenFile(d.path(p), flag, 0644) + return &dirFile{ + File: file, + }, err +} + +// Create implements Attachment.Create. +func (d Dir) Create(p string, perm FileMode, mode uint8) (File, error) { + p = d.path(p) + + if perm&ModeDir != 0 { + err := os.Mkdir(p, os.FileMode(perm.Perm())) + if err != nil { + return nil, err + } + } + + flag := toOSFlags(mode) + + file, err := os.OpenFile(p, flag|os.O_CREATE, os.FileMode(perm.Perm())) + return &dirFile{ + File: file, + }, err +} + +// Remove implements Attachment.Remove. +func (d Dir) Remove(p string) error { + return os.Remove(d.path(p)) +} + +type dirFile struct { + *os.File +} + +func (f *dirFile) Readdir() ([]DirEntry, error) { + fi, err := f.File.Readdir(-1) + if err != nil { + return nil, err + } + + entries := make([]DirEntry, 0, len(fi)) + for _, info := range fi { + entries = append(entries, infoToEntry(info)) + } + return entries, nil +} + +// ReadOnlyFS wraps a filesystem implementation with an implementation +// that rejects any attempts to cause changes to the filesystem with +// the exception of writing to an authfile. +func ReadOnlyFS(fs FileSystem) FileSystem { + return &readOnlyFS{fs} +} + +type readOnlyFS struct { + FileSystem +} + +func (ro readOnlyFS) Attach(afile File, user, aname string) (Attachment, error) { + a, err := ro.FileSystem.Attach(afile, user, aname) + if err != nil { + return nil, err + } + + return passQIDFS(&readOnlyAttachment{a}, a), nil +} + +type qidfsPasser struct { + Attachment + QIDFS +} + +func passQIDFS(w Attachment, u Attachment) Attachment { + if q, ok := u.(QIDFS); ok { + return &qidfsPasser{ + Attachment: w, + QIDFS: q, + } + } + + return w +} + +type readOnlyAttachment struct { + Attachment +} + +func (ro readOnlyAttachment) WriteStat(path string, changes StatChanges) error { + return errors.New("read-only filesystem") +} + +func (ro readOnlyAttachment) Open(path string, mode uint8) (File, error) { + if mode&(OWRITE|ORDWR|OEXEC|OTRUNC|OCEXEC|ORCLOSE) != 0 { + return nil, errors.New("read-only filesystem") + } + + return ro.Attachment.Open(path, mode) +} + +func (ro readOnlyAttachment) Create(path string, perm FileMode, mode uint8) (File, error) { + return nil, errors.New("read-only filesystem") +} + +func (ro readOnlyAttachment) Remove(path string) error { + return errors.New("read-only filesystem") +} + +// AuthFS allows simple wrapping and overwriting of the Auth and +// Attach methods of an existing FileSystem implementation, allowing +// the user to add authentication support to a FileSystem that does +// not have it, or to change the implementation of that support for +// FileSystems that do. +type AuthFS struct { + FileSystem + + // AuthFunc is the function called when the Auth() method is called. + AuthFunc func(user, aname string) (File, error) + + // AttachFunc is the function called when the Attach() method is + // called. Note that this function, unlike the method, does not + // return an Attachment. Instead, if this function returns a nil + // error, the underlying implementation's Attach() method is called + // with the returned file as its afile argument. + AttachFunc func(afile File, user, aname string) (File, error) +} + +// Auth implements FileSystem.Auth. +func (a AuthFS) Auth(user, aname string) (File, error) { + return a.AuthFunc(user, aname) +} + +// Attach implements FileSystem.Attach. +func (a AuthFS) Attach(afile File, user, aname string) (Attachment, error) { + file, err := a.AttachFunc(afile, user, aname) + if err != nil { + return nil, err + } + return a.FileSystem.Attach(file, user, aname) +} + +func toOSFlags(mode uint8) (flag int) { + if mode&OREAD != 0 { + flag |= os.O_RDONLY + } + if mode&OWRITE != 0 { + flag |= os.O_WRONLY + } + if mode&ORDWR != 0 { + flag |= os.O_RDWR + } + if mode&OTRUNC != 0 { + flag |= os.O_TRUNC + } + //if mode&OEXCL != 0 { + // flag |= os.O_EXCL + //} + //if mode&OAPPEND != 0 { + // flag |= os.O_APPEND + //} + + return flag +} diff --git a/vendor/github.com/DeedleFake/p9/dir_darwin.go b/vendor/github.com/DeedleFake/p9/dir_darwin.go new file mode 100644 index 0000000000..431d4e08ce --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/dir_darwin.go @@ -0,0 +1,62 @@ +package p9 + +import ( + "errors" + "os" + "os/user" + "strconv" + "syscall" + "time" +) + +func infoToEntry(fi os.FileInfo) DirEntry { + sys, _ := fi.Sys().(*syscall.Stat_t) + if sys == nil { + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + } + } + + var uname string + uid, err := user.LookupId(strconv.FormatUint(uint64(sys.Uid), 10)) + if err == nil { + uname = uid.Username + } + + var gname string + gid, err := user.LookupGroupId(strconv.FormatUint(uint64(sys.Gid), 10)) + if err == nil { + gname = gid.Name + } + + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + ATime: time.Unix(sys.Atimespec.Unix()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + UID: uname, + GID: gname, + } +} + +func (d Dir) GetQID(p string) (QID, error) { + fi, err := os.Stat(d.path(p)) + if err != nil { + return QID{}, err + } + + sys, _ := fi.Sys().(*syscall.Stat_t) + if sys == nil { + return QID{}, errors.New("failed to get QID: FileInfo was not Stat_t") + } + + return QID{ + Type: ModeFromOS(fi.Mode()).QIDType(), + Version: 0, + Path: sys.Ino, + }, nil +} diff --git a/vendor/github.com/DeedleFake/p9/dir_linux.go b/vendor/github.com/DeedleFake/p9/dir_linux.go new file mode 100644 index 0000000000..1765f41cf1 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/dir_linux.go @@ -0,0 +1,62 @@ +package p9 + +import ( + "errors" + "os" + "os/user" + "strconv" + "syscall" + "time" +) + +func infoToEntry(fi os.FileInfo) DirEntry { + sys, _ := fi.Sys().(*syscall.Stat_t) + if sys == nil { + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + } + } + + var uname string + uid, err := user.LookupId(strconv.FormatUint(uint64(sys.Uid), 10)) + if err == nil { + uname = uid.Username + } + + var gname string + gid, err := user.LookupGroupId(strconv.FormatUint(uint64(sys.Gid), 10)) + if err == nil { + gname = gid.Name + } + + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + ATime: time.Unix(sys.Atim.Unix()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + UID: uname, + GID: gname, + } +} + +func (d Dir) GetQID(p string) (QID, error) { + fi, err := os.Stat(d.path(p)) + if err != nil { + return QID{}, err + } + + sys, _ := fi.Sys().(*syscall.Stat_t) + if sys == nil { + return QID{}, errors.New("failed to get QID: FileInfo was not Stat_t") + } + + return QID{ + Type: ModeFromOS(fi.Mode()).QIDType(), + Version: 0, + Path: sys.Ino, + }, nil +} diff --git a/vendor/github.com/DeedleFake/p9/dir_other.go b/vendor/github.com/DeedleFake/p9/dir_other.go new file mode 100644 index 0000000000..d0b0201e08 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/dir_other.go @@ -0,0 +1,14 @@ +// +build !linux,!darwin,!plan9,!windows + +package p9 + +import "os" + +func infoToEntry(fi os.FileInfo) DirEntry { + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + } +} diff --git a/vendor/github.com/DeedleFake/p9/dir_plan9.go b/vendor/github.com/DeedleFake/p9/dir_plan9.go new file mode 100644 index 0000000000..89d46c8df3 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/dir_plan9.go @@ -0,0 +1,49 @@ +package p9 + +import ( + "errors" + "os" + "syscall" + "time" +) + +func infoToEntry(fi os.FileInfo) DirEntry { + sys, _ := fi.Sys().(*syscall.Dir) + if sys == nil { + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + } + } + + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + ATime: time.Unix(int64(sys.Atime), 0), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + UID: sys.Uid, + GID: sys.Gid, + MUID: sys.Muid, + } +} + +func (d Dir) GetQID(p string) (QID, error) { + fi, err := os.Stat(d.path(p)) + if err != nil { + return QID{}, err + } + + sys, _ := fi.Sys().(*syscall.Dir) + if sys == nil { + return QID{}, errors.New("failed to get QID: FileInfo was not Dir") + } + + return QID{ + Type: sys.Qid.Type, + Version: sys.Qid.Vers, + Path: sys.Qid.Path, + }, nil +} diff --git a/vendor/github.com/DeedleFake/p9/dir_windows.go b/vendor/github.com/DeedleFake/p9/dir_windows.go new file mode 100644 index 0000000000..e57e60dcdb --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/dir_windows.go @@ -0,0 +1,27 @@ +package p9 + +import ( + "os" + "syscall" + "time" +) + +func infoToEntry(fi os.FileInfo) DirEntry { + sys, _ := fi.Sys().(*syscall.Win32FileAttributeData) + if sys == nil { + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + } + } + + return DirEntry{ + FileMode: ModeFromOS(fi.Mode()), + ATime: time.Unix(0, sys.LastAccessTime.Nanoseconds()), + MTime: fi.ModTime(), + Length: uint64(fi.Size()), + EntryName: fi.Name(), + } +} diff --git a/vendor/github.com/DeedleFake/p9/doc.go b/vendor/github.com/DeedleFake/p9/doc.go new file mode 100644 index 0000000000..aa6e461955 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/doc.go @@ -0,0 +1,63 @@ +// Package p9 contains an implementation of 9P, the Plan 9 from Bell +// Labs file protocol. +// +// The package provides high-level APIs for both creating 9P servers +// and connecting to those servers as a client. Although it abstracts +// away a lot of the complexity of 9P, some familiarity with the +// protocol is advised. Like "net/http", it exposes a fair amount of +// the inner workings of the package so that a user can opt to build +// their own high-level implementation on top of it. +// +// The primary concept that the user should understand is that in 9P, +// everything is referenced relative to previously referenced objects. +// For example, a typical 9P exchange, skipping version negotiation +// and authentication, might look something like this: +// +// attach to filesystem "/" and call it 1 +// navigate to file at "some/path" relative to 1 and call it 2 +// navigate to file at "../woops/other/path" relative to 2 and call it 3 +// open file 3 +// read from file 3 +// +// This package attempts to completely abstract away the navigation +// aspects of 9P, but a lot of things are still relative to others. +// For example, opening a file on the server from the client is done +// by calling the Open method on an already existing file reference +// and passing it a path. +// +// The Client type provides a series of functionality that allows the +// user to connect to 9P servers. Here's an example of its use: +// +// c, _ := p9.Dial("tcp", addr) +// defer c.Close() +// c.Handshake(4096) +// +// root, _ := c.Attach(nil, "anyone", "/") +// defer root.Close() +// +// file, _ := root.Open("path/to/a/file", p9.OREAD) +// defer file.Close() +// buf, _ := ioutil.ReadAll(file) +// +// The client is split into two main types: Client and Remote. Client +// provides the basic functionality for establishing a connection, +// performing authentication, and attaching to file hierarchies. +// Remote provides functionality for opening and creating files, +// getting information about them, and reading from and writing to +// them. They behave similarly to files themselves, implementing many +// of the same interfaces that os.File implements. +// +// The server works similarly to the "net/http" package, but, due to +// the major differences in the protocol being handled, is quite a bit +// more complicated. At the top level, there is a ListenAndServe +// function, much like what is provided by "net/http". For most cases, +// the user can provide a filesystem by implementing the FileSystem +// interface and passing an instance of their implementation to the +// FSConnHandler function to get a handler to pass to ListenAndServe. +// +// If the user only wants to serve local files, the Dir type provides +// a pre-built implementation of FileSystem that does just that. +// Similarly, the AuthFS type allows the user to add the ability to +// authenticate to a FileSystem implementation that otherwise has +// none, such as the aforementioned Dir. +package p9 diff --git a/vendor/github.com/DeedleFake/p9/encoding.go b/vendor/github.com/DeedleFake/p9/encoding.go new file mode 100644 index 0000000000..7a3ddfadd4 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/encoding.go @@ -0,0 +1,42 @@ +package p9 + +import ( + "io" + + "github.com/DeedleFake/p9/proto" +) + +// ReadDir decodes a series of directory entries from a reader. It +// reads until EOF, so it doesn't return io.EOF as a possible error. +// +// It is recommended that the reader passed to ReadDir have some form +// of buffering, as some servers will silently mishandle attempts to +// read pieces of a directory. Wrapping the reader with a bufio.Reader +// is often sufficient. +func ReadDir(r io.Reader) ([]DirEntry, error) { + var entries []DirEntry + for { + var stat Stat + err := proto.Read(r, &stat) + if err != nil { + if err == io.EOF { + err = nil + } + return entries, err + } + + entries = append(entries, stat.DirEntry()) + } +} + +// WriteDir writes a series of directory entries to w. +func WriteDir(w io.Writer, entries []DirEntry) error { + for _, entry := range entries { + err := proto.Write(w, entry.Stat()) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/DeedleFake/p9/fs.go b/vendor/github.com/DeedleFake/p9/fs.go new file mode 100644 index 0000000000..ab9059147c --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/fs.go @@ -0,0 +1,729 @@ +package p9 + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "fmt" + "io" + "path" + "sync" + "time" + "unsafe" + + "github.com/DeedleFake/p9/internal/debug" + "github.com/DeedleFake/p9/proto" +) + +// FileSystem is an interface that allows high-level implementations +// of 9P servers by allowing the implementation to ignore the majority +// of the details of the protocol. +type FileSystem interface { + // Auth returns an authentication file. This file can be used to + // send authentication information back and forth between the server + // and the client. + // + // The file returned from this method must report its own type as + // QTAuth. + Auth(user, aname string) (File, error) + + // Attach attachs to a file hierarchy provided by the filesystem. + // If the client is attempting to authenticate, a File previously + // returned from Auth() will be passed as afile. + Attach(afile File, user, aname string) (Attachment, error) +} + +// Attachment is a file hierarchy provided by an FS. +// +// All paths passed to the methods of this system begin with the aname +// used to attach the attachment, use forward slashes as separators, +// and have been cleaned using path.Clean. For example, if the client +// attaches using the aname "/example" and then tries to open the file +// located at "some/other/example", the Open method will be called +// with the path "/example/some/other/example". +type Attachment interface { + // Stat returns a DirEntry giving info about the file or directory + // at the given path. If an error is returned, the text of the error + // will be transmitted to the client. + Stat(path string) (DirEntry, error) + + // WriteStat applies changes to the metadata of the file at path. + // If a method of the changes argument returns false, it should not + // be changed. + // + // If an error is returned, it is assumed that the entire operation + // failed. In particular, name changes will not be tracked by the + // system if this returns an error. + WriteStat(path string, changes StatChanges) error + + // Open opens the file at path in the given mode. If an error is + // returned, it will be transmitted to the client. + Open(path string, mode uint8) (File, error) + + // Create creates and opens a file at path with the given perms and + // mode. If an error is returned, it will be transmitted to the + // client. + // + // When creating a directory, this method will be called with the + // DMDIR bit set in perm. Even in this situation it should still + // open and return the newly created file. + Create(path string, perm FileMode, mode uint8) (File, error) + + // Remove deletes the file at path, returning any errors encountered. + Remove(path string) error +} + +// IOUnitFS is implemented by Attachments that want to report an +// IOUnit value to clients when open and create requests are made. An +// IOUnit value lets the client know the maximum amount of data during +// reads and writes that is guaranteed to be an atomic operation. +type IOUnitFS interface { + IOUnit() uint32 +} + +// QIDFS is implemented by Attachments that want to deal with QIDs +// manually. A QID represents a unique identifier for a given file. In +// particular, the Path field must be unique for every path, even if +// the file at that path has been deleted and replaced with a +// completely new file. +type QIDFS interface { + GetQID(p string) (QID, error) +} + +// File is the interface implemented by files being dealt with by a +// FileSystem. +// +// Note that although this interface implements io.ReaderAt and +// io.WriterAt, a number of the restrictions placed on those +// interfaces may be ignored. The implementation need only follow +// restrictions placed by the 9P protocol specification. +type File interface { + // Used to handle 9P read requests. + io.ReaderAt + + // Used to handle 9P write requests. + io.WriterAt + + // Used to handle 9P clunk requests. + io.Closer + + // Readdir is called when an attempt is made to read a directory. It + // should return a list of entries in the directory or an error. If + // an error is returned, the error will be transmitted to the + // client. + // + // When a client attempts to read a file, if file reports itself as + // a QTDir, then this method will be used to read it instead of + // ReadAt(). + Readdir() ([]DirEntry, error) +} + +type fsFile struct { + sync.RWMutex + + path string + + a Attachment + + file File + dir bytes.Buffer +} + +type fsHandler struct { + fs FileSystem + msize uint32 + + fids sync.Map // map[uint32]*fsFile +} + +// FSHandler returns a MessageHandler that provides a virtual +// filesystem using the provided FileSystem implementation. msize is +// the maximum size that messages from either the server or the client +// are allowed to be. +// +// The returned MessageHandler implementation will print debug +// messages to stderr if the p9debug build tag is set. +// +// BUG: Tflush requests are not currently handled at all by this +// implementation due to no clear method of stopping a pending call to +// ReadAt or WriteAt. +func FSHandler(fs FileSystem, msize uint32) proto.MessageHandler { + return &fsHandler{ + fs: fs, + msize: msize, + } +} + +// FSConnHandler returns a ConnHandler that calls FSHandler to +// generate MessageHandlers. +// +// The returned ConnHandler implementation will print debug messages +// to stderr if the p9debug build tag is set. +func FSConnHandler(fs FileSystem, msize uint32) proto.ConnHandler { + return proto.ConnHandlerFunc(func() proto.MessageHandler { + debug.Log("Got new connection to FSConnHandler.\n") + return FSHandler(fs, msize) + }) +} + +func (h *fsHandler) getQID(p string, attach Attachment) (QID, error) { + if q, ok := attach.(QIDFS); ok { + return q.GetQID(p) + } + + stat, err := attach.Stat(p) + if err != nil { + return QID{}, err + } + + sum := sha256.Sum256(*(*[]byte)(unsafe.Pointer(&p))) + path := binary.LittleEndian.Uint64(sum[:]) + + return QID{ + Type: stat.FileMode.QIDType(), + Path: path, + }, nil +} + +func (h *fsHandler) getFile(fid uint32, create bool) (*fsFile, bool) { + if create { + f, ok := h.fids.LoadOrStore(fid, new(fsFile)) + return f.(*fsFile), ok + } + + f, ok := h.fids.Load(fid) + if !ok { + return nil, false + } + return f.(*fsFile), true +} + +func (h *fsHandler) largeCount(count uint32) bool { + return IOHeaderSize+count > h.msize +} + +func (h *fsHandler) version(msg *Tversion) interface{} { + if msg.Version != Version { + return &Rerror{ + Ename: ErrUnsupportedVersion.Error(), + } + } + + if h.msize > msg.Msize { + h.msize = msg.Msize + } + + return &Rversion{ + Msize: h.msize, + Version: Version, + } +} + +func (h *fsHandler) auth(msg *Tauth) interface{} { + file, err := h.fs.Auth(msg.Uname, msg.Aname) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + f, _ := h.getFile(msg.AFID, true) + f.Lock() + defer f.Unlock() + + f.file = file + + return &Rauth{ + AQID: QID{ + Type: QTAuth, + Path: uint64(time.Now().UnixNano()), + }, + } +} + +func (h *fsHandler) flush(msg *Tflush) interface{} { + // TODO: Implement this. + return &Rerror{ + Ename: "flush is not supported", + } +} + +func (h *fsHandler) attach(msg *Tattach) interface{} { + var afile File + if msg.AFID != NoFID { + tmp, ok := h.getFile(msg.AFID, false) + if !ok { + return &Rerror{ + Ename: "no such AFID", + } + } + + tmp.RLock() + afile = tmp.file + tmp.RUnlock() + } + + attach, err := h.fs.Attach(afile, msg.Uname, msg.Aname) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + qid, err := h.getQID(msg.Aname, attach) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + file, ok := h.getFile(msg.FID, true) + if ok { + return &Rerror{ + Ename: "FID in use", + } + } + file.Lock() + defer file.Unlock() + + file.path = msg.Aname + file.a = attach + + return &Rattach{ + QID: qid, + } +} + +func (h *fsHandler) walk(msg *Twalk) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + + file.RLock() + base := file.path + a := file.a + file.RUnlock() + + qids := make([]QID, 0, len(msg.Wname)) + for i, name := range msg.Wname { + next := path.Join(base, name) + + qid, err := h.getQID(next, a) + if err != nil { + if i == 0 { + return &Rerror{ + Ename: err.Error(), + } + } + + return &Rwalk{ + WQID: qids, + } + } + + qids = append(qids, qid) + base = next + } + + file, ok = h.getFile(msg.NewFID, true) + if ok { + return &Rerror{ + Ename: "FID in use", + } + } + file.Lock() + defer file.Unlock() + + file.path = base + file.a = a + + return &Rwalk{ + WQID: qids, + } +} + +func (h *fsHandler) open(msg *Topen) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.Lock() + defer file.Unlock() + + if file.file != nil { + return &Rerror{ + Ename: "file already open", + } + } + + f, err := file.a.Open(file.path, msg.Mode) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + file.file = f + + qid, err := h.getQID(file.path, file.a) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + var iounit uint32 + if unit, ok := file.a.(IOUnitFS); ok { + iounit = unit.IOUnit() + } + + return &Ropen{ + QID: qid, + IOUnit: iounit, + } +} + +func (h *fsHandler) create(msg *Tcreate) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.Lock() + defer file.Unlock() + + if file.file != nil { + return &Rerror{ + Ename: "file already open", + } + } + + p := path.Join(file.path, msg.Name) + + f, err := file.a.Create(p, msg.Perm, msg.Mode) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + file.path = p + file.file = f + + qid, err := h.getQID(p, file.a) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + var iounit uint32 + if unit, ok := file.a.(IOUnitFS); ok { + iounit = unit.IOUnit() + } + + return &Rcreate{ + QID: qid, + IOUnit: iounit, + } +} + +func (h *fsHandler) read(msg *Tread) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.Lock() + defer file.Unlock() + + if file.file == nil { + return &Rerror{ + Ename: "file not open", + } + } + + qid, err := h.getQID(file.path, file.a) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + if h.largeCount(msg.Count) { + return &Rerror{ + Ename: "read too large", + } + } + + var n int + buf := make([]byte, msg.Count) + + switch { + case qid.Type&QTDir != 0: + if msg.Offset == 0 { + dir, err := file.file.Readdir() + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + for i := range dir { + qid, err := h.getQID(path.Join(file.path, dir[i].EntryName), file.a) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + dir[i].Version = qid.Version + dir[i].Path = qid.Path + } + + file.dir.Reset() + err = WriteDir(&file.dir, dir) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + } + + // This technically isn't quite accurate to the 9P specification. + // The specification states that all reads of a directory must + // either be at offset 0 or at the previous offset plus the length + // of the previous read. Instead, this implemenation just ignores + // the offset if it's not zero. This is backwards compatible with + // the specification, however, so it's probably not really an + // issue. + tmp, err := file.dir.Read(buf) + if (err != nil) && (err != io.EOF) { + return &Rerror{ + Ename: err.Error(), + } + } + n = tmp + + default: + tmp, err := file.file.ReadAt(buf, int64(msg.Offset)) + if (err != nil) && (err != io.EOF) { + return &Rerror{ + Ename: err.Error(), + } + } + n = tmp + } + + return &Rread{ + Data: buf[:n], + } +} + +func (h *fsHandler) write(msg *Twrite) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.RLock() // Somewhat ironic that this doesn't require a + defer file.RUnlock() // full lock like read() does. + + if file.file == nil { + return &Rerror{ + Ename: "file not open", + } + } + + n, err := file.file.WriteAt(msg.Data, int64(msg.Offset)) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + return &Rwrite{ + Count: uint32(n), + } +} + +func (h *fsHandler) clunk(msg *Tclunk) interface{} { + defer h.fids.Delete(msg.FID) + + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.RLock() + defer file.RUnlock() + + if file.file == nil { + return &Rclunk{} + } + + err := file.file.Close() + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + return &Rclunk{} +} + +func (h *fsHandler) remove(msg *Tremove) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.RLock() + defer file.RUnlock() + + rsp := h.clunk(&Tclunk{ + FID: msg.FID, + }) + if _, ok := rsp.(error); ok { + return rsp + } + + err := file.a.Remove(file.path) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + return &Rremove{} +} + +func (h *fsHandler) stat(msg *Tstat) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.RLock() + defer file.RUnlock() + + stat, err := file.a.Stat(file.path) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + qid, err := h.getQID(file.path, file.a) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + stat.Version = qid.Version + stat.Path = qid.Path + + return &Rstat{ + Stat: stat.Stat(), + } +} + +func (h *fsHandler) wstat(msg *Twstat) interface{} { + file, ok := h.getFile(msg.FID, false) + if !ok { + return &Rerror{ + Ename: "unknown FID", + } + } + file.RLock() + defer file.RUnlock() + + changes := StatChanges{ + DirEntry: msg.Stat.DirEntry(), + } + + err := file.a.WriteStat(file.path, changes) + if err != nil { + return &Rerror{ + Ename: err.Error(), + } + } + + return &Rwstat{} +} + +func (h *fsHandler) HandleMessage(msg interface{}) (r interface{}) { + defer func() { + debug.Log("%#v\n", r) + }() + + debug.Log("%#v\n", msg) + + switch msg := msg.(type) { + case *Tversion: + return h.version(msg) + + case *Tauth: + return h.auth(msg) + + case *Tflush: + return h.flush(msg) + + case *Tattach: + return h.attach(msg) + + case *Twalk: + return h.walk(msg) + + case *Topen: + return h.open(msg) + + case *Tcreate: + return h.create(msg) + + case *Tread: + return h.read(msg) + + case *Twrite: + return h.write(msg) + + case *Tclunk: + return h.clunk(msg) + + case *Tremove: + return h.remove(msg) + + case *Tstat: + return h.stat(msg) + + case *Twstat: + return h.wstat(msg) + + default: + return &Rerror{ + Ename: fmt.Sprintf("unexpected message type: %T", msg), + } + } +} + +func (h *fsHandler) Close() error { + h.fids.Range(func(k, v interface{}) bool { + file := v.(*fsFile) + if file.file != nil { + file.file.Close() + } + return true + }) + + return nil +} diff --git a/vendor/github.com/DeedleFake/p9/internal/debug/debug.go b/vendor/github.com/DeedleFake/p9/internal/debug/debug.go new file mode 100644 index 0000000000..d7e7f40c62 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/internal/debug/debug.go @@ -0,0 +1,12 @@ +// +build p9debug + +package debug + +import ( + "fmt" + "os" +) + +func Log(str string, args ...interface{}) { + fmt.Fprintf(os.Stderr, str, args...) +} diff --git a/vendor/github.com/DeedleFake/p9/internal/debug/nodebug.go b/vendor/github.com/DeedleFake/p9/internal/debug/nodebug.go new file mode 100644 index 0000000000..fc0434fb8a --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/internal/debug/nodebug.go @@ -0,0 +1,5 @@ +// +build !p9debug + +package debug + +func Log(str string, args ...interface{}) {} diff --git a/vendor/github.com/DeedleFake/p9/internal/util/util.go b/vendor/github.com/DeedleFake/p9/internal/util/util.go new file mode 100644 index 0000000000..b5708e7a3a --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/internal/util/util.go @@ -0,0 +1,52 @@ +package util + +import ( + "fmt" + "io" +) + +// LimitedReader is a reimplementation of io.LimitedReader with two +// main differences: +// +// * N is a uint32, allowing for larger sizes on 32-bit systems. +// * A custom error can be returned if N becomes zero. +type LimitedReader struct { + R io.Reader + N uint32 + E error +} + +func (lr LimitedReader) err() error { + if lr.E == nil { + return io.EOF + } + + return lr.E +} + +func (lr *LimitedReader) Read(buf []byte) (int, error) { + if lr.N <= 0 { + return 0, lr.err() + } + + if uint32(len(buf)) > lr.N { + buf = buf[:lr.N] + } + + n, err := lr.R.Read(buf) + lr.N -= uint32(n) + return n, err +} + +// Errorf is a variant of fmt.Errorf that returns an error being +// wrapped directly if it is one of a number of specific values, such +// as nil or io.EOF. +func Errorf(str string, args ...interface{}) error { + for _, arg := range args { + if arg == io.EOF { + return arg.(error) + } + } + + return fmt.Errorf(str, args...) +} diff --git a/vendor/github.com/DeedleFake/p9/msg.go b/vendor/github.com/DeedleFake/p9/msg.go new file mode 100644 index 0000000000..4c1e334853 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/msg.go @@ -0,0 +1,279 @@ +package p9 + +import ( + "bytes" + "io" + "reflect" + + "github.com/DeedleFake/p9/internal/util" + "github.com/DeedleFake/p9/proto" +) + +// Message type identifiers. +const ( + TversionType uint8 = 100 + iota + RversionType + TauthType + RauthType + TattachType + RattachType + _ // Terror isn't used, but the slot is skipped over. + RerrorType + TflushType + RflushType + TwalkType + RwalkType + TopenType + RopenType + TcreateType + RcreateType + TreadType + RreadType + TwriteType + RwriteType + TclunkType + RclunkType + TremoveType + RremoveType + TstatType + RstatType + TwstatType + RwstatType +) + +var protocol = proto.NewProto(map[uint8]reflect.Type{ + TversionType: reflect.TypeOf(Tversion{}), + RversionType: reflect.TypeOf(Rversion{}), + TauthType: reflect.TypeOf(Tauth{}), + RauthType: reflect.TypeOf(Rauth{}), + TattachType: reflect.TypeOf(Tattach{}), + RattachType: reflect.TypeOf(Rattach{}), + RerrorType: reflect.TypeOf(Rerror{}), + TflushType: reflect.TypeOf(Tflush{}), + RflushType: reflect.TypeOf(Rflush{}), + TwalkType: reflect.TypeOf(Twalk{}), + RwalkType: reflect.TypeOf(Rwalk{}), + TopenType: reflect.TypeOf(Topen{}), + RopenType: reflect.TypeOf(Ropen{}), + TcreateType: reflect.TypeOf(Tcreate{}), + RcreateType: reflect.TypeOf(Rcreate{}), + TreadType: reflect.TypeOf(Tread{}), + RreadType: reflect.TypeOf(Rread{}), + TwriteType: reflect.TypeOf(Twrite{}), + RwriteType: reflect.TypeOf(Rwrite{}), + TclunkType: reflect.TypeOf(Tclunk{}), + RclunkType: reflect.TypeOf(Rclunk{}), + TremoveType: reflect.TypeOf(Tremove{}), + RremoveType: reflect.TypeOf(Rremove{}), + TstatType: reflect.TypeOf(Tstat{}), + RstatType: reflect.TypeOf(Rstat{}), + TwstatType: reflect.TypeOf(Twstat{}), + RwstatType: reflect.TypeOf(Rwstat{}), +}) + +// Proto returns the protocol implementation for 9P. +func Proto() proto.Proto { + return protocol +} + +type Tversion struct { + Msize uint32 + Version string +} + +func (*Tversion) P9NoTag() {} + +type Rversion struct { + Msize uint32 + Version string +} + +func (r *Rversion) P9Msize() uint32 { + return r.Msize +} + +type Tauth struct { + AFID uint32 + Uname string + Aname string +} + +type Rauth struct { + AQID QID +} + +type Tattach struct { + FID uint32 + AFID uint32 + Uname string + Aname string +} + +type Rattach struct { + QID QID +} + +// Rerror is a special response that represents an error. As a special +// case, this type also implements error for convenience. +type Rerror struct { + Ename string +} + +func (msg *Rerror) Error() string { + return msg.Ename +} + +type Tflush struct { + OldTag uint16 +} + +type Rflush struct { +} + +type Twalk struct { + FID uint32 + NewFID uint32 + Wname []string +} + +type Rwalk struct { + WQID []QID +} + +type Topen struct { + FID uint32 + Mode uint8 // TODO: Make a Mode type? +} + +type Ropen struct { + QID QID + IOUnit uint32 +} + +type Tcreate struct { + FID uint32 + Name string + Perm FileMode + Mode uint8 // TODO: Make a Mode type? +} + +type Rcreate struct { + QID QID + IOUnit uint32 +} + +type Tread struct { + FID uint32 + Offset uint64 + Count uint32 +} + +type Rread struct { + Data []byte +} + +type Twrite struct { + FID uint32 + Offset uint64 + Data []byte +} + +type Rwrite struct { + Count uint32 +} + +type Tclunk struct { + FID uint32 +} + +type Rclunk struct { +} + +type Tremove struct { + FID uint32 +} + +type Rremove struct { +} + +type Tstat struct { + FID uint32 +} + +type Rstat struct { + Stat Stat +} + +func (stat *Rstat) P9Encode() ([]byte, error) { + var buf bytes.Buffer + + err := proto.Write(&buf, stat.Stat.size()+2) + if err != nil { + return nil, err + } + + err = proto.Write(&buf, stat.Stat) + return buf.Bytes(), err +} + +func (stat *Rstat) P9Decode(r io.Reader) error { + var size uint16 + err := proto.Read(r, &size) + if err != nil { + return err + } + + r = &util.LimitedReader{ + R: r, + N: uint32(size), + E: ErrLargeStat, + } + + return proto.Read(r, &stat.Stat) +} + +type Twstat struct { + FID uint32 + Stat Stat +} + +func (stat *Twstat) P9Encode() ([]byte, error) { + var buf bytes.Buffer + + err := proto.Write(&buf, stat.FID) + if err != nil { + return nil, err + } + + err = proto.Write(&buf, stat.Stat.size()+2) + if err != nil { + return nil, err + } + + err = proto.Write(&buf, stat.Stat) + return buf.Bytes(), err +} + +func (stat *Twstat) P9Decode(r io.Reader) error { + err := proto.Read(r, &stat.FID) + if err != nil { + return err + } + + var size uint16 + err = proto.Read(r, &size) + if err != nil { + return err + } + + r = &util.LimitedReader{ + R: r, + N: uint32(size), + E: ErrLargeStat, + } + + return proto.Read(r, &stat.Stat) +} + +type Rwstat struct { +} diff --git a/vendor/github.com/DeedleFake/p9/p9.go b/vendor/github.com/DeedleFake/p9/p9.go new file mode 100644 index 0000000000..14b57c9ba9 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/p9.go @@ -0,0 +1,71 @@ +package p9 + +import ( + "math" +) + +const ( + // Version is the 9P version implemented by this package, both for + // server and client. + Version = "9P2000" +) + +const ( + // NoTag is a special tag that is used when the tag is unimportant. + NoTag uint16 = math.MaxUint16 + + // NoFID is a special FID that is used to signal a lack of an FID. + NoFID uint32 = math.MaxUint32 +) + +// File open modes and flags. Note that not all flags are supported +// where you might expect them to be. If you get an error saying that +// a flag won't fit into uint8, the flag you're trying to use probably +// isn't supported there. +const ( + OREAD uint8 = iota + OWRITE + ORDWR + OEXEC + + OTRUNC = 1 << (iota + 1) + OCEXEC + ORCLOSE + ODIRECT + ONONBLOCK + OEXCL + OLOCK + OAPPEND +) + +// QID represents a QID value. +type QID struct { + Type QIDType + Version uint32 + Path uint64 +} + +// QIDType represents an 8-bit QID type identifier. +type QIDType uint8 + +// Valid types of files. +const ( + QTFile QIDType = 0 + QTDir QIDType = 1 << (8 - iota) + QTAppend + QTExcl + QTMount + QTAuth + QTTmp + QTSymlink +) + +// FileMode converts the QIDType to a FileMode. +func (t QIDType) FileMode() FileMode { + return FileMode(t) << 24 +} + +// Other constants. +const ( + IOHeaderSize = 24 +) diff --git a/vendor/github.com/DeedleFake/p9/proto/client.go b/vendor/github.com/DeedleFake/p9/proto/client.go new file mode 100644 index 0000000000..6dcb959436 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/proto/client.go @@ -0,0 +1,223 @@ +package proto + +import ( + "context" + "io" + "log" + "net" + "sync/atomic" + "time" + + "github.com/DeedleFake/p9/internal/debug" +) + +// Client provides functionality for sending requests to and receiving +// responses from a server for a given protocol. It automatically +// handles message tags, properly blocking until a matching tag +// response has been received. +type Client struct { + done chan struct{} + cancel func() + + p Proto + c net.Conn + + nextTag chan uint16 + sentMsg chan clientMsg + recvMsg chan clientMsg + cancelMsg chan uint16 + + msize uint32 +} + +// NewClient initializes a client that communicates using c. The +// Client will close c when the Client is closed. +func NewClient(p Proto, c net.Conn) *Client { + ctx, cancel := context.WithCancel(context.Background()) + + client := &Client{ + done: make(chan struct{}), + cancel: cancel, + + p: p, + c: c, + + nextTag: make(chan uint16), + sentMsg: make(chan clientMsg), + recvMsg: make(chan clientMsg), + cancelMsg: make(chan uint16), + + msize: 1024, + } + go client.reader(ctx) + go client.coord(ctx) + + return client +} + +// Dial is a convenience function that dials and creates a client in +// the same step. +func Dial(p Proto, network, addr string) (*Client, error) { + c, err := net.Dial(network, addr) + if err != nil { + return nil, err + } + + return NewClient(p, c), nil +} + +// Close cleans up resources created by the client as well as closing +// the underlying connection. +func (c *Client) Close() error { + c.cancel() + return c.c.Close() +} + +// reader reads messages from the connection, sending them to the +// coordinator to be sent to waiting Send calls. +func (c *Client) reader(ctx context.Context) { + for { + err := c.c.SetReadDeadline(time.Now().Add(10 * time.Second)) + if err != nil { + if ctx.Err() != nil { + return + } + + log.Printf("Failed to set conn deadline: %v", err) + return + } + + msg, tag, err := c.p.Receive(c.c, c.Msize()) + if err != nil { + if (ctx.Err() != nil) || (err == io.EOF) { + return + } + + continue + } + + select { + case <-ctx.Done(): + return + + case c.recvMsg <- clientMsg{ + tag: tag, + recv: msg, + }: + } + } +} + +// coord coordinates between Send calls and the reader. +func (c *Client) coord(ctx context.Context) { + defer close(c.done) + + var nextTag uint16 + tags := make(map[uint16]chan interface{}) + + for { + select { + case <-ctx.Done(): + return + + case cm := <-c.sentMsg: + tags[cm.tag] = cm.ret + + case cm := <-c.recvMsg: + rcm, ok := tags[cm.tag] + if !ok { + continue + } + + rcm <- cm.recv + delete(tags, cm.tag) + + case tag := <-c.cancelMsg: + delete(tags, tag) + + case c.nextTag <- nextTag: + for { + nextTag++ + if _, ok := tags[nextTag]; !ok { + break + } + } + } + } +} + +// Msize returns the maxiumum size of a message. This does not perform +// any communication with the server. +func (c *Client) Msize() uint32 { + return atomic.LoadUint32(&c.msize) +} + +// SetMsize sets the maximum size of a message. This does not perform +// any communication with the server. +func (c *Client) SetMsize(size uint32) { + atomic.StoreUint32(&c.msize, size) +} + +// Send sends a message to the server, blocking until a response has +// been received. It is safe to place multiple Send calls +// concurrently, and each will return when the response to that +// request has been received. +func (c *Client) Send(msg interface{}) (interface{}, error) { + debug.Log("client -> %#v\n", msg) + + tag := NoTag + if _, ok := msg.(P9NoTag); !ok { + select { + case <-c.done: + return nil, ErrClientClosed + case tag = <-c.nextTag: + } + } + + ret := make(chan interface{}, 1) + select { + case <-c.done: + return nil, ErrClientClosed + + case c.sentMsg <- clientMsg{ + tag: tag, + ret: ret, + }: + } + + err := c.p.Send(c.c, tag, msg) + if err != nil { + select { + case <-c.done: + case c.cancelMsg <- tag: + } + return nil, err + } + + select { + case <-c.done: + return nil, ErrClientClosed + case rsp := <-ret: + debug.Log("client <- %#v\n", rsp) + + if err, ok := rsp.(error); ok { + return nil, err + } + return rsp, nil + } +} + +// Sometimes I think that some type of tuples would be nice... +type clientMsg struct { + tag uint16 + recv interface{} + ret chan interface{} +} + +// P9NoTag is implemented by any types that should not use tags for +// communicating. In 9P, for example, this is true of the Tversion +// message type, as it must be the first thing sent and no further +// communication can happen before an Rversion is sent in response. +type P9NoTag interface { + P9NoTag() +} diff --git a/vendor/github.com/DeedleFake/p9/proto/encoding.go b/vendor/github.com/DeedleFake/p9/proto/encoding.go new file mode 100644 index 0000000000..87facbee6e --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/proto/encoding.go @@ -0,0 +1,193 @@ +package proto + +import ( + "encoding/binary" + "io" + "reflect" + "time" + "unsafe" + + "github.com/DeedleFake/p9/internal/util" +) + +// Size returns the size of a message after encoding. +func Size(v interface{}) (uint32, error) { + e := &encoder{} + e.mode = e.size + + e.encode(reflect.ValueOf(v)) + return e.n, e.err +} + +// Write encodes and writes a message to w. It does not perform any +// buffering. It is the caller's responsibility to ensure that +// encoding does not interleave with other usages of w. +func Write(w io.Writer, v interface{}) error { + e := &encoder{w: w} + e.mode = e.write + + e.encode(reflect.ValueOf(v)) + return e.err +} + +// Read decodes a message from r into v. +func Read(r io.Reader, v interface{}) error { + d := &decoder{r: r} + d.decode(reflect.ValueOf(v)) + return d.err +} + +type encoder struct { + w io.Writer + n uint32 + err error + + mode func(interface{}) +} + +func (e *encoder) size(v interface{}) { + e.n += uint32(binary.Size(v)) +} + +func (e *encoder) write(v interface{}) { + if e.err != nil { + return + } + + e.err = binary.Write(e.w, binary.LittleEndian, v) +} + +func (e *encoder) encode(v reflect.Value) { + if e.err != nil { + return + } + + switch v := v.Interface().(type) { + case Encoder: + buf, err := v.P9Encode() + e.err = err + e.mode(buf) + return + } + + v = reflect.Indirect(v) + + switch v := v.Interface().(type) { + case time.Time: + e.mode(uint32(v.Unix())) + return + } + + switch v.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uintptr: + e.mode(v.Interface()) + + case reflect.Array, reflect.Slice: + switch v.Type().Elem().Kind() { + case reflect.Uint8: + e.mode(uint32(v.Len())) + default: + e.mode(uint16(v.Len())) + } + + for i := 0; i < v.Len(); i++ { + e.encode(v.Index(i)) + } + + case reflect.String: + e.mode(uint16(v.Len())) + e.mode([]byte(v.String())) + + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + e.encode(v.Field(i)) + } + + default: + e.err = util.Errorf("invalid type: %T", v) + } +} + +type decoder struct { + r io.Reader + err error +} + +func (d *decoder) read(v interface{}) { + if d.err != nil { + return + } + + d.err = binary.Read(d.r, binary.LittleEndian, v) +} + +func (d *decoder) decode(v reflect.Value) { + if d.err != nil { + return + } + + v = reflect.Indirect(v) + + switch v := v.Addr().Interface().(type) { + case Decoder: + d.err = v.P9Decode(d.r) + return + case *time.Time: + var unix uint32 + d.read(&unix) + *v = time.Unix(int64(unix), 0) + return + } + + switch v.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uintptr: + d.read(v.Addr().Interface()) + + case reflect.Slice: + var length uint32 + switch v.Type().Elem().Kind() { + case reflect.Uint8: + d.read(&length) + default: + d.read((*uint16)(unsafe.Pointer(&length))) + } + + if int(length) > v.Cap() { + v.Set(reflect.MakeSlice(v.Type(), int(length), int(length))) + } + v.Set(v.Slice(0, int(length))) + + for i := 0; i < v.Len(); i++ { + d.decode(v.Index(i)) + } + + case reflect.String: + var length uint16 + d.read(&length) + + buf := make([]byte, int(length)) + d.read(buf) + + v.SetString(*(*string)(unsafe.Pointer(&buf))) + + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + d.decode(v.Field(i)) + } + + default: + d.err = util.Errorf("invalid type: %T", v) + } +} + +// Encoder is implemented by types that want to encode themselves in +// a customized, non-standard way. +type Encoder interface { + P9Encode() ([]byte, error) +} + +// Decoder is implemented by types that want to decode themselves in +// a customized, non-standard way. +type Decoder interface { + P9Decode(r io.Reader) error +} diff --git a/vendor/github.com/DeedleFake/p9/proto/proto.go b/vendor/github.com/DeedleFake/p9/proto/proto.go new file mode 100644 index 0000000000..83e6c4b06b --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/proto/proto.go @@ -0,0 +1,171 @@ +// Package proto provides a usage-inspecific wrapper around 9P's data +// serialization and communication scheme. +package proto + +import ( + "bytes" + "errors" + "io" + "reflect" + "sync" + + "github.com/DeedleFake/p9/internal/util" +) + +var ( + // ErrLargeMessage is returned by various functions if a message is + // larger than the current maximum message size. + ErrLargeMessage = errors.New("message larger than msize") + + // ErrClientClosed is returned by attempts to send to a closed + // client. + ErrClientClosed = errors.New("client closed") +) + +const ( + // NoTag is a special tag that is used when a tag is unimportant. + NoTag uint16 = 0xFFFF +) + +var bufPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +// Proto represents a protocol. It maps between message type IDs and +// the Go types that those IDs correspond to. +type Proto struct { + rmap map[uint8]reflect.Type + smap map[reflect.Type]uint8 +} + +// NewProto builds a Proto from the given one-way mapping. +func NewProto(mapping map[uint8]reflect.Type) Proto { + rmap := make(map[uint8]reflect.Type, len(mapping)) + smap := make(map[reflect.Type]uint8, len(mapping)) + for id, t := range mapping { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + + rmap[id] = t + smap[t] = id + } + + return Proto{ + rmap: rmap, + smap: smap, + } +} + +// TypeFromID returns the Go type that corresponds to the given ID. If +// the ID is not recognized, it returns nil. +func (p Proto) TypeFromID(id uint8) reflect.Type { + return p.rmap[id] +} + +// IDFromType returns the message type ID that corresponds to the +// given Go type, and a boolean indicating that the mapping is valid. +func (p Proto) IDFromType(t reflect.Type) (uint8, bool) { + id, ok := p.smap[t] + return id, ok +} + +// Receive receives a message from r using the given maximum message +// size. It returns the message, the tag that the message was sent +// with, and an error, if any. +func (p Proto) Receive(r io.Reader, msize uint32) (msg interface{}, tag uint16, err error) { + var size uint32 + err = Read(r, &size) + if err != nil { + return nil, NoTag, util.Errorf("receive: %w", err) + } + + if (msize > 0) && (size > msize) { + return nil, NoTag, util.Errorf("receive: %w", ErrLargeMessage) + } + + lr := &util.LimitedReader{ + R: r, + N: size, + E: ErrLargeMessage, + } + + var msgType uint8 + err = Read(lr, &msgType) + if err != nil { + return nil, NoTag, util.Errorf("receive: failed to read message type: %w", err) + } + + t := p.TypeFromID(msgType) + if t == nil { + if err != nil { + return nil, NoTag, err + } + + return nil, NoTag, util.Errorf("receive: invalid message type: %v", msgType) + } + + tag = NoTag + err = Read(lr, &tag) + if err != nil { + return nil, tag, util.Errorf("receive: failed to read tag: %w", err) + } + + m := reflect.New(t) + err = Read(lr, m.Interface()) + if err != nil { + return nil, tag, util.Errorf("receive %v: %w", m.Type().Elem(), err) + } + + return m.Interface(), tag, err +} + +// Send writes a message to w with the given tag. It returns any +// errors that occur. +// +// Encoded messages are buffered locally before sending to ensure that +// pieces of a message don't get mixed with another one. +func (p Proto) Send(w io.Writer, tag uint16, msg interface{}) (err error) { + buf := bufPool.Get().(*bytes.Buffer) + defer func() { + if err == nil { + _, err = io.Copy(w, buf) + if err != nil { + err = util.Errorf("send: %w", err) + } + } + + buf.Reset() + bufPool.Put(buf) + }() + + msgType, ok := p.IDFromType(reflect.Indirect(reflect.ValueOf(msg)).Type()) + if !ok { + return util.Errorf("send: invalid message type: %T", msg) + } + + write := func(v interface{}) { + if err != nil { + return + } + + err = Write(buf, v) + if err != nil { + err = util.Errorf("send %T: %w", msg, err) + } + } + + n, err := Size(msg) + if err != nil { + return util.Errorf("send %T: %w", msg, err) + } + + write(4 + 1 + 2 + n) + write(msgType) + write(tag) + write(msg) + + return err +} diff --git a/vendor/github.com/DeedleFake/p9/proto/server.go b/vendor/github.com/DeedleFake/p9/proto/server.go new file mode 100644 index 0000000000..fb21910cd5 --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/proto/server.go @@ -0,0 +1,157 @@ +package proto + +import ( + "io" + "log" + "net" + "sync" +) + +// Serve serves a server for the given Proto, listening for new +// connection on lis and handling them using the provided handler. +// +// Note that to avoid a data race, messages from a single client are +// handled entirely sequentially until an msize has been established, +// at which point they will be handled concurrently. An msize is +// established when a handler returns a Msizer. +func Serve(lis net.Listener, p Proto, connHandler ConnHandler) (err error) { + for { + c, err := lis.Accept() + if err != nil { + return err + } + + go func() { + defer c.Close() + + if h, ok := connHandler.(handleConn); ok { + h.HandleConn(c) + } + if h, ok := connHandler.(handleDisconnect); ok { + defer h.HandleDisconnect(c) + } + + mh := connHandler.MessageHandler() + if c, ok := mh.(io.Closer); ok { + defer c.Close() + } + + handleMessages(c, p, mh) + }() + } +} + +// ListenAndServe is a convenience function that establishes a +// listener, via net.Listen, and then calls Serve. +func ListenAndServe(network, addr string, p Proto, connHandler ConnHandler) (rerr error) { + lis, err := net.Listen(network, addr) + if err != nil { + return err + } + defer func() { + err := lis.Close() + if (err != nil) && (rerr == nil) { + rerr = err + } + }() + + return Serve(lis, p, connHandler) +} + +func handleMessages(c net.Conn, p Proto, handler MessageHandler) { + var setter sync.Once + + var msize uint32 + mode := func(f func()) { + f() + } + + for { + tmsg, tag, err := p.Receive(c, msize) + if err != nil { + if err == io.EOF { + return + } + + log.Printf("Error reading message: %v", err) + } + + mode(func() { + rmsg := handler.HandleMessage(tmsg) + if rmsg, ok := rmsg.(Msizer); ok { + if msize > 0 { + log.Println("Warning: Attempted to set msize twice.") + } + + setter.Do(func() { + msize = rmsg.P9Msize() + mode = func(f func()) { + go f() + } + }) + } + + err := p.Send(c, tag, rmsg) + if err != nil { + log.Printf("Error writing message: %v", err) + } + }) + } +} + +// ConnHandler initializes new MessageHandlers for incoming +// connections. Unlike HTTP, which is a connectionless protocol, 9P +// and related protocols require that each connection be handled as a +// unique client session with a stored state, hence this two-step +// process. +// +// If a ConnHandler provides a HandleConn(net.Conn) method, that +// method will be called when a new connection is made. Similarly, if +// it provides a HandleDisconnect(net.Conn) method, that method will +// be called when a connection is ended. +type ConnHandler interface { + MessageHandler() MessageHandler +} + +type handleConn interface { + HandleConn(c net.Conn) +} + +type handleDisconnect interface { + HandleDisconnect(c net.Conn) +} + +// ConnHandlerFunc allows a function to be used as a ConnHandler. +type ConnHandlerFunc func() MessageHandler + +func (h ConnHandlerFunc) MessageHandler() MessageHandler { + return h() +} + +// MessageHandler handles messages for a single client connection. +// +// If a MessageHandler also implements io.Closer, then Close will be +// called when the connection ends. Its return value is ignored. +type MessageHandler interface { + // HandleMessage is passed received messages from the client. Its + // return value is then sent back to the client with the same tag. + HandleMessage(interface{}) interface{} +} + +// MessageHandlerFunc allows a function to be used as a MessageHandler. +type MessageHandlerFunc func(interface{}) interface{} + +func (h MessageHandlerFunc) HandleMessage(msg interface{}) interface{} { + return h(msg) +} + +// Msizer is implemented by types that, when returned from a message +// handler, should modify the maximum message size that the server +// should from that point forward. +// +// Note that if this is returned more than once for a single +// connection, a warning will be printed to stderr and the later +// values will be ignored. +type Msizer interface { + P9Msize() uint32 +} diff --git a/vendor/github.com/DeedleFake/p9/remote.go b/vendor/github.com/DeedleFake/p9/remote.go new file mode 100644 index 0000000000..f860b9352b --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/remote.go @@ -0,0 +1,353 @@ +package p9 + +import ( + "bufio" + "errors" + "io" + "path" + "strings" + "sync" + + "github.com/DeedleFake/p9/internal/util" +) + +// Remote provides a file-like interface for performing operations on +// files presented by a 9P server. +// +// Remote implements File, allowing it to be itself served using +// FileSystem. +type Remote struct { + client *Client + + fid uint32 + qid QID + + m sync.Mutex + pos uint64 +} + +// Type returns the type of the file represented by the Remote. +func (file *Remote) Type() QIDType { + return file.qid.Type +} + +func (file *Remote) walk(p string) (*Remote, error) { + fid := file.client.nextFID() + + w := []string{path.Clean(p)} + if w[0] != "/" { + w = strings.Split(w[0], "/") + } + if (len(w) == 1) && (w[0] == ".") { + w = nil + } + rsp, err := file.client.Send(&Twalk{ + FID: file.fid, + NewFID: fid, + Wname: w, + }) + if err != nil { + return nil, err + } + walk := rsp.(*Rwalk) + + qid := file.qid + if len(walk.WQID) != 0 { + qid = walk.WQID[len(walk.WQID)-1] + } + if len(walk.WQID) != len(w) { + qid = QID{ + Type: 0xFF, + Version: 0xFFFFFFFF, + Path: 0xFFFFFFFFFFFFFFFF, + } + } + + return &Remote{ + client: file.client, + fid: fid, + qid: qid, + }, nil +} + +// Open opens and returns a file relative to the current one. In many +// cases, this will likely be relative to the filesystem root. For +// example: +// +// root, _ := client.Attach(nil, "anyone", "/") +// file, _ := root.Open("some/file/or/another", p9.OREAD) +func (file *Remote) Open(p string, mode uint8) (*Remote, error) { + next, err := file.walk(p) + if err != nil { + return nil, err + } + + rsp, err := file.client.Send(&Topen{ + FID: next.fid, + Mode: mode, + }) + if err != nil { + return nil, err + } + open := rsp.(*Ropen) + + next.qid = open.QID + + return next, nil +} + +// Create creates, with the given permissions, and opens, with the +// given mode, a new file at p relative to the current file. +func (file *Remote) Create(p string, perm FileMode, mode uint8) (*Remote, error) { + dir, name := path.Split(p) + next, err := file.walk(path.Clean(dir)) + if err != nil { + return nil, err + } + + rsp, err := file.client.Send(&Tcreate{ + FID: next.fid, + Name: name, + Perm: perm, + Mode: mode, + }) + if err != nil { + return nil, err + } + create := rsp.(*Rcreate) + + next.qid = create.QID + + return next, nil +} + +// Remove deletes the file at p, relative to the current file. If p is +// "", it closes the current file, if open, and deletes it. +func (file *Remote) Remove(p string) error { + if p != "" { + file, err := file.walk(p) + if err != nil { + return err + } + // Close is not necessary. Remove is also a clunk. + + return file.Remove("") + } + + _, err := file.client.Send(&Tremove{ + FID: file.fid, + }) + return err +} + +// Seek seeks a file. As 9P requires clients to track their own +// positions in files, this is purely a local operation with the +// exception of the case of whence being io.SeekEnd, in which case a +// request will be made to the server in order to get the file's size. +func (file *Remote) Seek(offset int64, whence int) (int64, error) { + file.m.Lock() + defer file.m.Unlock() + + switch whence { + case io.SeekStart: + if offset < 0 { + return int64(file.pos), errors.New("negative offset") + } + + file.pos = uint64(offset) + return offset, nil + + case io.SeekCurrent: + npos := int64(file.pos) + offset + if npos < 0 { + return int64(file.pos), errors.New("negative offset") + } + + file.pos = uint64(npos) + return npos, nil + + case io.SeekEnd: + stat, err := file.Stat("") + if err != nil { + return int64(file.pos), err + } + + npos := int64(stat.Length) + offset + if npos < 0 { + return int64(file.pos), errors.New("negative offset") + } + + file.pos = uint64(npos) + return npos, nil + } + + panic(util.Errorf("Invalid whence: %v", whence)) +} + +// Read reads from the file at the internally-tracked offset. For more +// information, see ReadAt. +func (file *Remote) Read(buf []byte) (int, error) { + file.m.Lock() + defer file.m.Unlock() + + n, err := file.ReadAt(buf, int64(file.pos)) + file.pos += uint64(n) + return n, err +} + +func (file *Remote) maxBufSize() int { + return int(file.client.Msize() - IOHeaderSize) +} + +func (file *Remote) readPart(buf []byte, off int64) (int, error) { + rsp, err := file.client.Send(&Tread{ + FID: file.fid, + Offset: uint64(off), + Count: uint32(len(buf)), + }) + if err != nil { + return 0, err + } + read := rsp.(*Rread) + if len(read.Data) == 0 { + return 0, io.EOF + } + + n := copy(buf, read.Data) + return n, nil +} + +// ReadAt reads from the file at the given offset. If the buffer given +// will result in a response that is larger than the currently allowed +// message size, as established by the handshake, it will perform +// multiple read requests in sequence, reading each into the +// appropriate parts of the buffer. It returns the number of bytes +// read and an error, if any occurred. +// +// If an error occurs while performing the sequential requests, it +// will return immediately. +func (file *Remote) ReadAt(buf []byte, off int64) (int, error) { + size := len(buf) + if size > file.maxBufSize() { + size = file.maxBufSize() + } + + var total int + for start := 0; start < len(buf); start += size { + end := start + size + if end > len(buf) { + end = len(buf) + } + + n, err := file.readPart(buf[start:end], off+int64(start)) + total += n + if err != nil { + return total, err + } + } + return total, nil +} + +// Write writes to the file at the internally-tracked offset. For more +// information, see WriteAt. +func (file *Remote) Write(data []byte) (int, error) { + file.m.Lock() + defer file.m.Unlock() + + n, err := file.WriteAt(data, int64(file.pos)) + file.pos += uint64(n) + return n, err +} + +func (file *Remote) writePart(data []byte, off int64) (int, error) { + rsp, err := file.client.Send(&Twrite{ + FID: file.fid, + Offset: uint64(off), + Data: data, + }) + if err != nil { + return 0, err + } + write := rsp.(*Rwrite) + + if write.Count < uint32(len(data)) { + return int(write.Count), io.EOF + } + return int(write.Count), nil +} + +// WriteAt writes from the file at the given offset. If the buffer +// given will result in a request that is larger than the currently +// allowed message size, as established by the handshake, it will +// perform multiple write requests in sequence, writing each with +// appropriate offsets such that the entire buffer is written. It +// returns the number of bytes written and an error, if any occurred. +// +// If an error occurs while performing the sequential requests, it +// will return immediately. +func (file *Remote) WriteAt(data []byte, off int64) (int, error) { + size := len(data) + if size > file.maxBufSize() { + size = file.maxBufSize() + } + + var total int + for start := 0; start < len(data); start += size { + end := start + size + if end > len(data) { + end = len(data) + } + + n, err := file.writePart(data[start:end], off+int64(start)) + total += n + if err != nil { + return total, err + } + } + return total, nil +} + +// Close closes the file on the server. Further usage of the file will +// produce errors. +func (file *Remote) Close() error { + _, err := file.client.Send(&Tclunk{ + FID: file.fid, + }) + return err +} + +// Stat fetches and returns the DirEntry for the file located at p, +// relative to the current file. If p is "", it is considered to be +// the current file. +func (file *Remote) Stat(p string) (DirEntry, error) { + if p != "" { + file, err := file.walk(p) + if err != nil { + return DirEntry{}, err + } + defer file.Close() + + return file.Stat("") + } + + rsp, err := file.client.Send(&Tstat{ + FID: file.fid, + }) + if err != nil { + return DirEntry{}, err + } + stat := rsp.(*Rstat) + + return stat.Stat.DirEntry(), nil +} + +// Readdir reads the file as a directory, returning the list of +// directory entries returned by the server. +// +// As it returns the full list of entries, it never returns io.EOF. +// +// Note that to read this list again, the file must first be seeked to +// the beginning. +func (file *Remote) Readdir() ([]DirEntry, error) { + return ReadDir(bufio.NewReaderSize(file, file.maxBufSize())) +} diff --git a/vendor/github.com/DeedleFake/p9/stat.go b/vendor/github.com/DeedleFake/p9/stat.go new file mode 100644 index 0000000000..1117588cae --- /dev/null +++ b/vendor/github.com/DeedleFake/p9/stat.go @@ -0,0 +1,350 @@ +package p9 + +import ( + "bytes" + "errors" + "io" + "os" + "time" + "unsafe" + + "github.com/DeedleFake/p9/internal/util" + "github.com/DeedleFake/p9/proto" +) + +var ( + // ErrLargeStat is returned during decoding when a stat is larger + // than its own declared size. + ErrLargeStat = errors.New("stat larger than declared size") +) + +// FileMode stores permission and type information about a file or +// directory. +type FileMode uint32 + +// FileMode type bitmasks. +const ( + ModeDir FileMode = 1 << (31 - iota) + ModeAppend + ModeExclusive + ModeMount + ModeAuth + ModeTemporary + ModeSymlink + _ + ModeDevice + ModeNamedPipe + ModeSocket + ModeSetuid + ModeSetgid +) + +// ModeFromOS converts an os.FileMode to a FileMode. +func ModeFromOS(m os.FileMode) FileMode { + r := FileMode(m.Perm()) + + if m&os.ModeDir != 0 { + r |= ModeDir + } + if m&os.ModeAppend != 0 { + r |= ModeAppend + } + if m&os.ModeExclusive != 0 { + r |= ModeExclusive + } + if m&os.ModeTemporary != 0 { + r |= ModeTemporary + } + if m&os.ModeSymlink != 0 { + r |= ModeSymlink + } + if m&os.ModeDevice != 0 { + r |= ModeDevice + } + if m&os.ModeNamedPipe != 0 { + r |= ModeNamedPipe + } + if m&os.ModeSocket != 0 { + r |= ModeSocket + } + if m&os.ModeSetuid != 0 { + r |= ModeSetuid + } + if m&os.ModeSetgid != 0 { + r |= ModeSetgid + } + + return r +} + +// OS converts a FileMode to an os.FileMode. +func (m FileMode) OS() os.FileMode { + r := os.FileMode(m.Perm()) + + if m&ModeDir != 0 { + r |= os.ModeDir + } + if m&ModeAppend != 0 { + r |= os.ModeAppend + } + if m&ModeExclusive != 0 { + r |= os.ModeExclusive + } + if m&ModeTemporary != 0 { + r |= os.ModeTemporary + } + if m&ModeSymlink != 0 { + r |= os.ModeSymlink + } + if m&ModeDevice != 0 { + r |= os.ModeDevice + } + if m&ModeNamedPipe != 0 { + r |= os.ModeNamedPipe + } + if m&ModeSocket != 0 { + r |= os.ModeSocket + } + if m&ModeSetuid != 0 { + r |= os.ModeSetuid + } + if m&ModeSetgid != 0 { + r |= os.ModeSetgid + } + + return r +} + +// QIDType converts a FileMode to a QIDType. Note that this will +// result in a loss of information, as the information stored by +// QIDType is a direct subset of that handled by FileMode. +func (m FileMode) QIDType() QIDType { + return QIDType(m >> 24) +} + +// Type returns a FileMode containing only the type bits of m. +func (m FileMode) Type() FileMode { + return m & 0xFFFF0000 +} + +// Perm returns a FileMode containing only the permission bits of m. +func (m FileMode) Perm() FileMode { + return m & 0777 +} + +func (m FileMode) String() string { + buf := []byte("----------") + + const types = "dalMATL!DpSug" + for i := range types { + if m&(1<> 16), + QID: QID{ + Type: QIDType(d.FileMode >> 24), + Version: d.Version, + Path: d.Path, + }, + Mode: d.FileMode, + ATime: d.ATime, + MTime: d.MTime, + Length: d.Length, + Name: d.EntryName, + UID: d.UID, + GID: d.GID, + MUID: d.MUID, + } +} + +func (d DirEntry) Name() string { + return d.EntryName +} + +func (d DirEntry) Size() int64 { + return int64(d.Length) +} + +func (d DirEntry) Mode() os.FileMode { + return d.FileMode.OS() +} + +func (d DirEntry) ModTime() time.Time { + return d.MTime +} + +func (d DirEntry) IsDir() bool { + return d.FileMode&ModeDir != 0 +} + +func (d DirEntry) Sys() interface{} { + return d +} + +// StatChanges is a wrapper around DirEntry that is used in wstat +// requests. If one of its methods returns false, that field should be +// considered unset in the DirEntry. +type StatChanges struct { + DirEntry +} + +func (c StatChanges) Mode() (FileMode, bool) { + return c.DirEntry.FileMode, c.DirEntry.FileMode != 0xFFFFFFFF +} + +func (c StatChanges) ATime() (time.Time, bool) { + return c.DirEntry.ATime, c.DirEntry.ATime.Unix() != -1 +} + +func (c StatChanges) MTime() (time.Time, bool) { + return c.DirEntry.MTime, c.DirEntry.MTime.Unix() != -1 +} + +func (c StatChanges) Length() (uint64, bool) { + return c.DirEntry.Length, c.DirEntry.Length != 0xFFFFFFFFFFFFFFFF +} + +func (c StatChanges) Name() (string, bool) { + return c.DirEntry.EntryName, c.DirEntry.EntryName != "" +} + +func (c StatChanges) UID() (string, bool) { + return c.DirEntry.UID, c.DirEntry.UID != "" +} + +func (c StatChanges) GID() (string, bool) { + return c.DirEntry.GID, c.DirEntry.GID != "" +} + +func (c StatChanges) MUID() (string, bool) { + return c.DirEntry.MUID, c.DirEntry.MUID != "" +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9a91482015..ba21bd3f99 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,6 +10,12 @@ github.com/AlecAivazis/survey/v2/terminal ## explicit; go 1.18 github.com/BurntSushi/toml github.com/BurntSushi/toml/internal +# github.com/DeedleFake/p9 v0.6.12 +## explicit; go 1.13 +github.com/DeedleFake/p9 +github.com/DeedleFake/p9/internal/debug +github.com/DeedleFake/p9/internal/util +github.com/DeedleFake/p9/proto # github.com/Masterminds/semver/v3 v3.4.0 ## explicit; go 1.21 github.com/Masterminds/semver/v3 From 61491c2537d08652f3d839e7fbb88dcbec0a54c2 Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Mon, 15 Sep 2025 01:31:49 +0200 Subject: [PATCH 3/6] 9p: initial version --- cmd/crc/cmd/daemon.go | 17 +++++ packaging/windows/product.wxs.template | 6 ++ pkg/crc/constants/constants.go | 5 ++ pkg/crc/machine/start.go | 15 +++++ pkg/fileserver/fs9p/server.go | 87 ++++++++++++++++++++++++++ pkg/fileserver/fs9p/shares.go | 61 ++++++++++++++++++ pkg/fileserver/fs9p/shares_fallback.go | 13 ++++ pkg/fileserver/fs9p/shares_windows.go | 33 ++++++++++ 8 files changed, 237 insertions(+) create mode 100644 pkg/fileserver/fs9p/server.go create mode 100644 pkg/fileserver/fs9p/shares.go create mode 100644 pkg/fileserver/fs9p/shares_fallback.go create mode 100644 pkg/fileserver/fs9p/shares_windows.go diff --git a/cmd/crc/cmd/daemon.go b/cmd/crc/cmd/daemon.go index ed83e08a1f..08fd6779c3 100644 --- a/cmd/crc/cmd/daemon.go +++ b/cmd/crc/cmd/daemon.go @@ -11,6 +11,7 @@ import ( "os" "os/signal" "regexp" + "runtime" "syscall" "time" @@ -24,6 +25,7 @@ import ( "github.com/crc-org/crc/v2/pkg/crc/constants" "github.com/crc-org/crc/v2/pkg/crc/daemonclient" "github.com/crc-org/crc/v2/pkg/crc/logging" + "github.com/crc-org/crc/v2/pkg/fileserver/fs9p" "github.com/crc-org/machine/libmachine/drivers" "github.com/docker/go-units" "github.com/gorilla/handlers" @@ -248,6 +250,21 @@ func run(configuration *types.Configuration) error { } }() + // 9p home directory sharing + if runtime.GOOS == "windows" { + if _, err := fs9p.StartHvsockShares([]fs9p.HvsockMount9p{{Path: constants.GetHomeDir(), HvsockGUID: constants.Plan9HvsockGUID}}); err != nil { + logging.Warnf("Failed to start 9p file server on hvsock: %v", err) + logging.Warnf("Falling back to 9p over TCP") + listener9p, err := vn.Listen("tcp", net.JoinHostPort(configuration.GatewayIP, fmt.Sprintf("%d", constants.Plan9TcpPort))) + if err != nil { + return err + } + if _, err := fs9p.StartShares([]fs9p.Mount9p{{Listener: listener9p, Path: constants.GetHomeDir()}}); err != nil { + return err + } + } + } + startupDone() if logging.IsDebug() { diff --git a/packaging/windows/product.wxs.template b/packaging/windows/product.wxs.template index 20a3b8b921..73c341237c 100755 --- a/packaging/windows/product.wxs.template +++ b/packaging/windows/product.wxs.template @@ -72,6 +72,11 @@ + + + + + @@ -130,6 +135,7 @@ + diff --git a/pkg/crc/constants/constants.go b/pkg/crc/constants/constants.go index b2cc95feca..fee3d19752 100644 --- a/pkg/crc/constants/constants.go +++ b/pkg/crc/constants/constants.go @@ -55,6 +55,11 @@ const ( OpenShiftIngressHTTPSPort = 443 BackgroundLauncherExecutable = "crc-background-launcher.exe" + + Plan9Msize = 1024 * 1024 + Plan9TcpPort = 564 + Plan9HvsockGUID = "00009000-FACB-11E6-BD58-64006A7986D3" + Plan9HvsockPort = 36864 ) var adminHelperExecutableForOs = map[string]string{ diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go index 58ee542a6b..dc61136f8e 100644 --- a/pkg/crc/machine/start.go +++ b/pkg/crc/machine/start.go @@ -248,6 +248,7 @@ func configureSharedDirs(vm *virtualMachine, sshRunner *crcssh.Runner) error { if _, _, err := sshRunner.RunPrivileged(fmt.Sprintf("Mounting %s", mount.Target), "mount", "-o", "context=\"system_u:object_r:container_file_t:s0\"", "-t", mount.Type, mount.Tag, mount.Target); err != nil { return err } + case "cifs": smbUncPath := fmt.Sprintf("//%s/%s", hostVirtualIP, mount.Tag) if _, _, err := sshRunner.RunPrivate("sudo", "mount", "-o", fmt.Sprintf("rw,uid=core,gid=core,username='%s',password='%s'", mount.Username, mount.Password), "-t", mount.Type, smbUncPath, mount.Target); err != nil { @@ -257,6 +258,20 @@ func configureSharedDirs(vm *virtualMachine, sshRunner *crcssh.Runner) error { } return fmt.Errorf("Failed to mount CIFS/SMB share '%s' please make sure configured password is correct: %w", mount.Tag, err) } + + case "9p": + // change owner to core user to allow mounting to it as a non-root user + if _, _, err := sshRunner.RunPrivileged("Changing owner of mount directory", "chown core:core", mount.Target); err != nil { + return err + } + if _, _, err := sshRunner.Run("9pfs -V -p", fmt.Sprintf("%d", constants.Plan9HvsockPort), mount.Target); err != nil { + logging.Warnf("Failed to connect to 9p server over hvsock: %v", err) + logging.Warnf("Falling back to 9p over TCP") + if _, _, err := sshRunner.Run("9pfs 192.168.127.1", mount.Target); err != nil { + return err + } + } + default: return fmt.Errorf("Unknown Shared dir type requested: %s", mount.Type) } diff --git a/pkg/fileserver/fs9p/server.go b/pkg/fileserver/fs9p/server.go new file mode 100644 index 0000000000..850b439e2a --- /dev/null +++ b/pkg/fileserver/fs9p/server.go @@ -0,0 +1,87 @@ +package fs9p + +import ( + "fmt" + "net" + "os" + "path/filepath" + + "github.com/DeedleFake/p9" + "github.com/DeedleFake/p9/proto" + "github.com/crc-org/crc/v2/pkg/crc/constants" + "github.com/sirupsen/logrus" +) + +type Server struct { + // Listener this server is bound to + Listener net.Listener + // Plan9 Filesystem type that holds the exposed directory + Filesystem p9.FileSystem + // Directory this server exposes + ExposedDir string + // Errors from the server being started will come out here + ErrChan chan error +} + +// New9pServer exposes a single directory (and all children) via the given net.Listener +// and returns the server struct. +// Directory given must be an absolute path and must exist. +func New9pServer(listener net.Listener, exposeDir string) (*Server, error) { + // Verify that exposeDir makes sense. + if !filepath.IsAbs(exposeDir) { + return nil, fmt.Errorf("path to expose to machine must be absolute: %s", exposeDir) + } + stat, err := os.Stat(exposeDir) + if err != nil { + return nil, fmt.Errorf("cannot stat path to expose to machine: %w", err) + } + if !stat.IsDir() { + return nil, fmt.Errorf("path to expose to machine must be a directory: %s", exposeDir) + } + + fs := p9.FileSystem(p9.Dir(exposeDir)) + errChan := make(chan error) + + toReturn := new(Server) + toReturn.Listener = listener + toReturn.Filesystem = fs + toReturn.ExposedDir = exposeDir + toReturn.ErrChan = errChan + + return toReturn, nil +} + +// Start a server created by New9pServer. +func (s *Server) Start() error { + go func() { + s.ErrChan <- proto.Serve(s.Listener, p9.Proto(), p9.FSConnHandler(s.Filesystem, constants.Plan9Msize)) + close(s.ErrChan) + }() + + // Just before returning, check to see if we got an error off server startup. + select { + case err := <-s.ErrChan: + return fmt.Errorf("starting 9p server: %w", err) + default: + logrus.Infof("Successfully started 9p server on %s for directory %s", s.Listener.Addr().String(), s.ExposedDir) + return nil + } +} + +// Stop a running server. +// Please note that this does *BAD THINGS* to clients if they are still running +// when the server stops. Processes get stuck in I/O deep sleep and zombify, and +// nothing I do other than restarting the VM can remove the zombies. +func (s *Server) Stop() error { + if err := s.Listener.Close(); err != nil { + return err + } + logrus.Infof("Successfully stopped 9p server for directory %s", s.ExposedDir) + return nil +} + +// WaitForError from a running server. +func (s *Server) WaitForError() error { + err := <-s.ErrChan + return err +} diff --git a/pkg/fileserver/fs9p/shares.go b/pkg/fileserver/fs9p/shares.go new file mode 100644 index 0000000000..713f2c83c6 --- /dev/null +++ b/pkg/fileserver/fs9p/shares.go @@ -0,0 +1,61 @@ +package fs9p + +import ( + "fmt" + "net" + + "github.com/sirupsen/logrus" +) + +// Mount9p represents an exposed directory path with the listener +// the 9p server is bound to +type Mount9p struct { + Path string + Listener net.Listener +} + +// HvsockMount9p represents an exposed directory path with the hvsock GUID +// the 9p server is bound to +type HvsockMount9p struct { + Path string + HvsockGUID string +} + +// StartShares starts a new 9p server for each of the supplied mounts. +func StartShares(mounts []Mount9p) (servers []*Server, defErr error) { + servers9p := []*Server{} + for _, m := range mounts { + server, err := New9pServer(m.Listener, m.Path) + if err != nil { + return nil, fmt.Errorf("serving directory %s on %s: %w", m.Path, m.Listener.Addr().String(), err) + } + + err = server.Start() + if err != nil { + return nil, fmt.Errorf("starting 9p server for directory %s: %w", m.Path, err) + } + + servers9p = append(servers9p, server) + + defer func() { + if defErr != nil { + if err := server.Stop(); err != nil { + logrus.Errorf("Error stopping 9p server: %v", err) + } + } + }() + + serverDir := m.Path + + go func() { + if err := server.WaitForError(); err != nil { + logrus.Errorf("Error from 9p server on %s for %s: %v", m.Listener.Addr().String(), serverDir, err) + } else { + // We do not expect server exits - this should run until the program exits. + logrus.Warnf("9p server on %s for %s exited without error", m.Listener.Addr().String(), serverDir) + } + }() + } + + return servers9p, nil +} diff --git a/pkg/fileserver/fs9p/shares_fallback.go b/pkg/fileserver/fs9p/shares_fallback.go new file mode 100644 index 0000000000..149f0e1ffb --- /dev/null +++ b/pkg/fileserver/fs9p/shares_fallback.go @@ -0,0 +1,13 @@ +//go:build !windows + +package fs9p + +import ( + "fmt" + "runtime" +) + +// StartHvsockShares is only supported on Windows +func StartHvsockShares(mounts []HvsockMount9p) ([]*Server, error) { + return nil, fmt.Errorf("StartHvsockShares() not implemented on %s", runtime.GOOS) +} diff --git a/pkg/fileserver/fs9p/shares_windows.go b/pkg/fileserver/fs9p/shares_windows.go new file mode 100644 index 0000000000..6702222b71 --- /dev/null +++ b/pkg/fileserver/fs9p/shares_windows.go @@ -0,0 +1,33 @@ +package fs9p + +import ( + "fmt" + + "github.com/linuxkit/virtsock/pkg/hvsock" + "github.com/sirupsen/logrus" +) + +// StartHvsockShares starts serving the given shares on hvsocks instead of TCP sockets. +// The hvsocks used must already be defined before StartHvsockShares is called. +func StartHvsockShares(mounts []HvsockMount9p) ([]*Server, error) { + mounts9p := []Mount9p{} + for _, mount := range mounts { + service, err := hvsock.GUIDFromString(mount.HvsockGUID) + if err != nil { + return nil, fmt.Errorf("parsing hvsock guid %s: %w", mount.HvsockGUID, err) + } + + listener, err := hvsock.Listen(hvsock.Addr{ + VMID: hvsock.GUIDWildcard, + ServiceID: service, + }) + if err != nil { + return nil, fmt.Errorf("retrieving listener for hvsock %s: %w", mount.HvsockGUID, err) + } + + logrus.Debugf("Going to serve directory %s on hvsock %s", mount.Path, mount.HvsockGUID) + mounts9p = append(mounts9p, Mount9p{Path: mount.Path, Listener: listener}) + } + + return StartShares(mounts9p) +} From 31f4ba77733dcbb14df3a7dd8518d21fd4a8d634 Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Mon, 15 Sep 2025 01:32:21 +0200 Subject: [PATCH 4/6] 9p: add another shared directory for 9p when creating VM --- pkg/crc/machine/libhvee/driver_windows.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/crc/machine/libhvee/driver_windows.go b/pkg/crc/machine/libhvee/driver_windows.go index 9464b7ac6b..1b7728b6f1 100644 --- a/pkg/crc/machine/libhvee/driver_windows.go +++ b/pkg/crc/machine/libhvee/driver_windows.go @@ -43,7 +43,14 @@ func configureShareDirs(machineConfig config.MachineConfig) []drivers.SharedDir Type: "cifs", Username: machineConfig.SharedDirUsername, } - sharedDirs = append(sharedDirs, sharedDir) + sharedDir9p := drivers.SharedDir{ + Source: dir, + Target: convertToUnixPath(dir) + "9p", // temporary solution until smb sharing is removed + Tag: "crc-dir0", // same as above + //Tag: fmt.Sprintf("dir%d", i), + Type: "9p", + } + sharedDirs = append(sharedDirs, sharedDir, sharedDir9p) } return sharedDirs } From f04c9fdcfbdba23dc06d0b6a797398a7ace06c97 Mon Sep 17 00:00:00 2001 From: Anjan Nath Date: Fri, 14 Feb 2025 16:58:00 +0530 Subject: [PATCH 5/6] remove enabling of file sharing and creation of smbshare from MSI this will be moved to the 'crc setup' command as we should only enable file sharing and create the smb share for home dir when users have set the config option 'enable-shared-dirs' to 'true' this should remove at least of the warnings reported by virustotal for the MSI: https://www.virustotal.com/gui/file/31b402dcc1da24265074a21a26018d6cde8eef0b63c77a18f89eb079b6556790 --- packaging/windows/product.wxs.template | 30 -------------------------- 1 file changed, 30 deletions(-) diff --git a/packaging/windows/product.wxs.template b/packaging/windows/product.wxs.template index 73c341237c..a04b19fd06 100755 --- a/packaging/windows/product.wxs.template +++ b/packaging/windows/product.wxs.template @@ -35,12 +35,6 @@ Name="Path" /> - - - - - crc-dir0 - @@ -97,24 +91,6 @@ Before="AddUserToHypervAdminGroup" Sequence="execute"/> - - - - - - NOT Installed AND NOT REMOVE~="ALL" AND NOT WIX_UPGRADE_DETECTED @@ -123,9 +99,6 @@ NOT Installed AND NOT REMOVE~="ALL" AND NOT WIX_UPGRADE_DETECTED NOT Installed AND NOT REMOVE~="ALL" AND NOT WIX_UPGRADE_DETECTED Installed AND NOT UPGRADINGPRODUCTCODE - NOT Installed AND NOT REMOVE~="ALL" AND NOT WIX_UPGRADE_DETECTED - NOT Installed AND NOT REMOVE~="ALL" - Installed AND NOT UPGRADINGPRODUCTCODE NOT Installed AND NOT REMOVE~="ALL" AND NOT WIX_UPGRADE_DETECTED @@ -146,9 +119,6 @@ Installing Hyper-V Adding user: [LogonUser] to Hyper-V Administrators group Removing crcDaemon task - Creating share named: [SHAREDDIRNAME] for folder: [USERFOLDER] - Removing share named: [SHAREDDIRNAME] for folder: [USERFOLDER] - Enabling file and printer Sharing From f330f896f5207e9b6dd6b7840aa39d105e10ead3 Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Thu, 14 Aug 2025 02:17:12 +0200 Subject: [PATCH 6/6] removed SMB file sharing After the addition of 9p file sharing for Windows, we are removing SMB file sharing support (#4768). --- cmd/crc/cmd/start.go | 11 ---------- pkg/crc/config/settings.go | 26 ++--------------------- pkg/crc/machine/driver.go | 16 -------------- pkg/crc/machine/driver_darwin.go | 5 ----- pkg/crc/machine/driver_linux.go | 5 ----- pkg/crc/machine/driver_windows.go | 5 ----- pkg/crc/machine/libhvee/driver_windows.go | 19 ++++++----------- pkg/crc/machine/start.go | 20 +---------------- pkg/drivers/libhvee/libhvee_windows.go | 5 ----- pkg/drivers/libhvee/powershell_windows.go | 13 ------------ 10 files changed, 9 insertions(+), 116 deletions(-) diff --git a/cmd/crc/cmd/start.go b/cmd/crc/cmd/start.go index 445af50880..e8f926e5a0 100644 --- a/cmd/crc/cmd/start.go +++ b/cmd/crc/cmd/start.go @@ -106,17 +106,6 @@ func runStart(ctx context.Context) (*types.StartResult, error) { } } - if runtime.GOOS == "windows" { - username, err := crcos.GetCurrentUsername() - if err != nil { - return nil, err - } - - // config SharedDirPassword ('shared-dir-password') only exists in windows - startConfig.SharedDirPassword = config.Get(crcConfig.SharedDirPassword).AsString() - startConfig.SharedDirUsername = username - } - return client.Start(ctx, startConfig) } diff --git a/pkg/crc/config/settings.go b/pkg/crc/config/settings.go index b8c24c5242..df7f200c11 100644 --- a/pkg/crc/config/settings.go +++ b/pkg/crc/config/settings.go @@ -2,7 +2,6 @@ package config import ( "fmt" - "runtime" "github.com/crc-org/crc/v2/pkg/crc/constants" "github.com/crc-org/crc/v2/pkg/crc/logging" @@ -62,17 +61,6 @@ func RegisterSettings(cfg *Config) { return validateBundlePath(value, GetPreset(cfg)) } - validateSmbSharedDirs := func(value interface{}) (bool, string) { - if !cfg.Get(HostNetworkAccess).AsBool() { - return false, fmt.Sprintf("%s can only be used with %s set to 'true'", - EnableSharedDirs, HostNetworkAccess) - } - if cfg.Get(SharedDirPassword).IsDefault { - return false, fmt.Sprintf("Please set '%s' first to enable shared directories", SharedDirPassword) - } - return ValidateBool(value) - } - // Preset setting should be on top because CPUs/Memory config depend on it. cfg.AddSetting(Preset, version.GetDefaultPreset().String(), validatePreset, RequiresDeleteAndSetupMsg, fmt.Sprintf("Virtual machine preset (valid values are: %s)", preset.AllPresets())) @@ -96,18 +84,8 @@ func RegisterSettings(cfg *Config) { "Enable emergency login for 'core' user. Password is randomly generated. (true/false, default: false)") cfg.AddSetting(PersistentVolumeSize, constants.DefaultPersistentVolumeSize, validatePersistentVolumeSize, SuccessfullyApplied, fmt.Sprintf("Total size in GiB of the persistent volume used by the CSI driver for %s preset (must be greater than or equal to '%d')", preset.Microshift, constants.DefaultPersistentVolumeSize)) - - // Shared directories configs - if runtime.GOOS == "windows" { - cfg.AddSetting(SharedDirPassword, Secret(""), validateString, SuccessfullyApplied, - "Password used while using CIFS/SMB file sharing (It is the password for the current logged in user)") - - cfg.AddSetting(EnableSharedDirs, false, validateSmbSharedDirs, SuccessfullyApplied, - "Mounts host's user profile folder at '/' in the CRC VM (true/false, default: false)") - } else { - cfg.AddSetting(EnableSharedDirs, true, ValidateBool, SuccessfullyApplied, - "Mounts host's home directory at '/' in the CRC VM (true/false, default: true)") - } + cfg.AddSetting(EnableSharedDirs, true, ValidateBool, SuccessfullyApplied, + "Mounts host's home directory at '/' in the CRC VM (true/false, default: true)") if !version.IsInstaller() { cfg.AddSetting(NetworkMode, string(defaultNetworkMode()), network.ValidateMode, network.SuccessfullyAppliedMode, diff --git a/pkg/crc/machine/driver.go b/pkg/crc/machine/driver.go index b96f03e204..4e6de3e3b9 100644 --- a/pkg/crc/machine/driver.go +++ b/pkg/crc/machine/driver.go @@ -57,19 +57,3 @@ func setDiskSize(host *host.Host, diskSize strongunits.GiB) error { return updateDriverValue(host, diskSizeSetter) } - -func setSharedDirPassword(host *host.Host, password string) error { - driver, err := loadDriverConfig(host) - if err != nil { - return err - } - - if len(driver.SharedDirs) == 0 { - return nil - } - - for i := range driver.SharedDirs { - driver.SharedDirs[i].Password = password - } - return updateDriverStruct(host, driver) -} diff --git a/pkg/crc/machine/driver_darwin.go b/pkg/crc/machine/driver_darwin.go index 9567304ebf..30ff1617f1 100644 --- a/pkg/crc/machine/driver_darwin.go +++ b/pkg/crc/machine/driver_darwin.go @@ -9,7 +9,6 @@ import ( machineVf "github.com/crc-org/crc/v2/pkg/drivers/vfkit" "github.com/crc-org/crc/v2/pkg/libmachine" "github.com/crc-org/crc/v2/pkg/libmachine/host" - "github.com/crc-org/machine/libmachine/drivers" ) func newHost(api libmachine.API, machineConfig config.MachineConfig) (*host.Host, error) { @@ -35,7 +34,3 @@ func updateDriverConfig(host *host.Host, driver *machineVf.Driver) error { return host.UpdateConfig(driverData) } - -func updateDriverStruct(_ *host.Host, _ *machineVf.Driver) error { - return drivers.ErrNotImplemented -} diff --git a/pkg/crc/machine/driver_linux.go b/pkg/crc/machine/driver_linux.go index 87e4475c0b..a6d43f0303 100644 --- a/pkg/crc/machine/driver_linux.go +++ b/pkg/crc/machine/driver_linux.go @@ -10,7 +10,6 @@ import ( "github.com/crc-org/crc/v2/pkg/libmachine" "github.com/crc-org/crc/v2/pkg/libmachine/host" machineLibvirt "github.com/crc-org/machine/drivers/libvirt" - "github.com/crc-org/machine/libmachine/drivers" ) func newHost(api libmachine.API, machineConfig config.MachineConfig) (*host.Host, error) { @@ -44,7 +43,3 @@ func (r *RPCServerDriver) SetConfigRaw(data []byte, _ *struct{}) error { return json.Unmarshal(data, &r.ActualDriver) } */ - -func updateDriverStruct(_ *host.Host, _ *machineLibvirt.Driver) error { - return drivers.ErrNotImplemented -} diff --git a/pkg/crc/machine/driver_windows.go b/pkg/crc/machine/driver_windows.go index 0a9a6b516b..b7ef70bab7 100644 --- a/pkg/crc/machine/driver_windows.go +++ b/pkg/crc/machine/driver_windows.go @@ -33,8 +33,3 @@ func updateDriverConfig(host *host.Host, driver *machineLibhvee.Driver) error { } return host.UpdateConfig(driverData) } - -func updateDriverStruct(host *host.Host, driver *machineLibhvee.Driver) error { - host.Driver = driver - return nil -} diff --git a/pkg/crc/machine/libhvee/driver_windows.go b/pkg/crc/machine/libhvee/driver_windows.go index 1b7728b6f1..5fe7e3a879 100644 --- a/pkg/crc/machine/libhvee/driver_windows.go +++ b/pkg/crc/machine/libhvee/driver_windows.go @@ -1,6 +1,7 @@ package libhvee import ( + "fmt" "path/filepath" "strings" @@ -35,22 +36,14 @@ func convertToUnixPath(path string) string { func configureShareDirs(machineConfig config.MachineConfig) []drivers.SharedDir { var sharedDirs []drivers.SharedDir - for _, dir := range machineConfig.SharedDirs { + for i, dir := range machineConfig.SharedDirs { sharedDir := drivers.SharedDir{ - Source: dir, - Target: convertToUnixPath(dir), - Tag: "crc-dir0", // smb share 'crc-dir0' is created in the msi - Type: "cifs", - Username: machineConfig.SharedDirUsername, - } - sharedDir9p := drivers.SharedDir{ Source: dir, - Target: convertToUnixPath(dir) + "9p", // temporary solution until smb sharing is removed - Tag: "crc-dir0", // same as above - //Tag: fmt.Sprintf("dir%d", i), - Type: "9p", + Target: convertToUnixPath(dir), + Tag: fmt.Sprintf("dir%d", i), + Type: "9p", } - sharedDirs = append(sharedDirs, sharedDir, sharedDir9p) + sharedDirs = append(sharedDirs, sharedDir) } return sharedDirs } diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go index dc61136f8e..bd7f62dc1d 100644 --- a/pkg/crc/machine/start.go +++ b/pkg/crc/machine/start.go @@ -100,14 +100,6 @@ func (client *client) updateVMConfig(startConfig types.StartConfig, vm *virtualM } } - // we want to set the shared dir password on-the-fly to be used - // we do not want this value to be persisted to disk - if startConfig.SharedDirPassword != "" { - if err := setSharedDirPassword(vm.Host, startConfig.SharedDirPassword); err != nil { - return fmt.Errorf("Failed to set shared dir password: %w", err) - } - } - return nil } @@ -214,7 +206,7 @@ func configureSharedDirs(vm *virtualMachine, sshRunner *crcssh.Runner) error { if err != nil { // the libvirt machine driver uses net/rpc, which wraps errors // in rpc.ServerError, but without using golang 1.13 error - // wrapping feature. Moreover this package is marked as + // wrapping feature. Moreover, this package is marked as // frozen/not accepting new features, so it's unlikely we'll // ever be able to use errors.Is() if err.Error() == drivers.ErrNotSupported.Error() || err.Error() == drivers.ErrNotImplemented.Error() { @@ -249,16 +241,6 @@ func configureSharedDirs(vm *virtualMachine, sshRunner *crcssh.Runner) error { return err } - case "cifs": - smbUncPath := fmt.Sprintf("//%s/%s", hostVirtualIP, mount.Tag) - if _, _, err := sshRunner.RunPrivate("sudo", "mount", "-o", fmt.Sprintf("rw,uid=core,gid=core,username='%s',password='%s'", mount.Username, mount.Password), "-t", mount.Type, smbUncPath, mount.Target); err != nil { - err = &crcerrors.MaskedSecretError{ - Err: err, - Secret: mount.Password, - } - return fmt.Errorf("Failed to mount CIFS/SMB share '%s' please make sure configured password is correct: %w", mount.Tag, err) - } - case "9p": // change owner to core user to allow mounting to it as a non-root user if _, _, err := sshRunner.RunPrivileged("Changing owner of mount directory", "chown core:core", mount.Target); err != nil { diff --git a/pkg/drivers/libhvee/libhvee_windows.go b/pkg/drivers/libhvee/libhvee_windows.go index df49b2054f..18f0e031c6 100644 --- a/pkg/drivers/libhvee/libhvee_windows.go +++ b/pkg/drivers/libhvee/libhvee_windows.go @@ -271,11 +271,6 @@ func (d *Driver) GetIP() (string, error) { } func (d *Driver) GetSharedDirs() ([]drivers.SharedDir, error) { - for _, dir := range d.SharedDirs { - if !smbShareExists(dir.Tag) { - return []drivers.SharedDir{}, nil - } - } return d.SharedDirs, nil } diff --git a/pkg/drivers/libhvee/powershell_windows.go b/pkg/drivers/libhvee/powershell_windows.go index 6c8f5c7ab3..91f741efdd 100644 --- a/pkg/drivers/libhvee/powershell_windows.go +++ b/pkg/drivers/libhvee/powershell_windows.go @@ -2,7 +2,6 @@ package libhvee import ( "errors" - "fmt" log "github.com/crc-org/crc/v2/pkg/crc/logging" "github.com/crc-org/crc/v2/pkg/os/windows/powershell" @@ -20,11 +19,6 @@ func cmdOut(args ...string) (string, error) { return stdout, err } -func cmd(args ...string) error { - _, err := cmdOut(args...) - return err -} - func hypervAvailable() error { stdout, err := cmdOut("@(Get-Module -ListAvailable hyper-v).Name | Get-Unique") if err != nil { @@ -75,10 +69,3 @@ func isWindowsAdministrator() (bool, error) { resp := crcstrings.FirstLine(stdout) return resp == "True", nil } - -func smbShareExists(name string) bool { - if err := cmd(fmt.Sprintf("Get-SmbShare -Name %s", name)); err != nil { - return false - } - return true -}