252 lines
7.9 KiB
Go
252 lines
7.9 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 gofer
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"gvisor.googlesource.com/gvisor/pkg/p9"
|
|
"gvisor.googlesource.com/gvisor/pkg/refs"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/context"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/device"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
|
|
"gvisor.googlesource.com/gvisor/pkg/sentry/fs/fsutil"
|
|
"gvisor.googlesource.com/gvisor/pkg/tcpip/transport/unix"
|
|
"gvisor.googlesource.com/gvisor/pkg/unet"
|
|
)
|
|
|
|
type endpointMap struct {
|
|
mu sync.RWMutex
|
|
m map[device.MultiDeviceKey]unix.BoundEndpoint
|
|
}
|
|
|
|
// add adds the endpoint to the map.
|
|
//
|
|
// Precondition: map must have been locked with 'lock'.
|
|
func (e *endpointMap) add(key device.MultiDeviceKey, ep unix.BoundEndpoint) {
|
|
e.m[key] = ep
|
|
}
|
|
|
|
// remove deletes the key from the map.
|
|
//
|
|
// Precondition: map must have been locked with 'lock'.
|
|
func (e *endpointMap) remove(key device.MultiDeviceKey) {
|
|
delete(e.m, key)
|
|
}
|
|
|
|
// lock blocks other addition and removal operations from happening while
|
|
// the backing file is being created or deleted. Returns a function that unlocks
|
|
// the endpoint map.
|
|
func (e *endpointMap) lock() func() {
|
|
e.mu.Lock()
|
|
return func() { e.mu.Unlock() }
|
|
}
|
|
|
|
func (e *endpointMap) get(key device.MultiDeviceKey) unix.BoundEndpoint {
|
|
e.mu.RLock()
|
|
ep := e.m[key]
|
|
e.mu.RUnlock()
|
|
return ep
|
|
}
|
|
|
|
// session holds state for each 9p session established during sys_mount.
|
|
type session struct {
|
|
refs.AtomicRefCount
|
|
|
|
// conn is a unet.Socket that wraps the readFD/writeFD mount option,
|
|
// see fs/gofer/fs.go.
|
|
conn *unet.Socket `state:"nosave"`
|
|
|
|
// msize is the value of the msize mount option, see fs/gofer/fs.go.
|
|
msize uint32 `state:"wait"`
|
|
|
|
// version is the value of the version mount option, see fs/gofer/fs.go.
|
|
version string `state:"wait"`
|
|
|
|
// cachePolicy is the cache policy. It may be either cacheAll or cacheNone.
|
|
cachePolicy cachePolicy `state:"wait"`
|
|
|
|
// aname is the value of the aname mount option, see fs/gofer/fs.go.
|
|
aname string `state:"wait"`
|
|
|
|
// The client associated with this session. This will be initialized lazily.
|
|
client *p9.Client `state:"nosave"`
|
|
|
|
// The p9.File pointing to attachName via the client. This will be initialized
|
|
// lazily.
|
|
attach contextFile `state:"nosave"`
|
|
|
|
// Flags provided to the mount.
|
|
superBlockFlags fs.MountSourceFlags `state:"wait"`
|
|
|
|
// connID is a unique identifier for the session connection.
|
|
connID string `state:"wait"`
|
|
|
|
// inodeMappings contains mappings of fs.Inodes associated with this session
|
|
// to paths relative to the attach point, where inodeMappings is keyed by
|
|
// Inode.StableAttr.InodeID.
|
|
inodeMappings map[uint64]string `state:"wait"`
|
|
|
|
// mounter is the EUID/EGID that mounted this file system.
|
|
mounter fs.FileOwner `state:"wait"`
|
|
|
|
// endpoints is used to map inodes that represent socket files to their
|
|
// corresponding endpoint. Socket files are created as regular files in the
|
|
// gofer and their presence in this map indicate that they should indeed be
|
|
// socket files. This allows unix domain sockets to be used with paths that
|
|
// belong to a gofer.
|
|
//
|
|
// TODO: there are few possible races with someone stat'ing the
|
|
// file and another deleting it concurrently, where the file will not be
|
|
// reported as socket file.
|
|
endpoints *endpointMap `state:"wait"`
|
|
}
|
|
|
|
// Destroy tears down the session.
|
|
func (s *session) Destroy() {
|
|
s.conn.Close()
|
|
}
|
|
|
|
// Revalidate returns true if the cache policy is does not allow for VFS caching.
|
|
func (s *session) Revalidate(*fs.Dirent) bool {
|
|
return s.cachePolicy == cacheNone
|
|
}
|
|
|
|
// TakeRefs takes an extra reference on dirent if possible.
|
|
func (s *session) Keep(dirent *fs.Dirent) bool {
|
|
// NOTE: Only cache files and directories.
|
|
sattr := dirent.Inode.StableAttr
|
|
return s.cachePolicy != cacheNone && (fs.IsFile(sattr) || fs.IsDir(sattr))
|
|
}
|
|
|
|
// ResetInodeMappings implements fs.MountSourceOperations.ResetInodeMappings.
|
|
func (s *session) ResetInodeMappings() {
|
|
s.inodeMappings = make(map[uint64]string)
|
|
}
|
|
|
|
// SaveInodeMapping implements fs.MountSourceOperations.SaveInodeMapping.
|
|
func (s *session) SaveInodeMapping(inode *fs.Inode, path string) {
|
|
// This is very unintuitive. We *CANNOT* trust the inode's StableAttrs,
|
|
// because overlay copyUp may have changed them out from under us.
|
|
// So much for "immutable".
|
|
sattr := inode.InodeOperations.(*inodeOperations).fileState.sattr
|
|
s.inodeMappings[sattr.InodeID] = path
|
|
}
|
|
|
|
// newInodeOperations creates a new 9p fs.InodeOperations backed by a p9.File and attributes
|
|
// (p9.QID, p9.AttrMask, p9.Attr).
|
|
func newInodeOperations(ctx context.Context, s *session, file contextFile, qid p9.QID, valid p9.AttrMask, attr p9.Attr) (fs.StableAttr, *inodeOperations) {
|
|
deviceKey := device.MultiDeviceKey{
|
|
Device: attr.RDev,
|
|
SecondaryDevice: s.connID,
|
|
Inode: qid.Path,
|
|
}
|
|
|
|
sattr := fs.StableAttr{
|
|
Type: ntype(attr),
|
|
DeviceID: goferDevice.DeviceID(),
|
|
InodeID: goferDevice.Map(deviceKey),
|
|
BlockSize: bsize(attr),
|
|
}
|
|
|
|
if s.endpoints != nil {
|
|
// If unix sockets are allowed on this filesystem, check if this file is
|
|
// supposed to be a socket file.
|
|
if s.endpoints.get(deviceKey) != nil {
|
|
sattr.Type = fs.Socket
|
|
}
|
|
}
|
|
|
|
fileState := &inodeFileState{
|
|
s: s,
|
|
file: file,
|
|
sattr: sattr,
|
|
key: deviceKey,
|
|
}
|
|
|
|
uattr := unstable(ctx, valid, attr, s.mounter, s.client)
|
|
return sattr, &inodeOperations{
|
|
fileState: fileState,
|
|
cachingInodeOps: fsutil.NewCachingInodeOperations(ctx, fileState, uattr, s.superBlockFlags.ForcePageCache),
|
|
}
|
|
}
|
|
|
|
// Root returns the root of a 9p mount. This mount is bound to a 9p server
|
|
// based on conn. Otherwise configuration parameters are:
|
|
//
|
|
// * dev: connection id
|
|
// * filesystem: the filesystem backing the mount
|
|
// * superBlockFlags: the mount flags describing general mount options
|
|
// * opts: parsed 9p mount options
|
|
func Root(ctx context.Context, dev string, filesystem fs.Filesystem, superBlockFlags fs.MountSourceFlags, o opts) (*fs.Inode, error) {
|
|
// The mounting EUID/EGID will be cached by this file system. This will
|
|
// be used to assign ownership to files that the Gofer owns.
|
|
mounter := fs.FileOwnerFromContext(ctx)
|
|
|
|
conn, err := unet.NewSocket(o.fd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Construct the session.
|
|
s := &session{
|
|
connID: dev,
|
|
conn: conn,
|
|
msize: o.msize,
|
|
version: o.version,
|
|
cachePolicy: o.policy,
|
|
aname: o.aname,
|
|
superBlockFlags: superBlockFlags,
|
|
mounter: mounter,
|
|
}
|
|
|
|
if o.privateunixsocket {
|
|
s.endpoints = &endpointMap{m: make(map[device.MultiDeviceKey]unix.BoundEndpoint)}
|
|
}
|
|
|
|
// Construct the MountSource with the session and superBlockFlags.
|
|
m := fs.NewMountSource(s, filesystem, superBlockFlags)
|
|
|
|
// Send the Tversion request.
|
|
s.client, err = p9.NewClient(s.conn, s.msize, s.version)
|
|
if err != nil {
|
|
// Drop our reference on the session, it needs to be torn down.
|
|
s.DecRef()
|
|
return nil, err
|
|
}
|
|
|
|
// Notify that we're about to call the Gofer and block.
|
|
ctx.UninterruptibleSleepStart(false)
|
|
// Send the Tattach request.
|
|
s.attach.file, err = s.client.Attach(s.aname)
|
|
ctx.UninterruptibleSleepFinish(false)
|
|
if err != nil {
|
|
// Same as above.
|
|
s.DecRef()
|
|
return nil, err
|
|
}
|
|
|
|
qid, valid, attr, err := s.attach.getAttr(ctx, p9.AttrMaskAll())
|
|
if err != nil {
|
|
s.attach.close(ctx)
|
|
// Same as above, but after we execute the Close request.
|
|
s.DecRef()
|
|
return nil, err
|
|
}
|
|
|
|
sattr, iops := newInodeOperations(ctx, s, s.attach, qid, valid, attr)
|
|
return fs.NewInode(iops, m, sattr), nil
|
|
}
|