gvisor/runsc/fsgofer/control.go

204 lines
6.0 KiB
Go

// 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)
}