Skip to content

Commit 6c80c48

Browse files
committed
9p: initial version
1 parent 35486f0 commit 6c80c48

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

cmd/crc/cmd/daemon.go

Lines changed: 27 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,31 @@ func run(configuration *types.Configuration) error {
248250
}
249251
}()
250252

253+
// 9p home directory sharing
254+
if runtime.GOOS == "windows" && config.Get(crcConfig.EnableSharedDirs).AsBool() {
255+
listener9p, err := vn.Listen("tcp", net.JoinHostPort(configuration.GatewayIP, fmt.Sprintf("%d", constants.Plan9TcpPort)))
256+
if err != nil {
257+
return err
258+
}
259+
server9p, err := fs9p.New9pServer(listener9p, constants.GetHomeDir())
260+
if err != nil {
261+
return err
262+
}
263+
if err := server9p.Start(); err != nil {
264+
return err
265+
}
266+
defer func() {
267+
if err := server9p.Stop(); err != nil {
268+
logging.Warnf("error stopping 9p server: %v", err)
269+
}
270+
}()
271+
go func() {
272+
if err := server9p.WaitForError(); err != nil {
273+
logging.Errorf("9p server error: %v", err)
274+
}
275+
}()
276+
}
277+
251278
startupDone()
252279

253280
if logging.IsDebug() {

pkg/crc/constants/constants.go

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

5757
BackgroundLauncherExecutable = "crc-background-launcher.exe"
58+
59+
Plan9Msize = 1024 * 1024
60+
Plan9TcpPort = 564
5861
)
5962

6063
var adminHelperExecutableForOs = map[string]string{

pkg/crc/machine/start.go

Lines changed: 11 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,16 @@ 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 192.168.127.1", mount.Target); err != nil {
268+
return err
269+
}
270+
260271
default:
261272
return fmt.Errorf("Unknown Shared dir type requested: %s", mount.Type)
262273
}

pkg/fileserver/fs9p/server.go

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

0 commit comments

Comments
 (0)