mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add support for pipes in VFS2.
PiperOrigin-RevId: 275650307
This commit is contained in:
committed by
gVisor bot
parent
74044f2cca
commit
652f7b1d0f
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user