Add support for pipes in VFS2.

PiperOrigin-RevId: 275650307
This commit is contained in:
Kevin Krakauer
2019-10-19 11:49:38 -07:00
committed by gVisor bot
parent 74044f2cca
commit 652f7b1d0f
7 changed files with 380 additions and 13 deletions
+20
View File
@@ -24,14 +24,18 @@ go_library(
"directory.go",
"filesystem.go",
"memfs.go",
"named_pipe.go",
"regular_file.go",
"symlink.go",
],
importpath = "gvisor.dev/gvisor/pkg/sentry/fsimpl/memfs",
deps = [
"//pkg/abi/linux",
"//pkg/amutex",
"//pkg/sentry/arch",
"//pkg/sentry/context",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/kernel/pipe",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
"//pkg/syserror",
@@ -54,3 +58,19 @@ go_test(
"//pkg/syserror",
],
)
go_test(
name = "memfs_test",
size = "small",
srcs = ["pipe_test.go"],
embed = [":memfs"],
deps = [
"//pkg/abi/linux",
"//pkg/sentry/context",
"//pkg/sentry/context/contexttest",
"//pkg/sentry/kernel/auth",
"//pkg/sentry/usermem",
"//pkg/sentry/vfs",
"//pkg/syserror",
],
)
+45 -10
View File
@@ -233,7 +233,7 @@ func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
if err != nil {
return err
}
_, err = checkCreateLocked(rp, parentVFSD, parentInode)
pc, err := checkCreateLocked(rp, parentVFSD, parentInode)
if err != nil {
return err
}
@@ -241,8 +241,40 @@ func (fs *filesystem) MknodAt(ctx context.Context, rp *vfs.ResolvingPath, opts v
return err
}
defer rp.Mount().EndWrite()
// TODO: actually implement mknod
return syserror.EPERM
switch opts.Mode.FileType() {
case 0:
// "Zero file type is equivalent to type S_IFREG." - mknod(2)
fallthrough
case linux.ModeRegular:
// TODO(b/138862511): Implement.
return syserror.EINVAL
case linux.ModeNamedPipe:
child := fs.newDentry(fs.newNamedPipe(rp.Credentials(), opts.Mode))
parentVFSD.InsertChild(&child.vfsd, pc)
parentInode.impl.(*directory).childList.PushBack(child)
return nil
case linux.ModeSocket:
// TODO(b/138862511): Implement.
return syserror.EINVAL
case linux.ModeCharacterDevice:
fallthrough
case linux.ModeBlockDevice:
// TODO(b/72101894): We don't support creating block or character
// devices at the moment.
//
// When we start supporting block and character devices, we'll
// need to check for CAP_MKNOD here.
return syserror.EPERM
default:
// "EINVAL - mode requested creation of something other than a
// regular file, device special file, FIFO or socket." - mknod(2)
return syserror.EINVAL
}
}
// OpenAt implements vfs.FilesystemImpl.OpenAt.
@@ -250,8 +282,9 @@ func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
// Filter out flags that are not supported by memfs. O_DIRECTORY and
// O_NOFOLLOW have no effect here (they're handled by VFS by setting
// appropriate bits in rp), but are returned by
// FileDescriptionImpl.StatusFlags().
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC | linux.O_DIRECTORY | linux.O_NOFOLLOW
// FileDescriptionImpl.StatusFlags(). O_NONBLOCK is supported only by
// pipes.
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_TRUNC | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NONBLOCK
if opts.Flags&linux.O_CREAT == 0 {
fs.mu.RLock()
@@ -260,7 +293,7 @@ func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
if err != nil {
return nil, err
}
return inode.open(rp, vfsd, opts.Flags, false)
return inode.open(ctx, rp, vfsd, opts.Flags, false)
}
mustCreate := opts.Flags&linux.O_EXCL != 0
@@ -275,7 +308,7 @@ func (fs *filesystem) OpenAt(ctx context.Context, rp *vfs.ResolvingPath, opts vf
if mustCreate {
return nil, syserror.EEXIST
}
return inode.open(rp, vfsd, opts.Flags, false)
return inode.open(ctx, rp, vfsd, opts.Flags, false)
}
afterTrailingSymlink:
// Walk to the parent directory of the last path component.
@@ -320,7 +353,7 @@ afterTrailingSymlink:
child := fs.newDentry(childInode)
vfsd.InsertChild(&child.vfsd, pc)
inode.impl.(*directory).childList.PushBack(child)
return childInode.open(rp, &child.vfsd, opts.Flags, true)
return childInode.open(ctx, rp, &child.vfsd, opts.Flags, true)
}
// Open existing file or follow symlink.
if mustCreate {
@@ -336,10 +369,10 @@ afterTrailingSymlink:
// symlink target.
goto afterTrailingSymlink
}
return childInode.open(rp, childVFSD, opts.Flags, false)
return childInode.open(ctx, rp, childVFSD, opts.Flags, false)
}
func (i *inode) open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32, afterCreate bool) (*vfs.FileDescription, error) {
func (i *inode) open(ctx context.Context, rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32, afterCreate bool) (*vfs.FileDescription, error) {
ats := vfs.AccessTypesForOpenFlags(flags)
if !afterCreate {
if err := i.checkPermissions(rp.Credentials(), ats, i.isDir()); err != nil {
@@ -378,6 +411,8 @@ func (i *inode) open(rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32, afte
case *symlink:
// Can't open symlinks without O_PATH (which is unimplemented).
return nil, syserror.ELOOP
case *namedPipe:
return newNamedPipeFD(ctx, impl, rp, vfsd, flags)
default:
panic(fmt.Sprintf("unknown inode type: %T", i.impl))
}
+2
View File
@@ -227,6 +227,8 @@ func (i *inode) statTo(stat *linux.Statx) {
stat.Mask |= linux.STATX_SIZE | linux.STATX_BLOCKS
stat.Size = uint64(len(impl.target))
stat.Blocks = allocatedBlocksForSize(stat.Size)
case *namedPipe:
stat.Mode |= linux.S_IFIFO
default:
panic(fmt.Sprintf("unknown inode type: %T", i.impl))
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright 2019 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 memfs
import (
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/kernel/pipe"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
type namedPipe struct {
inode inode
pipe *pipe.VFSPipe
}
// Preconditions:
// * fs.mu must be locked.
// * rp.Mount().CheckBeginWrite() has been called successfully.
func (fs *filesystem) newNamedPipe(creds *auth.Credentials, mode linux.FileMode) *inode {
file := &namedPipe{pipe: pipe.NewVFSPipe(pipe.DefaultPipeSize, usermem.PageSize)}
file.inode.init(file, fs, creds, mode)
file.inode.nlink = 1 // Only the parent has a link.
return &file.inode
}
// namedPipeFD implements vfs.FileDescriptionImpl. Methods are implemented
// entirely via struct embedding.
type namedPipeFD struct {
fileDescription
*pipe.VFSPipeFD
}
func newNamedPipeFD(ctx context.Context, np *namedPipe, rp *vfs.ResolvingPath, vfsd *vfs.Dentry, flags uint32) (*vfs.FileDescription, error) {
var err error
var fd namedPipeFD
fd.VFSPipeFD, err = np.pipe.NewVFSPipeFD(ctx, rp, vfsd, &fd.vfsfd, flags)
if err != nil {
return nil, err
}
fd.vfsfd.Init(&fd, rp.Mount(), vfsd)
return &fd.vfsfd, nil
}
+233
View File
@@ -0,0 +1,233 @@
// Copyright 2019 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 memfs
import (
"bytes"
"testing"
"gvisor.dev/gvisor/pkg/abi/linux"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/usermem"
"gvisor.dev/gvisor/pkg/sentry/vfs"
"gvisor.dev/gvisor/pkg/syserror"
)
const fileName = "mypipe"
func TestSeparateFDs(t *testing.T) {
ctx, creds, vfsObj, root := setup(t)
defer root.DecRef()
// Open the read side. This is done in a concurrently because opening
// One end the pipe blocks until the other end is opened.
pop := vfs.PathOperation{
Root: root,
Start: root,
Pathname: fileName,
FollowFinalSymlink: true,
}
rfdchan := make(chan *vfs.FileDescription)
go func() {
openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY}
rfd, _ := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
rfdchan <- rfd
}()
// Open the write side.
openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY}
wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
if err != nil {
t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
}
defer wfd.DecRef()
rfd, ok := <-rfdchan
if !ok {
t.Fatalf("failed to open pipe for reading %q", fileName)
}
defer rfd.DecRef()
const msg = "vamos azul"
checkEmpty(ctx, t, rfd)
checkWrite(ctx, t, wfd, msg)
checkRead(ctx, t, rfd, msg)
}
func TestNonblockingRead(t *testing.T) {
ctx, creds, vfsObj, root := setup(t)
defer root.DecRef()
// Open the read side as nonblocking.
pop := vfs.PathOperation{
Root: root,
Start: root,
Pathname: fileName,
FollowFinalSymlink: true,
}
openOpts := vfs.OpenOptions{Flags: linux.O_RDONLY | linux.O_NONBLOCK}
rfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
if err != nil {
t.Fatalf("failed to open pipe for reading %q: %v", fileName, err)
}
defer rfd.DecRef()
// Open the write side.
openOpts = vfs.OpenOptions{Flags: linux.O_WRONLY}
wfd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
if err != nil {
t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
}
defer wfd.DecRef()
const msg = "geh blau"
checkEmpty(ctx, t, rfd)
checkWrite(ctx, t, wfd, msg)
checkRead(ctx, t, rfd, msg)
}
func TestNonblockingWriteError(t *testing.T) {
ctx, creds, vfsObj, root := setup(t)
defer root.DecRef()
// Open the write side as nonblocking, which should return ENXIO.
pop := vfs.PathOperation{
Root: root,
Start: root,
Pathname: fileName,
FollowFinalSymlink: true,
}
openOpts := vfs.OpenOptions{Flags: linux.O_WRONLY | linux.O_NONBLOCK}
_, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
if err != syserror.ENXIO {
t.Fatalf("expected ENXIO, but got error: %v", err)
}
}
func TestSingleFD(t *testing.T) {
ctx, creds, vfsObj, root := setup(t)
defer root.DecRef()
// Open the pipe as readable and writable.
pop := vfs.PathOperation{
Root: root,
Start: root,
Pathname: fileName,
FollowFinalSymlink: true,
}
openOpts := vfs.OpenOptions{Flags: linux.O_RDWR}
fd, err := vfsObj.OpenAt(ctx, creds, &pop, &openOpts)
if err != nil {
t.Fatalf("failed to open pipe for writing %q: %v", fileName, err)
}
defer fd.DecRef()
const msg = "forza blu"
checkEmpty(ctx, t, fd)
checkWrite(ctx, t, fd, msg)
checkRead(ctx, t, fd, msg)
}
// setup creates a VFS with a pipe in the root directory at path fileName. The
// returned VirtualDentry must be DecRef()'d be the caller. It calls t.Fatal
// upon failure.
func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesystem, vfs.VirtualDentry) {
ctx := contexttest.Context(t)
creds := auth.CredentialsFromContext(ctx)
// Create VFS.
vfsObj := vfs.New()
vfsObj.MustRegisterFilesystemType("memfs", FilesystemType{})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "memfs", &vfs.NewFilesystemOptions{})
if err != nil {
t.Fatalf("failed to create tmpfs root mount: %v", err)
}
// Create the pipe.
root := mntns.Root()
pop := vfs.PathOperation{
Root: root,
Start: root,
Pathname: fileName,
FollowFinalSymlink: true,
}
mknodOpts := vfs.MknodOptions{Mode: linux.ModeNamedPipe | 0644}
if err := vfsObj.MknodAt(ctx, creds, &pop, &mknodOpts); err != nil {
t.Fatalf("failed to create file %q: %v", fileName, err)
}
// Sanity check: the file pipe exists and has the correct mode.
stat, err := vfsObj.StatAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Pathname: fileName,
FollowFinalSymlink: true,
}, &vfs.StatOptions{})
if err != nil {
t.Fatalf("stat(%q) failed: %v", fileName, err)
}
if stat.Mode&^linux.S_IFMT != 0644 {
t.Errorf("got wrong permissions (%0o)", stat.Mode)
}
if stat.Mode&linux.S_IFMT != linux.ModeNamedPipe {
t.Errorf("got wrong file type (%0o)", stat.Mode)
}
return ctx, creds, vfsObj, root
}
// checkEmpty calls t.Fatal if the pipe in fd is not empty.
func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
readData := make([]byte, 1)
dst := usermem.BytesIOSequence(readData)
bytesRead, err := fd.Impl().Read(ctx, dst, vfs.ReadOptions{})
if err != syserror.ErrWouldBlock {
t.Fatalf("expected ErrWouldBlock reading from empty pipe %q, but got: %v", fileName, err)
}
if bytesRead != 0 {
t.Fatalf("expected to read 0 bytes, but got %d", bytesRead)
}
}
// checkWrite calls t.Fatal if it fails to write all of msg to fd.
func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
writeData := []byte(msg)
src := usermem.BytesIOSequence(writeData)
bytesWritten, err := fd.Impl().Write(ctx, src, vfs.WriteOptions{})
if err != nil {
t.Fatalf("error writing to pipe %q: %v", fileName, err)
}
if bytesWritten != int64(len(writeData)) {
t.Fatalf("expected to write %d bytes, but wrote %d", len(writeData), bytesWritten)
}
}
// checkRead calls t.Fatal if it fails to read msg from fd.
func checkRead(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg string) {
readData := make([]byte, len(msg))
dst := usermem.BytesIOSequence(readData)
bytesRead, err := fd.Impl().Read(ctx, dst, vfs.ReadOptions{})
if err != nil {
t.Fatalf("error reading from pipe %q: %v", fileName, err)
}
if bytesRead != int64(len(msg)) {
t.Fatalf("expected to read %d bytes, but got %d", len(msg), bytesRead)
}
if !bytes.Equal(readData, []byte(msg)) {
t.Fatalf("expected to read %q from pipe, but got %q", msg, string(readData))
}
}
+1 -1
View File
@@ -182,7 +182,7 @@ func (fd *VFSPipeFD) Release() {
}
// OnClose implements vfs.FileDescriptionImpl.OnClose.
func (fd *VFSPipeFD) OnClose() error {
func (fd *VFSPipeFD) OnClose(_ context.Context) error {
return nil
}
+20 -2
View File
@@ -96,6 +96,26 @@ func (vfs *VirtualFilesystem) MkdirAt(ctx context.Context, creds *auth.Credentia
}
}
// MknodAt creates a file of the given mode at the given path. It returns an
// error from the syserror package.
func (vfs *VirtualFilesystem) MknodAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MknodOptions) error {
rp, err := vfs.getResolvingPath(creds, pop)
if err != nil {
return nil
}
for {
if err = rp.mount.fs.impl.MknodAt(ctx, rp, *opts); err == nil {
vfs.putResolvingPath(rp)
return nil
}
// Handle mount traversals.
if !rp.handleError(err) {
vfs.putResolvingPath(rp)
return err
}
}
}
// OpenAt returns a FileDescription providing access to the file at the given
// path. A reference is taken on the returned FileDescription.
func (vfs *VirtualFilesystem) OpenAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *OpenOptions) (*FileDescription, error) {
@@ -198,8 +218,6 @@ func (fd *FileDescription) SetStatusFlags(ctx context.Context, flags uint32) err
//
// - VFS.LinkAt()
//
// - VFS.MknodAt()
//
// - VFS.ReadlinkAt()
//
// - VFS.RenameAt()