From 4b2e62528be870618c4af9f2b3a6c520856bf18c Mon Sep 17 00:00:00 2001 From: Christophe Fergeau Date: Mon, 8 Apr 2024 15:20:50 +0200 Subject: [PATCH 1/4] 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 5b1c7f098b7d417f7fb3668f5b016d6e4077c390 Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Sat, 19 Apr 2025 16:56:30 +0200 Subject: [PATCH 2/4] 9p: add libraries and dependencies --- go.mod | 1 + 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, 3521 insertions(+) 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 97148cf7ed..3ac25bdf95 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 diff --git a/go.sum b/go.sum index a73b1bdd62..d765643588 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= @@ -108,6 +112,8 @@ 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/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k= github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +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= @@ -349,6 +355,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/robertkrimen/godocdown v0.0.0-20130622164427-0bfa04905481/go.mod h1:C9WhFzY47SzYBIvzFqSvHIR6ROgDo4TtdTuRaOMjF/s= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= @@ -395,6 +402,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= @@ -426,6 +434,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= @@ -518,9 +527,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= @@ -564,6 +575,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 de8d94baec..df48bf699f 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 bd115c9d7dd958478490e84429faf8f0d84ded2e Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Wed, 1 Oct 2025 16:56:39 +0200 Subject: [PATCH 3/4] 9p: initial version --- cmd/crc/cmd/daemon.go | 27 +++++++++++ pkg/crc/constants/constants.go | 3 ++ pkg/crc/machine/start.go | 11 +++++ pkg/fileserver/fs9p/server.go | 88 ++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 pkg/fileserver/fs9p/server.go diff --git a/cmd/crc/cmd/daemon.go b/cmd/crc/cmd/daemon.go index ed83e08a1f..c91b8b1cd4 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,31 @@ func run(configuration *types.Configuration) error { } }() + // 9p home directory sharing + if runtime.GOOS == "windows" { + listener9p, err := vn.Listen("tcp", net.JoinHostPort(configuration.GatewayIP, fmt.Sprintf("%d", constants.Plan9TcpPort))) + if err != nil { + return err + } + server9p, err := fs9p.New9pServer(listener9p, constants.GetHomeDir()) + if err != nil { + return err + } + if err := server9p.Start(); err != nil { + return err + } + defer func() { + if err := server9p.Stop(); err != nil { + logging.Warnf("Error stopping 9p server: %v", err) + } + }() + go func() { + if err := server9p.WaitForError(); err != nil { + logging.Errorf("9p server error: %v", err) + } + }() + } + startupDone() if logging.IsDebug() { diff --git a/pkg/crc/constants/constants.go b/pkg/crc/constants/constants.go index b2cc95feca..c0dce0f84f 100644 --- a/pkg/crc/constants/constants.go +++ b/pkg/crc/constants/constants.go @@ -55,6 +55,9 @@ const ( OpenShiftIngressHTTPSPort = 443 BackgroundLauncherExecutable = "crc-background-launcher.exe" + + Plan9Msize = 1024 * 1024 + Plan9TcpPort = 564 ) var adminHelperExecutableForOs = map[string]string{ diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go index 58ee542a6b..b6aada9517 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,16 @@ 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 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..f808d6c546 --- /dev/null +++ b/pkg/fileserver/fs9p/server.go @@ -0,0 +1,88 @@ +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)) + // set size to 1 making channel buffered to prevent proto.Serve blocking + errChan := make(chan error, 1) + + 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 +} From 659a961647062561e93a657e1ac2af0229c53986 Mon Sep 17 00:00:00 2001 From: Matus Skvarla Date: Wed, 1 Oct 2025 16:56:56 +0200 Subject: [PATCH 4/4] 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 }