summaryrefslogtreecommitdiffhomepage
path: root/runsc/fsgofer/control.go
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/fsgofer/control.go')
-rw-r--r--runsc/fsgofer/control.go203
1 files changed, 203 insertions, 0 deletions
diff --git a/runsc/fsgofer/control.go b/runsc/fsgofer/control.go
new file mode 100644
index 000000000..8ce8ee8a0
--- /dev/null
+++ b/runsc/fsgofer/control.go
@@ -0,0 +1,203 @@
+// Copyright 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fsgofer
+
+import (
+ "fmt"
+ "path/filepath"
+ "sync"
+
+ "gvisor.googlesource.com/gvisor/pkg/control/server"
+ "gvisor.googlesource.com/gvisor/pkg/log"
+ "gvisor.googlesource.com/gvisor/pkg/p9"
+ "gvisor.googlesource.com/gvisor/pkg/unet"
+ "gvisor.googlesource.com/gvisor/pkg/urpc"
+)
+
+// Controller manages the fsgofer's control server.
+type Controller struct {
+ // api holds the control server's URPC endpoints.
+ api api
+
+ // srv is the control server.
+ srv *server.Server
+}
+
+// NewController creates a new Controller and starts it listenting
+func NewController(fd int, rootBundleDir string) (*Controller, error) {
+ if !filepath.IsAbs(rootBundleDir) {
+ return nil, fmt.Errorf("NewController should receive an absolute bundle dir path, but got %q", rootBundleDir)
+ }
+
+ srv, err := server.CreateFromFD(fd)
+ if err != nil {
+ return nil, err
+ }
+
+ cr := &Controller{srv: srv}
+ cr.api.rootBundleDir = rootBundleDir
+ cr.api.bundleDirs = make(map[string]string)
+ srv.Register(&cr.api)
+
+ if err := srv.StartServing(); err != nil {
+ return nil, err
+ }
+
+ return cr, nil
+}
+
+// Wait waits for all the p9 servers to finish, then shuts down the control
+// server.
+func (cr *Controller) Wait() {
+ cr.api.p9wg.Wait()
+ cr.srv.Stop()
+ log.Infof("All 9P servers exited.")
+}
+
+// Serve starts serving each Attacher in ats via its corresponding file
+// descriptor in ioFDs.
+func (cr *Controller) Serve(ats []p9.Attacher, ioFDs []int) error {
+ if len(ats) != len(ioFDs) {
+ return fmt.Errorf("number of attach points does not match the number of IO FDs (%d and %d)", len(ats), len(ioFDs))
+ }
+ for i, _ := range ats {
+ cr.api.serve(ats[i], ioFDs[i])
+ }
+ return nil
+}
+
+// api URPC methods.
+const (
+ // AddBundleDirs readies the gofer to serve from a new bundle
+ // directory. It should be called during runsc create.
+ AddBundleDirs = "api.AddBundleDirs"
+
+ // ServeDirectory serves a new directory via the fsgofer. It should be
+ // called during runsc start.
+ ServeDirectory = "api.ServeDirectory"
+)
+
+// API defines and implements the URPC endpoints for the gofer.
+type api struct {
+ // p9wg waits for all the goroutines serving the sentry via p9. When its
+ // counter is 0, the gofer is out of work and exits.
+ p9wg sync.WaitGroup
+
+ // bundleDirs maps from container ID to bundle directory for each
+ // container.
+ bundleDirs map[string]string
+
+ // rootBundleDir is the bundle directory of the root container.
+ rootBundleDir string
+}
+
+// AddBundleDirsRequest is the URPC argument to AddBundleDirs.
+type AddBundleDirsRequest struct {
+ // BundleDirs is a map of container IDs to bundle directories to add to
+ // the gofer.
+ BundleDirs map[string]string
+}
+
+// AddBundleDirsRequest adds bundle directories that for the gofer to serve.
+func (api *api) AddBundleDirs(req *AddBundleDirsRequest, _ *struct{}) error {
+ log.Debugf("fsgofer.AddBundleDirs")
+ for cid, bd := range req.BundleDirs {
+ if _, ok := api.bundleDirs[cid]; ok {
+ return fmt.Errorf("fsgofer already has a bundleDir for container %q", cid)
+ }
+ api.bundleDirs[cid] = bd
+ }
+ return nil
+}
+
+// ServeDirectoryRequest is the URPC argument to ServeDirectory.
+type ServeDirectoryRequest struct {
+ // Dir is the absolute path to a directory to be served to the sentry.
+ Dir string
+
+ // IsReadOnly specifies whether the directory should be served in
+ // read-only mode.
+ IsReadOnly bool
+
+ // CID is the container ID of the container that needs to serve a
+ // directory.
+ CID string
+
+ // FilePayload contains the socket over which the sentry will request
+ // files from Dir.
+ urpc.FilePayload
+}
+
+// ServeDirectory begins serving a directory via a file descriptor for the
+// sentry. Directories must be added via AddBundleDirsRequest before
+// ServeDirectory is called.
+func (api *api) ServeDirectory(req *ServeDirectoryRequest, _ *struct{}) error {
+ log.Debugf("fsgofer.ServeDirectory: %+v", req)
+
+ if req.Dir == "" {
+ return fmt.Errorf("ServeDirectory should receive a directory argument, but was empty")
+ }
+ if req.CID == "" {
+ return fmt.Errorf("ServeDirectory should receive a CID argument, but was empty")
+ }
+ // Prevent CIDs containing ".." from confusing the sentry when creating
+ // /containers/<cid> directory.
+ // TODO: Once we have multiple independant roots, this
+ // check won't be necessary.
+ if filepath.Clean(req.CID) != req.CID {
+ return fmt.Errorf("container ID shouldn't contain directory traversals such as \"..\": %q", req.CID)
+ }
+ if nFiles := len(req.FilePayload.Files); nFiles != 1 {
+ return fmt.Errorf("ServeDirectory should receive 1 file descriptor, but got %d", nFiles)
+ }
+
+ bd, ok := api.bundleDirs[req.CID]
+ if !ok {
+ // If there's no entry in bundleDirs for the container ID, this
+ // is the root container.
+ bd = api.rootBundleDir
+ }
+
+ // Relative paths are served relative to the bundle directory.
+ absDir := req.Dir
+ if !filepath.IsAbs(absDir) {
+ absDir = filepath.Join(bd, req.Dir)
+ }
+
+ // Create the attach point and start serving.
+ at := NewAttachPoint(absDir, Config{
+ ROMount: req.IsReadOnly,
+ LazyOpenForWrite: true,
+ })
+ api.serve(at, int(req.FilePayload.Files[0].Fd()))
+
+ return nil
+}
+
+// serve begins serving a directory via a file descriptor.
+func (api *api) serve(at p9.Attacher, ioFD int) {
+ api.p9wg.Add(1)
+ go func(ioFD int, at p9.Attacher) {
+ socket, err := unet.NewSocket(ioFD)
+ if err != nil {
+ panic(fmt.Sprintf("err creating server on FD %d: %v", ioFD, err))
+ }
+ s := p9.NewServer(at)
+ if err := s.Handle(socket); err != nil {
+ panic(fmt.Sprintf("P9 server returned error. Gofer is shutting down. FD: %d, err: %v", ioFD, err))
+ }
+ api.p9wg.Done()
+ }(ioFD, at)
+}