Skip to content

Commit 61491c2

Browse files
committed
9p: initial version
1 parent 4d0431d commit 61491c2

File tree

8 files changed

+237
-0
lines changed

8 files changed

+237
-0
lines changed

cmd/crc/cmd/daemon.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os"
1212
"os/signal"
1313
"regexp"
14+
"runtime"
1415
"syscall"
1516
"time"
1617

@@ -24,6 +25,7 @@ import (
2425
"github.com/crc-org/crc/v2/pkg/crc/constants"
2526
"github.com/crc-org/crc/v2/pkg/crc/daemonclient"
2627
"github.com/crc-org/crc/v2/pkg/crc/logging"
28+
"github.com/crc-org/crc/v2/pkg/fileserver/fs9p"
2729
"github.com/crc-org/machine/libmachine/drivers"
2830
"github.com/docker/go-units"
2931
"github.com/gorilla/handlers"
@@ -248,6 +250,21 @@ func run(configuration *types.Configuration) error {
248250
}
249251
}()
250252

253+
// 9p home directory sharing
254+
if runtime.GOOS == "windows" {
255+
if _, err := fs9p.StartHvsockShares([]fs9p.HvsockMount9p{{Path: constants.GetHomeDir(), HvsockGUID: constants.Plan9HvsockGUID}}); err != nil {
256+
logging.Warnf("Failed to start 9p file server on hvsock: %v", err)
257+
logging.Warnf("Falling back to 9p over TCP")
258+
listener9p, err := vn.Listen("tcp", net.JoinHostPort(configuration.GatewayIP, fmt.Sprintf("%d", constants.Plan9TcpPort)))
259+
if err != nil {
260+
return err
261+
}
262+
if _, err := fs9p.StartShares([]fs9p.Mount9p{{Listener: listener9p, Path: constants.GetHomeDir()}}); err != nil {
263+
return err
264+
}
265+
}
266+
}
267+
251268
startupDone()
252269

253270
if logging.IsDebug() {

packaging/windows/product.wxs.template

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@
7272
<RegistryValue Type="string" Name="ElementName" Value="gvisor-tap-vsock" KeyPath="yes"/>
7373
</RegistryKey>
7474
</Component>
75+
<Component Id="Hvsock9pRegistryEntry" Guid="*">
76+
<RegistryKey Root="HKLM" Key="Software\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices\00009000-FACB-11E6-BD58-64006A7986D3">
77+
<RegistryValue Type="string" Name="ElementName" Value="fs9p-hvsock" KeyPath="yes"/>
78+
</RegistryKey>
79+
</Component>
7580
</Directory>
7681
</Directory>
7782
</Directory>
@@ -130,6 +135,7 @@
130135
<ComponentRef Id="AddToPath"/>
131136
<ComponentRef Id="AddUserToCrcUsers" />
132137
<ComponentRef Id="VsockRegistryEntry" />
138+
<ComponentRef Id="Hvsock9pRegistryEntry" />
133139
</Feature>
134140
<UI>
135141
<UIRef Id="WixUI_ErrorProgressText"/>

pkg/crc/constants/constants.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ const (
5555
OpenShiftIngressHTTPSPort = 443
5656

5757
BackgroundLauncherExecutable = "crc-background-launcher.exe"
58+
59+
Plan9Msize = 1024 * 1024
60+
Plan9TcpPort = 564
61+
Plan9HvsockGUID = "00009000-FACB-11E6-BD58-64006A7986D3"
62+
Plan9HvsockPort = 36864
5863
)
5964

6065
var adminHelperExecutableForOs = map[string]string{

pkg/crc/machine/start.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ func configureSharedDirs(vm *virtualMachine, sshRunner *crcssh.Runner) error {
248248
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 {
249249
return err
250250
}
251+
251252
case "cifs":
252253
smbUncPath := fmt.Sprintf("//%s/%s", hostVirtualIP, mount.Tag)
253254
if _, _, err := sshRunner.RunPrivate("sudo", "mount", "-o", fmt.Sprintf("rw,uid=core,gid=core,username='%s',password='%s'", mount.Username, mount.Password), "-t", mount.Type, smbUncPath, mount.Target); err != nil {
@@ -257,6 +258,20 @@ func configureSharedDirs(vm *virtualMachine, sshRunner *crcssh.Runner) error {
257258
}
258259
return fmt.Errorf("Failed to mount CIFS/SMB share '%s' please make sure configured password is correct: %w", mount.Tag, err)
259260
}
261+
262+
case "9p":
263+
// change owner to core user to allow mounting to it as a non-root user
264+
if _, _, err := sshRunner.RunPrivileged("Changing owner of mount directory", "chown core:core", mount.Target); err != nil {
265+
return err
266+
}
267+
if _, _, err := sshRunner.Run("9pfs -V -p", fmt.Sprintf("%d", constants.Plan9HvsockPort), mount.Target); err != nil {
268+
logging.Warnf("Failed to connect to 9p server over hvsock: %v", err)
269+
logging.Warnf("Falling back to 9p over TCP")
270+
if _, _, err := sshRunner.Run("9pfs 192.168.127.1", mount.Target); err != nil {
271+
return err
272+
}
273+
}
274+
260275
default:
261276
return fmt.Errorf("Unknown Shared dir type requested: %s", mount.Type)
262277
}

pkg/fileserver/fs9p/server.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package fs9p
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/DeedleFake/p9"
10+
"github.com/DeedleFake/p9/proto"
11+
"github.com/crc-org/crc/v2/pkg/crc/constants"
12+
"github.com/sirupsen/logrus"
13+
)
14+
15+
type Server struct {
16+
// Listener this server is bound to
17+
Listener net.Listener
18+
// Plan9 Filesystem type that holds the exposed directory
19+
Filesystem p9.FileSystem
20+
// Directory this server exposes
21+
ExposedDir string
22+
// Errors from the server being started will come out here
23+
ErrChan chan error
24+
}
25+
26+
// New9pServer exposes a single directory (and all children) via the given net.Listener
27+
// and returns the server struct.
28+
// Directory given must be an absolute path and must exist.
29+
func New9pServer(listener net.Listener, exposeDir string) (*Server, error) {
30+
// Verify that exposeDir makes sense.
31+
if !filepath.IsAbs(exposeDir) {
32+
return nil, fmt.Errorf("path to expose to machine must be absolute: %s", exposeDir)
33+
}
34+
stat, err := os.Stat(exposeDir)
35+
if err != nil {
36+
return nil, fmt.Errorf("cannot stat path to expose to machine: %w", err)
37+
}
38+
if !stat.IsDir() {
39+
return nil, fmt.Errorf("path to expose to machine must be a directory: %s", exposeDir)
40+
}
41+
42+
fs := p9.FileSystem(p9.Dir(exposeDir))
43+
errChan := make(chan error)
44+
45+
toReturn := new(Server)
46+
toReturn.Listener = listener
47+
toReturn.Filesystem = fs
48+
toReturn.ExposedDir = exposeDir
49+
toReturn.ErrChan = errChan
50+
51+
return toReturn, nil
52+
}
53+
54+
// Start a server created by New9pServer.
55+
func (s *Server) Start() error {
56+
go func() {
57+
s.ErrChan <- proto.Serve(s.Listener, p9.Proto(), p9.FSConnHandler(s.Filesystem, constants.Plan9Msize))
58+
close(s.ErrChan)
59+
}()
60+
61+
// Just before returning, check to see if we got an error off server startup.
62+
select {
63+
case err := <-s.ErrChan:
64+
return fmt.Errorf("starting 9p server: %w", err)
65+
default:
66+
logrus.Infof("Successfully started 9p server on %s for directory %s", s.Listener.Addr().String(), s.ExposedDir)
67+
return nil
68+
}
69+
}
70+
71+
// Stop a running server.
72+
// Please note that this does *BAD THINGS* to clients if they are still running
73+
// when the server stops. Processes get stuck in I/O deep sleep and zombify, and
74+
// nothing I do other than restarting the VM can remove the zombies.
75+
func (s *Server) Stop() error {
76+
if err := s.Listener.Close(); err != nil {
77+
return err
78+
}
79+
logrus.Infof("Successfully stopped 9p server for directory %s", s.ExposedDir)
80+
return nil
81+
}
82+
83+
// WaitForError from a running server.
84+
func (s *Server) WaitForError() error {
85+
err := <-s.ErrChan
86+
return err
87+
}

pkg/fileserver/fs9p/shares.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package fs9p
2+
3+
import (
4+
"fmt"
5+
"net"
6+
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
// Mount9p represents an exposed directory path with the listener
11+
// the 9p server is bound to
12+
type Mount9p struct {
13+
Path string
14+
Listener net.Listener
15+
}
16+
17+
// HvsockMount9p represents an exposed directory path with the hvsock GUID
18+
// the 9p server is bound to
19+
type HvsockMount9p struct {
20+
Path string
21+
HvsockGUID string
22+
}
23+
24+
// StartShares starts a new 9p server for each of the supplied mounts.
25+
func StartShares(mounts []Mount9p) (servers []*Server, defErr error) {
26+
servers9p := []*Server{}
27+
for _, m := range mounts {
28+
server, err := New9pServer(m.Listener, m.Path)
29+
if err != nil {
30+
return nil, fmt.Errorf("serving directory %s on %s: %w", m.Path, m.Listener.Addr().String(), err)
31+
}
32+
33+
err = server.Start()
34+
if err != nil {
35+
return nil, fmt.Errorf("starting 9p server for directory %s: %w", m.Path, err)
36+
}
37+
38+
servers9p = append(servers9p, server)
39+
40+
defer func() {
41+
if defErr != nil {
42+
if err := server.Stop(); err != nil {
43+
logrus.Errorf("Error stopping 9p server: %v", err)
44+
}
45+
}
46+
}()
47+
48+
serverDir := m.Path
49+
50+
go func() {
51+
if err := server.WaitForError(); err != nil {
52+
logrus.Errorf("Error from 9p server on %s for %s: %v", m.Listener.Addr().String(), serverDir, err)
53+
} else {
54+
// We do not expect server exits - this should run until the program exits.
55+
logrus.Warnf("9p server on %s for %s exited without error", m.Listener.Addr().String(), serverDir)
56+
}
57+
}()
58+
}
59+
60+
return servers9p, nil
61+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !windows
2+
3+
package fs9p
4+
5+
import (
6+
"fmt"
7+
"runtime"
8+
)
9+
10+
// StartHvsockShares is only supported on Windows
11+
func StartHvsockShares(mounts []HvsockMount9p) ([]*Server, error) {
12+
return nil, fmt.Errorf("StartHvsockShares() not implemented on %s", runtime.GOOS)
13+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package fs9p
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/linuxkit/virtsock/pkg/hvsock"
7+
"github.com/sirupsen/logrus"
8+
)
9+
10+
// StartHvsockShares starts serving the given shares on hvsocks instead of TCP sockets.
11+
// The hvsocks used must already be defined before StartHvsockShares is called.
12+
func StartHvsockShares(mounts []HvsockMount9p) ([]*Server, error) {
13+
mounts9p := []Mount9p{}
14+
for _, mount := range mounts {
15+
service, err := hvsock.GUIDFromString(mount.HvsockGUID)
16+
if err != nil {
17+
return nil, fmt.Errorf("parsing hvsock guid %s: %w", mount.HvsockGUID, err)
18+
}
19+
20+
listener, err := hvsock.Listen(hvsock.Addr{
21+
VMID: hvsock.GUIDWildcard,
22+
ServiceID: service,
23+
})
24+
if err != nil {
25+
return nil, fmt.Errorf("retrieving listener for hvsock %s: %w", mount.HvsockGUID, err)
26+
}
27+
28+
logrus.Debugf("Going to serve directory %s on hvsock %s", mount.Path, mount.HvsockGUID)
29+
mounts9p = append(mounts9p, Mount9p{Path: mount.Path, Listener: listener})
30+
}
31+
32+
return StartShares(mounts9p)
33+
}

0 commit comments

Comments
 (0)