mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add support for sentry internal pipe for gofer mounts
Internal pipes are supported similarly to how internal UDS is done. It is also controlled by the same flag. Fixes #1102 PiperOrigin-RevId: 293150045
This commit is contained in:
committed by
gVisor bot
parent
f37e913a35
commit
d7cd484091
@@ -9,6 +9,7 @@ go_library(
|
||||
"cache_policy.go",
|
||||
"context_file.go",
|
||||
"device.go",
|
||||
"fifo.go",
|
||||
"file.go",
|
||||
"file_state.go",
|
||||
"fs.go",
|
||||
@@ -38,6 +39,7 @@ go_library(
|
||||
"//pkg/sentry/fs/fsutil",
|
||||
"//pkg/sentry/fs/host",
|
||||
"//pkg/sentry/kernel/auth",
|
||||
"//pkg/sentry/kernel/pipe",
|
||||
"//pkg/sentry/kernel/time",
|
||||
"//pkg/sentry/memmap",
|
||||
"//pkg/sentry/socket/unix/transport",
|
||||
|
||||
@@ -127,6 +127,9 @@ func (cp cachePolicy) revalidate(ctx context.Context, name string, parent, child
|
||||
|
||||
childIops, ok := child.InodeOperations.(*inodeOperations)
|
||||
if !ok {
|
||||
if _, ok := child.InodeOperations.(*fifo); ok {
|
||||
return false
|
||||
}
|
||||
panic(fmt.Sprintf("revalidating inode operations of unknown type %T", child.InodeOperations))
|
||||
}
|
||||
parentIops, ok := parent.InodeOperations.(*inodeOperations)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 (
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type fifo struct {
|
||||
fs.InodeOperations
|
||||
fileIops *inodeOperations
|
||||
}
|
||||
|
||||
var _ fs.InodeOperations = (*fifo)(nil)
|
||||
|
||||
// Rename implements fs.InodeOperations. It forwards the call to the underlying
|
||||
// file inode to handle the file rename. Note that file key remains the same
|
||||
// after the rename to keep the endpoint mapping.
|
||||
func (i *fifo) Rename(ctx context.Context, inode *fs.Inode, oldParent *fs.Inode, oldName string, newParent *fs.Inode, newName string, replacement bool) error {
|
||||
return i.fileIops.Rename(ctx, inode, oldParent, oldName, newParent, newName, replacement)
|
||||
}
|
||||
|
||||
// StatFS implements fs.InodeOperations.
|
||||
func (i *fifo) StatFS(ctx context.Context) (fs.Info, error) {
|
||||
return i.fileIops.StatFS(ctx)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ func rootTest(t *testing.T, name string, cp cachePolicy, fn func(context.Context
|
||||
ctx := contexttest.Context(t)
|
||||
sattr, rootInodeOperations := newInodeOperations(ctx, s, contextFile{
|
||||
file: rootFile,
|
||||
}, root.QID, p9.AttrMaskAll(), root.Attr, false /* socket */)
|
||||
}, root.QID, p9.AttrMaskAll(), root.Attr)
|
||||
m := fs.NewMountSource(ctx, s, &filesystem{}, fs.MountSourceFlags{})
|
||||
rootInode := fs.NewInode(ctx, rootInodeOperations, m, sattr)
|
||||
|
||||
|
||||
+124
-61
@@ -23,14 +23,24 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/p9"
|
||||
"gvisor.dev/gvisor/pkg/sentry/device"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/pipe"
|
||||
"gvisor.dev/gvisor/pkg/sentry/socket/unix/transport"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// maxFilenameLen is the maximum length of a filename. This is dictated by 9P's
|
||||
// encoding of strings, which uses 2 bytes for the length prefix.
|
||||
const maxFilenameLen = (1 << 16) - 1
|
||||
|
||||
func changeType(mode p9.FileMode, newType p9.FileMode) p9.FileMode {
|
||||
if newType&^p9.FileModeMask != 0 {
|
||||
panic(fmt.Sprintf("newType contained more bits than just file mode: %x", newType))
|
||||
}
|
||||
clear := mode &^ p9.FileModeMask
|
||||
return clear | newType
|
||||
}
|
||||
|
||||
// Lookup loads an Inode at name into a Dirent based on the session's cache
|
||||
// policy.
|
||||
func (i *inodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string) (*fs.Dirent, error) {
|
||||
@@ -69,8 +79,25 @@ func (i *inodeOperations) Lookup(ctx context.Context, dir *fs.Inode, name string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if i.session().overrides != nil {
|
||||
// Check if file belongs to a internal named pipe. Note that it doesn't need
|
||||
// to check for sockets because it's done in newInodeOperations below.
|
||||
deviceKey := device.MultiDeviceKey{
|
||||
Device: p9attr.RDev,
|
||||
SecondaryDevice: i.session().connID,
|
||||
Inode: qids[0].Path,
|
||||
}
|
||||
unlock := i.session().overrides.lock()
|
||||
if pipeInode := i.session().overrides.getPipe(deviceKey); pipeInode != nil {
|
||||
unlock()
|
||||
pipeInode.IncRef()
|
||||
return fs.NewDirent(ctx, pipeInode, name), nil
|
||||
}
|
||||
unlock()
|
||||
}
|
||||
|
||||
// Construct the Inode operations.
|
||||
sattr, node := newInodeOperations(ctx, i.fileState.s, newFile, qids[0], mask, p9attr, false)
|
||||
sattr, node := newInodeOperations(ctx, i.fileState.s, newFile, qids[0], mask, p9attr)
|
||||
|
||||
// Construct a positive Dirent.
|
||||
return fs.NewDirent(ctx, fs.NewInode(ctx, node, dir.MountSource, sattr), name), nil
|
||||
@@ -138,7 +165,7 @@ func (i *inodeOperations) Create(ctx context.Context, dir *fs.Inode, name string
|
||||
qid := qids[0]
|
||||
|
||||
// Construct the InodeOperations.
|
||||
sattr, iops := newInodeOperations(ctx, i.fileState.s, unopened, qid, mask, p9attr, false)
|
||||
sattr, iops := newInodeOperations(ctx, i.fileState.s, unopened, qid, mask, p9attr)
|
||||
|
||||
// Construct the positive Dirent.
|
||||
d := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name)
|
||||
@@ -223,63 +250,22 @@ func (i *inodeOperations) Bind(ctx context.Context, dir *fs.Inode, name string,
|
||||
return nil, syserror.ENAMETOOLONG
|
||||
}
|
||||
|
||||
if i.session().endpoints == nil {
|
||||
if i.session().overrides == nil {
|
||||
return nil, syscall.EOPNOTSUPP
|
||||
}
|
||||
|
||||
// Create replaces the directory fid with the newly created/opened
|
||||
// file, so clone this directory so it doesn't change out from under
|
||||
// this node.
|
||||
_, newFile, err := i.fileState.file.walk(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We're not going to use newFile after return.
|
||||
defer newFile.close(ctx)
|
||||
|
||||
// Stabilize the endpoint map while creation is in progress.
|
||||
unlock := i.session().endpoints.lock()
|
||||
// Stabilize the override map while creation is in progress.
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
|
||||
// Create a regular file in the gofer and then mark it as a socket by
|
||||
// adding this inode key in the 'endpoints' map.
|
||||
owner := fs.FileOwnerFromContext(ctx)
|
||||
hostFile, err := newFile.create(ctx, name, p9.ReadWrite, p9.FileMode(perm.LinuxMode()), p9.UID(owner.UID), p9.GID(owner.GID))
|
||||
sattr, iops, err := i.createEndpointFile(ctx, dir, name, perm, p9.ModeSocket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We're not going to use this file.
|
||||
hostFile.Close()
|
||||
|
||||
i.touchModificationAndStatusChangeTime(ctx, dir)
|
||||
|
||||
// Get the attributes of the file to create inode key.
|
||||
qid, mask, attr, err := getattr(ctx, newFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := device.MultiDeviceKey{
|
||||
Device: attr.RDev,
|
||||
SecondaryDevice: i.session().connID,
|
||||
Inode: qid.Path,
|
||||
}
|
||||
|
||||
// Create child dirent.
|
||||
|
||||
// Get an unopened p9.File for the file we created so that it can be
|
||||
// cloned and re-opened multiple times after creation.
|
||||
_, unopened, err := i.fileState.file.walk(ctx, []string{name})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Construct the InodeOperations.
|
||||
sattr, iops := newInodeOperations(ctx, i.fileState.s, unopened, qid, mask, attr, true)
|
||||
|
||||
// Construct the positive Dirent.
|
||||
childDir := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name)
|
||||
i.session().endpoints.add(key, childDir, ep)
|
||||
i.session().overrides.addBoundEndpoint(iops.fileState.key, childDir, ep)
|
||||
return childDir, nil
|
||||
}
|
||||
|
||||
@@ -294,33 +280,110 @@ func (i *inodeOperations) CreateFifo(ctx context.Context, dir *fs.Inode, name st
|
||||
|
||||
// N.B. FIFOs use major/minor numbers 0.
|
||||
if _, err := i.fileState.file.mknod(ctx, name, mode, 0, 0, p9.UID(owner.UID), p9.GID(owner.GID)); err != nil {
|
||||
return err
|
||||
if i.session().overrides == nil || err != syscall.EPERM {
|
||||
return err
|
||||
}
|
||||
// If gofer doesn't support mknod, check if we can create an internal fifo.
|
||||
return i.createInternalFifo(ctx, dir, name, owner, perm)
|
||||
}
|
||||
|
||||
i.touchModificationAndStatusChangeTime(ctx, dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *inodeOperations) createInternalFifo(ctx context.Context, dir *fs.Inode, name string, owner fs.FileOwner, perm fs.FilePermissions) error {
|
||||
if i.session().overrides == nil {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
// Stabilize the override map while creation is in progress.
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
|
||||
sattr, fileOps, err := i.createEndpointFile(ctx, dir, name, perm, p9.ModeNamedPipe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// First create a pipe.
|
||||
p := pipe.NewPipe(true /* isNamed */, pipe.DefaultPipeSize, usermem.PageSize)
|
||||
|
||||
// Wrap the fileOps with our Fifo.
|
||||
iops := &fifo{
|
||||
InodeOperations: pipe.NewInodeOperations(ctx, perm, p),
|
||||
fileIops: fileOps,
|
||||
}
|
||||
inode := fs.NewInode(ctx, iops, dir.MountSource, sattr)
|
||||
|
||||
// Construct the positive Dirent.
|
||||
childDir := fs.NewDirent(ctx, fs.NewInode(ctx, iops, dir.MountSource, sattr), name)
|
||||
i.session().overrides.addPipe(fileOps.fileState.key, childDir, inode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Caller must hold Session.endpoint lock.
|
||||
func (i *inodeOperations) createEndpointFile(ctx context.Context, dir *fs.Inode, name string, perm fs.FilePermissions, fileType p9.FileMode) (fs.StableAttr, *inodeOperations, error) {
|
||||
_, dirClone, err := i.fileState.file.walk(ctx, nil)
|
||||
if err != nil {
|
||||
return fs.StableAttr{}, nil, err
|
||||
}
|
||||
// We're not going to use dirClone after return.
|
||||
defer dirClone.close(ctx)
|
||||
|
||||
// Create a regular file in the gofer and then mark it as a socket by
|
||||
// adding this inode key in the 'overrides' map.
|
||||
owner := fs.FileOwnerFromContext(ctx)
|
||||
hostFile, err := dirClone.create(ctx, name, p9.ReadWrite, p9.FileMode(perm.LinuxMode()), p9.UID(owner.UID), p9.GID(owner.GID))
|
||||
if err != nil {
|
||||
return fs.StableAttr{}, nil, err
|
||||
}
|
||||
// We're not going to use this file.
|
||||
hostFile.Close()
|
||||
|
||||
i.touchModificationAndStatusChangeTime(ctx, dir)
|
||||
|
||||
// Get the attributes of the file to create inode key.
|
||||
qid, mask, attr, err := getattr(ctx, dirClone)
|
||||
if err != nil {
|
||||
return fs.StableAttr{}, nil, err
|
||||
}
|
||||
|
||||
// Get an unopened p9.File for the file we created so that it can be
|
||||
// cloned and re-opened multiple times after creation.
|
||||
_, unopened, err := i.fileState.file.walk(ctx, []string{name})
|
||||
if err != nil {
|
||||
return fs.StableAttr{}, nil, err
|
||||
}
|
||||
|
||||
// Construct new inode with file type overridden.
|
||||
attr.Mode = changeType(attr.Mode, fileType)
|
||||
sattr, iops := newInodeOperations(ctx, i.fileState.s, unopened, qid, mask, attr)
|
||||
return sattr, iops, nil
|
||||
}
|
||||
|
||||
// Remove implements InodeOperations.Remove.
|
||||
func (i *inodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string) error {
|
||||
if len(name) > maxFilenameLen {
|
||||
return syserror.ENAMETOOLONG
|
||||
}
|
||||
|
||||
var key device.MultiDeviceKey
|
||||
removeSocket := false
|
||||
if i.session().endpoints != nil {
|
||||
// Find out if file being deleted is a socket that needs to be
|
||||
var key *device.MultiDeviceKey
|
||||
if i.session().overrides != nil {
|
||||
// Find out if file being deleted is a socket or pipe that needs to be
|
||||
// removed from endpoint map.
|
||||
if d, err := i.Lookup(ctx, dir, name); err == nil {
|
||||
defer d.DecRef()
|
||||
if fs.IsSocket(d.Inode.StableAttr) {
|
||||
child := d.Inode.InodeOperations.(*inodeOperations)
|
||||
key = child.fileState.key
|
||||
removeSocket = true
|
||||
|
||||
// Stabilize the endpoint map while deletion is in progress.
|
||||
unlock := i.session().endpoints.lock()
|
||||
if fs.IsSocket(d.Inode.StableAttr) || fs.IsPipe(d.Inode.StableAttr) {
|
||||
switch iops := d.Inode.InodeOperations.(type) {
|
||||
case *inodeOperations:
|
||||
key = &iops.fileState.key
|
||||
case *fifo:
|
||||
key = &iops.fileIops.fileState.key
|
||||
}
|
||||
|
||||
// Stabilize the override map while deletion is in progress.
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
}
|
||||
}
|
||||
@@ -329,8 +392,8 @@ func (i *inodeOperations) Remove(ctx context.Context, dir *fs.Inode, name string
|
||||
if err := i.fileState.file.unlinkAt(ctx, name, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if removeSocket {
|
||||
i.session().endpoints.remove(key)
|
||||
if key != nil {
|
||||
i.session().overrides.remove(*key)
|
||||
}
|
||||
i.touchModificationAndStatusChangeTime(ctx, dir)
|
||||
|
||||
|
||||
+118
-73
@@ -33,60 +33,107 @@ import (
|
||||
var DefaultDirentCacheSize uint64 = fs.DefaultDirentCacheSize
|
||||
|
||||
// +stateify savable
|
||||
type endpointMaps struct {
|
||||
// mu protexts the direntMap, the keyMap, and the pathMap below.
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
type overrideInfo struct {
|
||||
dirent *fs.Dirent
|
||||
|
||||
// direntMap links sockets to their dirents.
|
||||
// It is filled concurrently with the keyMap and is stored upon save.
|
||||
// Before saving, this map is used to populate the pathMap.
|
||||
direntMap map[transport.BoundEndpoint]*fs.Dirent
|
||||
// endpoint is set when dirent points to a socket. inode must not be set.
|
||||
endpoint transport.BoundEndpoint
|
||||
|
||||
// keyMap links MultiDeviceKeys (containing inode IDs) to their sockets.
|
||||
// It is not stored during save because the inode ID may change upon restore.
|
||||
keyMap map[device.MultiDeviceKey]transport.BoundEndpoint `state:"nosave"`
|
||||
|
||||
// pathMap links the sockets to their paths.
|
||||
// It is filled before saving from the direntMap and is stored upon save.
|
||||
// Upon restore, this map is used to re-populate the keyMap.
|
||||
pathMap map[transport.BoundEndpoint]string
|
||||
// inode is set when dirent points to a pipe. endpoint must not be set.
|
||||
inode *fs.Inode
|
||||
}
|
||||
|
||||
// add adds the endpoint to the maps.
|
||||
func (l *overrideInfo) inodeType() fs.InodeType {
|
||||
switch {
|
||||
case l.endpoint != nil:
|
||||
return fs.Socket
|
||||
case l.inode != nil:
|
||||
return fs.Pipe
|
||||
}
|
||||
panic("endpoint or node must be set")
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type overrideMaps struct {
|
||||
// mu protexts the keyMap, and the pathMap below.
|
||||
mu sync.RWMutex `state:"nosave"`
|
||||
|
||||
// keyMap links MultiDeviceKeys (containing inode IDs) to their sockets/pipes.
|
||||
// It is not stored during save because the inode ID may change upon restore.
|
||||
keyMap map[device.MultiDeviceKey]*overrideInfo `state:"nosave"`
|
||||
|
||||
// pathMap links the sockets/pipes to their paths.
|
||||
// It is filled before saving from the direntMap and is stored upon save.
|
||||
// Upon restore, this map is used to re-populate the keyMap.
|
||||
pathMap map[*overrideInfo]string
|
||||
}
|
||||
|
||||
// addBoundEndpoint adds the bound endpoint to the map.
|
||||
// A reference is taken on the dirent argument.
|
||||
//
|
||||
// Precondition: maps must have been locked with 'lock'.
|
||||
func (e *endpointMaps) add(key device.MultiDeviceKey, d *fs.Dirent, ep transport.BoundEndpoint) {
|
||||
e.keyMap[key] = ep
|
||||
func (e *overrideMaps) addBoundEndpoint(key device.MultiDeviceKey, d *fs.Dirent, ep transport.BoundEndpoint) {
|
||||
d.IncRef()
|
||||
e.direntMap[ep] = d
|
||||
e.keyMap[key] = &overrideInfo{dirent: d, endpoint: ep}
|
||||
}
|
||||
|
||||
// addPipe adds the pipe inode to the map.
|
||||
// A reference is taken on the dirent argument.
|
||||
//
|
||||
// Precondition: maps must have been locked with 'lock'.
|
||||
func (e *overrideMaps) addPipe(key device.MultiDeviceKey, d *fs.Dirent, inode *fs.Inode) {
|
||||
d.IncRef()
|
||||
e.keyMap[key] = &overrideInfo{dirent: d, inode: inode}
|
||||
}
|
||||
|
||||
// remove deletes the key from the maps.
|
||||
//
|
||||
// Precondition: maps must have been locked with 'lock'.
|
||||
func (e *endpointMaps) remove(key device.MultiDeviceKey) {
|
||||
endpoint := e.get(key)
|
||||
func (e *overrideMaps) remove(key device.MultiDeviceKey) {
|
||||
endpoint := e.keyMap[key]
|
||||
delete(e.keyMap, key)
|
||||
|
||||
d := e.direntMap[endpoint]
|
||||
d.DecRef()
|
||||
delete(e.direntMap, endpoint)
|
||||
endpoint.dirent.DecRef()
|
||||
}
|
||||
|
||||
// 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 *endpointMaps) lock() func() {
|
||||
func (e *overrideMaps) lock() func() {
|
||||
e.mu.Lock()
|
||||
return func() { e.mu.Unlock() }
|
||||
}
|
||||
|
||||
// get returns the endpoint mapped to the given key.
|
||||
// getBoundEndpoint returns the bound endpoint mapped to the given key.
|
||||
//
|
||||
// Precondition: maps must have been locked for reading.
|
||||
func (e *endpointMaps) get(key device.MultiDeviceKey) transport.BoundEndpoint {
|
||||
return e.keyMap[key]
|
||||
// Precondition: maps must have been locked.
|
||||
func (e *overrideMaps) getBoundEndpoint(key device.MultiDeviceKey) transport.BoundEndpoint {
|
||||
if v := e.keyMap[key]; v != nil {
|
||||
return v.endpoint
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPipe returns the pipe inode mapped to the given key.
|
||||
//
|
||||
// Precondition: maps must have been locked.
|
||||
func (e *overrideMaps) getPipe(key device.MultiDeviceKey) *fs.Inode {
|
||||
if v := e.keyMap[key]; v != nil {
|
||||
return v.inode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getType returns the inode type if there is a corresponding endpoint for the
|
||||
// given key. Returns false otherwise.
|
||||
func (e *overrideMaps) getType(key device.MultiDeviceKey) (fs.InodeType, bool) {
|
||||
e.mu.Lock()
|
||||
v := e.keyMap[key]
|
||||
e.mu.Unlock()
|
||||
|
||||
if v != nil {
|
||||
return v.inodeType(), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// session holds state for each 9p session established during sys_mount.
|
||||
@@ -137,16 +184,16 @@ type session struct {
|
||||
// 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.
|
||||
// overrides is used to map inodes that represent socket/pipes files to their
|
||||
// corresponding endpoint/iops. These files are created as regular files in
|
||||
// the gofer and their presence in this map indicate that they should indeed
|
||||
// be socket/pipe files. This allows unix domain sockets and named pipes to
|
||||
// be used with paths that belong to a gofer.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/1200): 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 *endpointMaps `state:"wait"`
|
||||
overrides *overrideMaps `state:"wait"`
|
||||
}
|
||||
|
||||
// Destroy tears down the session.
|
||||
@@ -179,15 +226,21 @@ 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
|
||||
switch iops := inode.InodeOperations.(type) {
|
||||
case *inodeOperations:
|
||||
s.inodeMappings[iops.fileState.sattr.InodeID] = path
|
||||
case *fifo:
|
||||
s.inodeMappings[iops.fileIops.fileState.sattr.InodeID] = path
|
||||
default:
|
||||
panic(fmt.Sprintf("Invalid type: %T", iops))
|
||||
}
|
||||
}
|
||||
|
||||
// newInodeOperations creates a new 9p fs.InodeOperations backed by a p9.File and attributes
|
||||
// (p9.QID, p9.AttrMask, p9.Attr).
|
||||
// newInodeOperations creates a new 9p fs.InodeOperations backed by a p9.File
|
||||
// and attributes (p9.QID, p9.AttrMask, p9.Attr).
|
||||
//
|
||||
// Endpoints lock must not be held if socket == false.
|
||||
func newInodeOperations(ctx context.Context, s *session, file contextFile, qid p9.QID, valid p9.AttrMask, attr p9.Attr, socket bool) (fs.StableAttr, *inodeOperations) {
|
||||
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,
|
||||
@@ -201,17 +254,11 @@ func newInodeOperations(ctx context.Context, s *session, file contextFile, qid p
|
||||
BlockSize: bsize(attr),
|
||||
}
|
||||
|
||||
if s.endpoints != nil {
|
||||
if socket {
|
||||
sattr.Type = fs.Socket
|
||||
} else {
|
||||
// If unix sockets are allowed on this filesystem, check if this file is
|
||||
// supposed to be a socket file.
|
||||
unlock := s.endpoints.lock()
|
||||
if s.endpoints.get(deviceKey) != nil {
|
||||
sattr.Type = fs.Socket
|
||||
}
|
||||
unlock()
|
||||
if s.overrides != nil && sattr.Type == fs.RegularFile {
|
||||
// If overrides are allowed on this filesystem, check if this file is
|
||||
// supposed to be of a different type, e.g. socket.
|
||||
if t, ok := s.overrides.getType(deviceKey); ok {
|
||||
sattr.Type = t
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +314,7 @@ func Root(ctx context.Context, dev string, filesystem fs.Filesystem, superBlockF
|
||||
s.EnableLeakCheck("gofer.session")
|
||||
|
||||
if o.privateunixsocket {
|
||||
s.endpoints = newEndpointMaps()
|
||||
s.overrides = newOverrideMaps()
|
||||
}
|
||||
|
||||
// Construct the MountSource with the session and superBlockFlags.
|
||||
@@ -305,26 +352,24 @@ func Root(ctx context.Context, dev string, filesystem fs.Filesystem, superBlockF
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sattr, iops := newInodeOperations(ctx, &s, s.attach, qid, valid, attr, false)
|
||||
sattr, iops := newInodeOperations(ctx, &s, s.attach, qid, valid, attr)
|
||||
return fs.NewInode(ctx, iops, m, sattr), nil
|
||||
}
|
||||
|
||||
// newEndpointMaps creates a new endpointMaps.
|
||||
func newEndpointMaps() *endpointMaps {
|
||||
return &endpointMaps{
|
||||
direntMap: make(map[transport.BoundEndpoint]*fs.Dirent),
|
||||
keyMap: make(map[device.MultiDeviceKey]transport.BoundEndpoint),
|
||||
pathMap: make(map[transport.BoundEndpoint]string),
|
||||
// newOverrideMaps creates a new overrideMaps.
|
||||
func newOverrideMaps() *overrideMaps {
|
||||
return &overrideMaps{
|
||||
keyMap: make(map[device.MultiDeviceKey]*overrideInfo),
|
||||
pathMap: make(map[*overrideInfo]string),
|
||||
}
|
||||
}
|
||||
|
||||
// fillKeyMap populates key and dirent maps upon restore from saved
|
||||
// pathmap.
|
||||
// fillKeyMap populates key and dirent maps upon restore from saved pathmap.
|
||||
func (s *session) fillKeyMap(ctx context.Context) error {
|
||||
unlock := s.endpoints.lock()
|
||||
unlock := s.overrides.lock()
|
||||
defer unlock()
|
||||
|
||||
for ep, dirPath := range s.endpoints.pathMap {
|
||||
for ep, dirPath := range s.overrides.pathMap {
|
||||
_, file, err := s.attach.walk(ctx, splitAbsolutePath(dirPath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error filling endpointmaps, failed to walk to %q: %v", dirPath, err)
|
||||
@@ -341,25 +386,25 @@ func (s *session) fillKeyMap(ctx context.Context) error {
|
||||
Inode: qid.Path,
|
||||
}
|
||||
|
||||
s.endpoints.keyMap[key] = ep
|
||||
s.overrides.keyMap[key] = ep
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fillPathMap populates paths for endpoints from dirents in direntMap
|
||||
// fillPathMap populates paths for overrides from dirents in direntMap
|
||||
// before save.
|
||||
func (s *session) fillPathMap() error {
|
||||
unlock := s.endpoints.lock()
|
||||
unlock := s.overrides.lock()
|
||||
defer unlock()
|
||||
|
||||
for ep, dir := range s.endpoints.direntMap {
|
||||
mountRoot := dir.MountRoot()
|
||||
for _, endpoint := range s.overrides.keyMap {
|
||||
mountRoot := endpoint.dirent.MountRoot()
|
||||
defer mountRoot.DecRef()
|
||||
dirPath, _ := dir.FullName(mountRoot)
|
||||
dirPath, _ := endpoint.dirent.FullName(mountRoot)
|
||||
if dirPath == "" {
|
||||
return fmt.Errorf("error getting path from dirent")
|
||||
}
|
||||
s.endpoints.pathMap[ep] = dirPath
|
||||
s.overrides.pathMap[endpoint] = dirPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -368,7 +413,7 @@ func (s *session) fillPathMap() error {
|
||||
func (s *session) restoreEndpointMaps(ctx context.Context) error {
|
||||
// When restoring, only need to create the keyMap because the dirent and path
|
||||
// maps got stored through the save.
|
||||
s.endpoints.keyMap = make(map[device.MultiDeviceKey]transport.BoundEndpoint)
|
||||
s.overrides.keyMap = make(map[device.MultiDeviceKey]*overrideInfo)
|
||||
if err := s.fillKeyMap(ctx); err != nil {
|
||||
return fmt.Errorf("failed to insert sockets into endpoint map: %v", err)
|
||||
}
|
||||
@@ -376,6 +421,6 @@ func (s *session) restoreEndpointMaps(ctx context.Context) error {
|
||||
// Re-create pathMap because it can no longer be trusted as socket paths can
|
||||
// change while process continues to run. Empty pathMap will be re-filled upon
|
||||
// next save.
|
||||
s.endpoints.pathMap = make(map[transport.BoundEndpoint]string)
|
||||
s.overrides.pathMap = make(map[*overrideInfo]string)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
|
||||
// beforeSave is invoked by stateify.
|
||||
func (s *session) beforeSave() {
|
||||
if s.endpoints != nil {
|
||||
if s.overrides != nil {
|
||||
if err := s.fillPathMap(); err != nil {
|
||||
panic("failed to save paths to endpoint map before saving" + err.Error())
|
||||
panic("failed to save paths to override map before saving" + err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,10 +74,10 @@ func (s *session) afterLoad() {
|
||||
panic(fmt.Sprintf("new attach name %v, want %v", opts.aname, s.aname))
|
||||
}
|
||||
|
||||
// Check if endpointMaps exist when uds sockets are enabled
|
||||
// (only pathmap will actualy have been saved).
|
||||
if opts.privateunixsocket != (s.endpoints != nil) {
|
||||
panic(fmt.Sprintf("new privateunixsocket option %v, want %v", opts.privateunixsocket, s.endpoints != nil))
|
||||
// Check if overrideMaps exist when uds sockets are enabled (only pathmaps
|
||||
// will actually have been saved).
|
||||
if opts.privateunixsocket != (s.overrides != nil) {
|
||||
panic(fmt.Sprintf("new privateunixsocket option %v, want %v", opts.privateunixsocket, s.overrides != nil))
|
||||
}
|
||||
if args.Flags != s.superBlockFlags {
|
||||
panic(fmt.Sprintf("new mount flags %v, want %v", args.Flags, s.superBlockFlags))
|
||||
|
||||
@@ -32,15 +32,15 @@ func (i *inodeOperations) BoundEndpoint(inode *fs.Inode, path string) transport.
|
||||
return nil
|
||||
}
|
||||
|
||||
if i.session().endpoints != nil {
|
||||
unlock := i.session().endpoints.lock()
|
||||
if i.session().overrides != nil {
|
||||
unlock := i.session().overrides.lock()
|
||||
defer unlock()
|
||||
ep := i.session().endpoints.get(i.fileState.key)
|
||||
ep := i.session().overrides.getBoundEndpoint(i.fileState.key)
|
||||
if ep != nil {
|
||||
return ep
|
||||
}
|
||||
|
||||
// Not found in endpoints map, it may be a gofer backed unix socket...
|
||||
// Not found in overrides map, it may be a gofer backed unix socket...
|
||||
}
|
||||
|
||||
inode.IncRef()
|
||||
|
||||
@@ -225,7 +225,6 @@ syscall_test(
|
||||
syscall_test(
|
||||
add_overlay = True,
|
||||
test = "//test/syscalls/linux:mknod_test",
|
||||
use_tmpfs = True, # mknod is not supported over gofer.
|
||||
)
|
||||
|
||||
syscall_test(
|
||||
|
||||
Reference in New Issue
Block a user