summaryrefslogtreecommitdiffhomepage
path: root/runsc/fsgofer/control.go
blob: 8cb2f67acea97793346ae9045d8cfe2142629b4a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// 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"
	"os"
	"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. This takes ownership of the FDs 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], os.NewFile(uintptr(ioFDs[i]), "io fd"))
	}
	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, req.FilePayload.Files[0])

	return nil
}

// serve begins serving a directory via a file descriptor.
func (api *api) serve(at p9.Attacher, ioFile *os.File) {
	api.p9wg.Add(1)
	go func() {
		socket, err := unet.NewSocket(int(ioFile.Fd()))
		if err != nil {
			panic(fmt.Sprintf("err creating server on FD %d: %v", ioFile.Fd(), 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", ioFile.Fd(), err))
		}
		api.p9wg.Done()
	}()
}