mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add most VFS methods for syscalls.
PiperOrigin-RevId: 284892289
This commit is contained in:
@@ -144,9 +144,13 @@ const (
|
||||
ModeCharacterDevice = S_IFCHR
|
||||
ModeNamedPipe = S_IFIFO
|
||||
|
||||
ModeSetUID = 04000
|
||||
ModeSetGID = 02000
|
||||
ModeSticky = 01000
|
||||
S_ISUID = 04000
|
||||
S_ISGID = 02000
|
||||
S_ISVTX = 01000
|
||||
|
||||
ModeSetUID = S_ISUID
|
||||
ModeSetGID = S_ISGID
|
||||
ModeSticky = S_ISVTX
|
||||
|
||||
ModeUserAll = 0700
|
||||
ModeUserRead = 0400
|
||||
|
||||
@@ -81,7 +81,11 @@ func mount(b *testing.B, imagePath string, vfsfs *vfs.VirtualFilesystem, pop *vf
|
||||
ctx := contexttest.Context(b)
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
if err := vfsfs.NewMount(ctx, creds, imagePath, pop, "extfs", &vfs.GetFilesystemOptions{InternalData: int(f.Fd())}); err != nil {
|
||||
if err := vfsfs.MountAt(ctx, creds, imagePath, pop, "extfs", &vfs.MountOptions{
|
||||
GetFilesystemOptions: vfs.GetFilesystemOptions{
|
||||
InternalData: int(f.Fd()),
|
||||
},
|
||||
}); err != nil {
|
||||
b.Fatalf("failed to mount tmpfs submount: %v", err)
|
||||
}
|
||||
return func() {
|
||||
|
||||
@@ -147,55 +147,54 @@ func TestSeek(t *testing.T) {
|
||||
t.Fatalf("vfsfs.OpenAt failed: %v", err)
|
||||
}
|
||||
|
||||
if n, err := fd.Impl().Seek(ctx, 0, linux.SEEK_SET); n != 0 || err != nil {
|
||||
if n, err := fd.Seek(ctx, 0, linux.SEEK_SET); n != 0 || err != nil {
|
||||
t.Errorf("expected seek position 0, got %d and error %v", n, err)
|
||||
}
|
||||
|
||||
stat, err := fd.Impl().Stat(ctx, vfs.StatOptions{})
|
||||
stat, err := fd.Stat(ctx, vfs.StatOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("fd.stat failed for file %s in image %s: %v", test.path, test.image, err)
|
||||
}
|
||||
|
||||
// We should be able to seek beyond the end of file.
|
||||
size := int64(stat.Size)
|
||||
if n, err := fd.Impl().Seek(ctx, size, linux.SEEK_SET); n != size || err != nil {
|
||||
if n, err := fd.Seek(ctx, size, linux.SEEK_SET); n != size || err != nil {
|
||||
t.Errorf("expected seek position %d, got %d and error %v", size, n, err)
|
||||
}
|
||||
|
||||
// EINVAL should be returned if the resulting offset is negative.
|
||||
if _, err := fd.Impl().Seek(ctx, -1, linux.SEEK_SET); err != syserror.EINVAL {
|
||||
if _, err := fd.Seek(ctx, -1, linux.SEEK_SET); err != syserror.EINVAL {
|
||||
t.Errorf("expected error EINVAL but got %v", err)
|
||||
}
|
||||
|
||||
if n, err := fd.Impl().Seek(ctx, 3, linux.SEEK_CUR); n != size+3 || err != nil {
|
||||
if n, err := fd.Seek(ctx, 3, linux.SEEK_CUR); n != size+3 || err != nil {
|
||||
t.Errorf("expected seek position %d, got %d and error %v", size+3, n, err)
|
||||
}
|
||||
|
||||
// Make sure negative offsets work with SEEK_CUR.
|
||||
if n, err := fd.Impl().Seek(ctx, -2, linux.SEEK_CUR); n != size+1 || err != nil {
|
||||
if n, err := fd.Seek(ctx, -2, linux.SEEK_CUR); n != size+1 || err != nil {
|
||||
t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err)
|
||||
}
|
||||
|
||||
// EINVAL should be returned if the resulting offset is negative.
|
||||
if _, err := fd.Impl().Seek(ctx, -(size + 2), linux.SEEK_CUR); err != syserror.EINVAL {
|
||||
if _, err := fd.Seek(ctx, -(size + 2), linux.SEEK_CUR); err != syserror.EINVAL {
|
||||
t.Errorf("expected error EINVAL but got %v", err)
|
||||
}
|
||||
|
||||
// Make sure SEEK_END works with regular files.
|
||||
switch fd.Impl().(type) {
|
||||
case *regularFileFD:
|
||||
if _, ok := fd.Impl().(*regularFileFD); ok {
|
||||
// Seek back to 0.
|
||||
if n, err := fd.Impl().Seek(ctx, -size, linux.SEEK_END); n != 0 || err != nil {
|
||||
if n, err := fd.Seek(ctx, -size, linux.SEEK_END); n != 0 || err != nil {
|
||||
t.Errorf("expected seek position %d, got %d and error %v", 0, n, err)
|
||||
}
|
||||
|
||||
// Seek forward beyond EOF.
|
||||
if n, err := fd.Impl().Seek(ctx, 1, linux.SEEK_END); n != size+1 || err != nil {
|
||||
if n, err := fd.Seek(ctx, 1, linux.SEEK_END); n != size+1 || err != nil {
|
||||
t.Errorf("expected seek position %d, got %d and error %v", size+1, n, err)
|
||||
}
|
||||
|
||||
// EINVAL should be returned if the resulting offset is negative.
|
||||
if _, err := fd.Impl().Seek(ctx, -(size + 1), linux.SEEK_END); err != syserror.EINVAL {
|
||||
if _, err := fd.Seek(ctx, -(size + 1), linux.SEEK_END); err != syserror.EINVAL {
|
||||
t.Errorf("expected error EINVAL but got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -456,7 +455,7 @@ func TestRead(t *testing.T) {
|
||||
want := make([]byte, 1)
|
||||
for {
|
||||
n, err := f.Read(want)
|
||||
fd.Impl().Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{})
|
||||
fd.Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{})
|
||||
|
||||
if diff := cmp.Diff(got, want); diff != "" {
|
||||
t.Errorf("file data mismatch (-want +got):\n%s", diff)
|
||||
@@ -464,7 +463,7 @@ func TestRead(t *testing.T) {
|
||||
|
||||
// Make sure there is no more file data left after getting EOF.
|
||||
if n == 0 || err == io.EOF {
|
||||
if n, _ := fd.Impl().Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{}); n != 0 {
|
||||
if n, _ := fd.Read(ctx, usermem.BytesIOSequence(got), vfs.ReadOptions{}); n != 0 {
|
||||
t.Errorf("extra unexpected file data in file %s in image %s", test.absPath, test.image)
|
||||
}
|
||||
|
||||
@@ -574,7 +573,7 @@ func TestIterDirents(t *testing.T) {
|
||||
}
|
||||
|
||||
cb := &iterDirentsCb{}
|
||||
if err = fd.Impl().IterDirents(ctx, cb); err != nil {
|
||||
if err = fd.IterDirents(ctx, cb); err != nil {
|
||||
t.Fatalf("dir fd.IterDirents() failed: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -394,7 +394,7 @@ func BenchmarkVFS2MemfsMountStat(b *testing.B) {
|
||||
}
|
||||
defer mountPoint.DecRef()
|
||||
// Create and mount the submount.
|
||||
if err := vfsObj.NewMount(ctx, creds, "", &pop, "memfs", &vfs.GetFilesystemOptions{}); err != nil {
|
||||
if err := vfsObj.MountAt(ctx, creds, "", &pop, "memfs", &vfs.MountOptions{}); err != nil {
|
||||
b.Fatalf("failed to mount tmpfs submount: %v", err)
|
||||
}
|
||||
filePathBuilder.WriteString(mountPointName)
|
||||
|
||||
@@ -194,7 +194,7 @@ func setup(t *testing.T) (context.Context, *auth.Credentials, *vfs.VirtualFilesy
|
||||
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{})
|
||||
bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
|
||||
if err != syserror.ErrWouldBlock {
|
||||
t.Fatalf("expected ErrWouldBlock reading from empty pipe %q, but got: %v", fileName, err)
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func checkEmpty(ctx context.Context, t *testing.T, fd *vfs.FileDescription) {
|
||||
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{})
|
||||
bytesWritten, err := fd.Write(ctx, src, vfs.WriteOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error writing to pipe %q: %v", fileName, err)
|
||||
}
|
||||
@@ -220,7 +220,7 @@ func checkWrite(ctx context.Context, t *testing.T, fd *vfs.FileDescription, msg
|
||||
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{})
|
||||
bytesRead, err := fd.Read(ctx, dst, vfs.ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("error reading from pipe %q: %v", fileName, err)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ go_library(
|
||||
"options.go",
|
||||
"permissions.go",
|
||||
"resolving_path.go",
|
||||
"syscalls.go",
|
||||
"testutil.go",
|
||||
"vfs.go",
|
||||
],
|
||||
|
||||
@@ -241,3 +241,96 @@ type IterDirentsCallback interface {
|
||||
// called.
|
||||
Handle(dirent Dirent) bool
|
||||
}
|
||||
|
||||
// OnClose is called when a file descriptor representing the FileDescription is
|
||||
// closed. Returning a non-nil error should not prevent the file descriptor
|
||||
// from being closed.
|
||||
func (fd *FileDescription) OnClose(ctx context.Context) error {
|
||||
return fd.impl.OnClose(ctx)
|
||||
}
|
||||
|
||||
// StatusFlags returns file description status flags, as for fcntl(F_GETFL).
|
||||
func (fd *FileDescription) StatusFlags(ctx context.Context) (uint32, error) {
|
||||
flags, err := fd.impl.StatusFlags(ctx)
|
||||
flags |= linux.O_LARGEFILE
|
||||
return flags, err
|
||||
}
|
||||
|
||||
// SetStatusFlags sets file description status flags, as for fcntl(F_SETFL).
|
||||
func (fd *FileDescription) SetStatusFlags(ctx context.Context, flags uint32) error {
|
||||
return fd.impl.SetStatusFlags(ctx, flags)
|
||||
}
|
||||
|
||||
// Stat returns metadata for the file represented by fd.
|
||||
func (fd *FileDescription) Stat(ctx context.Context, opts StatOptions) (linux.Statx, error) {
|
||||
return fd.impl.Stat(ctx, opts)
|
||||
}
|
||||
|
||||
// SetStat updates metadata for the file represented by fd.
|
||||
func (fd *FileDescription) SetStat(ctx context.Context, opts SetStatOptions) error {
|
||||
return fd.impl.SetStat(ctx, opts)
|
||||
}
|
||||
|
||||
// StatFS returns metadata for the filesystem containing the file represented
|
||||
// by fd.
|
||||
func (fd *FileDescription) StatFS(ctx context.Context) (linux.Statfs, error) {
|
||||
return fd.impl.StatFS(ctx)
|
||||
}
|
||||
|
||||
// PRead reads from the file represented by fd into dst, starting at the given
|
||||
// offset, and returns the number of bytes read. PRead is permitted to return
|
||||
// partial reads with a nil error.
|
||||
func (fd *FileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts ReadOptions) (int64, error) {
|
||||
return fd.impl.PRead(ctx, dst, offset, opts)
|
||||
}
|
||||
|
||||
// Read is similar to PRead, but does not specify an offset.
|
||||
func (fd *FileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts ReadOptions) (int64, error) {
|
||||
return fd.impl.Read(ctx, dst, opts)
|
||||
}
|
||||
|
||||
// PWrite writes src to the file represented by fd, starting at the given
|
||||
// offset, and returns the number of bytes written. PWrite is permitted to
|
||||
// return partial writes with a nil error.
|
||||
func (fd *FileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts WriteOptions) (int64, error) {
|
||||
return fd.impl.PWrite(ctx, src, offset, opts)
|
||||
}
|
||||
|
||||
// Write is similar to PWrite, but does not specify an offset.
|
||||
func (fd *FileDescription) Write(ctx context.Context, src usermem.IOSequence, opts WriteOptions) (int64, error) {
|
||||
return fd.impl.Write(ctx, src, opts)
|
||||
}
|
||||
|
||||
// IterDirents invokes cb on each entry in the directory represented by fd. If
|
||||
// IterDirents has been called since the last call to Seek, it continues
|
||||
// iteration from the end of the last call.
|
||||
func (fd *FileDescription) IterDirents(ctx context.Context, cb IterDirentsCallback) error {
|
||||
return fd.impl.IterDirents(ctx, cb)
|
||||
}
|
||||
|
||||
// Seek changes fd's offset (assuming one exists) and returns its new value.
|
||||
func (fd *FileDescription) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
return fd.impl.Seek(ctx, offset, whence)
|
||||
}
|
||||
|
||||
// Sync has the semantics of fsync(2).
|
||||
func (fd *FileDescription) Sync(ctx context.Context) error {
|
||||
return fd.impl.Sync(ctx)
|
||||
}
|
||||
|
||||
// ConfigureMMap mutates opts to implement mmap(2) for the file represented by
|
||||
// fd.
|
||||
func (fd *FileDescription) ConfigureMMap(ctx context.Context, opts *memmap.MMapOpts) error {
|
||||
return fd.impl.ConfigureMMap(ctx, opts)
|
||||
}
|
||||
|
||||
// Ioctl implements the ioctl(2) syscall.
|
||||
func (fd *FileDescription) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) {
|
||||
return fd.impl.Ioctl(ctx, uio, args)
|
||||
}
|
||||
|
||||
// SyncFS instructs the filesystem containing fd to execute the semantics of
|
||||
// syncfs(2).
|
||||
func (fd *FileDescription) SyncFS(ctx context.Context) error {
|
||||
return fd.vd.mount.fs.impl.Sync(ctx)
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestGenCountFD(t *testing.T) {
|
||||
// The first read causes Generate to be called to fill the FD's buffer.
|
||||
buf := make([]byte, 2)
|
||||
ioseq := usermem.BytesIOSequence(buf)
|
||||
n, err := fd.Impl().Read(ctx, ioseq, ReadOptions{})
|
||||
n, err := fd.Read(ctx, ioseq, ReadOptions{})
|
||||
if n != 1 || (err != nil && err != io.EOF) {
|
||||
t.Fatalf("first Read: got (%d, %v), wanted (1, nil or EOF)", n, err)
|
||||
}
|
||||
@@ -112,17 +112,17 @@ func TestGenCountFD(t *testing.T) {
|
||||
}
|
||||
|
||||
// A second read without seeking is still at EOF.
|
||||
n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
|
||||
n, err = fd.Read(ctx, ioseq, ReadOptions{})
|
||||
if n != 0 || err != io.EOF {
|
||||
t.Fatalf("second Read: got (%d, %v), wanted (0, EOF)", n, err)
|
||||
}
|
||||
|
||||
// Seeking to the beginning of the file causes it to be regenerated.
|
||||
n, err = fd.Impl().Seek(ctx, 0, linux.SEEK_SET)
|
||||
n, err = fd.Seek(ctx, 0, linux.SEEK_SET)
|
||||
if n != 0 || err != nil {
|
||||
t.Fatalf("Seek: got (%d, %v), wanted (0, nil)", n, err)
|
||||
}
|
||||
n, err = fd.Impl().Read(ctx, ioseq, ReadOptions{})
|
||||
n, err = fd.Read(ctx, ioseq, ReadOptions{})
|
||||
if n != 1 || (err != nil && err != io.EOF) {
|
||||
t.Fatalf("Read after Seek: got (%d, %v), wanted (1, nil or EOF)", n, err)
|
||||
}
|
||||
@@ -131,7 +131,7 @@ func TestGenCountFD(t *testing.T) {
|
||||
}
|
||||
|
||||
// PRead at the beginning of the file also causes it to be regenerated.
|
||||
n, err = fd.Impl().PRead(ctx, ioseq, 0, ReadOptions{})
|
||||
n, err = fd.PRead(ctx, ioseq, 0, ReadOptions{})
|
||||
if n != 1 || (err != nil && err != io.EOF) {
|
||||
t.Fatalf("PRead: got (%d, %v), wanted (1, nil or EOF)", n, err)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ func (fs *Filesystem) Init(vfsObj *VirtualFilesystem, impl FilesystemImpl) {
|
||||
fs.refs = 1
|
||||
fs.vfs = vfsObj
|
||||
fs.impl = impl
|
||||
vfsObj.filesystemsMu.Lock()
|
||||
vfsObj.filesystems[fs] = struct{}{}
|
||||
vfsObj.filesystemsMu.Unlock()
|
||||
}
|
||||
|
||||
// VirtualFilesystem returns the containing VirtualFilesystem.
|
||||
@@ -66,9 +69,28 @@ func (fs *Filesystem) IncRef() {
|
||||
}
|
||||
}
|
||||
|
||||
// TryIncRef increments fs' reference count and returns true. If fs' reference
|
||||
// count is zero, TryIncRef does nothing and returns false.
|
||||
//
|
||||
// TryIncRef does not require that a reference is held on fs.
|
||||
func (fs *Filesystem) TryIncRef() bool {
|
||||
for {
|
||||
refs := atomic.LoadInt64(&fs.refs)
|
||||
if refs <= 0 {
|
||||
return false
|
||||
}
|
||||
if atomic.CompareAndSwapInt64(&fs.refs, refs, refs+1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DecRef decrements fs' reference count.
|
||||
func (fs *Filesystem) DecRef() {
|
||||
if refs := atomic.AddInt64(&fs.refs, -1); refs == 0 {
|
||||
fs.vfs.filesystemsMu.Lock()
|
||||
delete(fs.vfs.filesystems, fs)
|
||||
fs.vfs.filesystemsMu.Unlock()
|
||||
fs.impl.Release()
|
||||
} else if refs < 0 {
|
||||
panic("Filesystem.decRef() called without holding a reference")
|
||||
|
||||
+66
-3
@@ -18,6 +18,7 @@ import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
@@ -133,13 +134,13 @@ func (vfs *VirtualFilesystem) NewMountNamespace(ctx context.Context, creds *auth
|
||||
return mntns, nil
|
||||
}
|
||||
|
||||
// NewMount creates and mounts a Filesystem configured by the given arguments.
|
||||
func (vfs *VirtualFilesystem) NewMount(ctx context.Context, creds *auth.Credentials, source string, target *PathOperation, fsTypeName string, opts *GetFilesystemOptions) error {
|
||||
// MountAt creates and mounts a Filesystem configured by the given arguments.
|
||||
func (vfs *VirtualFilesystem) MountAt(ctx context.Context, creds *auth.Credentials, source string, target *PathOperation, fsTypeName string, opts *MountOptions) error {
|
||||
fsType := vfs.getFilesystemType(fsTypeName)
|
||||
if fsType == nil {
|
||||
return syserror.ENODEV
|
||||
}
|
||||
fs, root, err := fsType.GetFilesystem(ctx, vfs, creds, source, *opts)
|
||||
fs, root, err := fsType.GetFilesystem(ctx, vfs, creds, source, opts.GetFilesystemOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -207,6 +208,68 @@ func (vfs *VirtualFilesystem) NewMount(ctx context.Context, creds *auth.Credenti
|
||||
return nil
|
||||
}
|
||||
|
||||
// UmountAt removes the Mount at the given path.
|
||||
func (vfs *VirtualFilesystem) UmountAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *UmountOptions) error {
|
||||
if opts.Flags&^(linux.MNT_FORCE|linux.MNT_DETACH) != 0 {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
|
||||
// MNT_FORCE is currently unimplemented except for the permission check.
|
||||
if opts.Flags&linux.MNT_FORCE != 0 && creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, creds.UserNamespace.Root()) {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
vd, err := vfs.GetDentryAt(ctx, creds, pop, &GetDentryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer vd.DecRef()
|
||||
if vd.dentry != vd.mount.root {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
vfs.mountMu.Lock()
|
||||
if mntns := MountNamespaceFromContext(ctx); mntns != nil && mntns != vd.mount.ns {
|
||||
vfs.mountMu.Unlock()
|
||||
return syserror.EINVAL
|
||||
}
|
||||
|
||||
// TODO(jamieliu): Linux special-cases umount of the caller's root, which
|
||||
// we don't implement yet (we'll just fail it since the caller holds a
|
||||
// reference on it).
|
||||
|
||||
vfs.mounts.seq.BeginWrite()
|
||||
if opts.Flags&linux.MNT_DETACH == 0 {
|
||||
if len(vd.mount.children) != 0 {
|
||||
vfs.mounts.seq.EndWrite()
|
||||
vfs.mountMu.Unlock()
|
||||
return syserror.EBUSY
|
||||
}
|
||||
// We are holding a reference on vd.mount.
|
||||
expectedRefs := int64(1)
|
||||
if !vd.mount.umounted {
|
||||
expectedRefs = 2
|
||||
}
|
||||
if atomic.LoadInt64(&vd.mount.refs)&^math.MinInt64 != expectedRefs { // mask out MSB
|
||||
vfs.mounts.seq.EndWrite()
|
||||
vfs.mountMu.Unlock()
|
||||
return syserror.EBUSY
|
||||
}
|
||||
}
|
||||
vdsToDecRef, mountsToDecRef := vfs.umountRecursiveLocked(vd.mount, &umountRecursiveOptions{
|
||||
eager: opts.Flags&linux.MNT_DETACH == 0,
|
||||
disconnectHierarchy: true,
|
||||
}, nil, nil)
|
||||
vfs.mounts.seq.EndWrite()
|
||||
vfs.mountMu.Unlock()
|
||||
for _, vd := range vdsToDecRef {
|
||||
vd.DecRef()
|
||||
}
|
||||
for _, mnt := range mountsToDecRef {
|
||||
mnt.DecRef()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type umountRecursiveOptions struct {
|
||||
// If eager is true, ensure that future calls to Mount.tryIncMountedRef()
|
||||
// on umounted mounts fail.
|
||||
|
||||
@@ -46,6 +46,12 @@ type MknodOptions struct {
|
||||
DevMinor uint32
|
||||
}
|
||||
|
||||
// MountOptions contains options to VirtualFilesystem.MountAt().
|
||||
type MountOptions struct {
|
||||
// GetFilesystemOptions contains options to FilesystemType.GetFilesystem().
|
||||
GetFilesystemOptions GetFilesystemOptions
|
||||
}
|
||||
|
||||
// OpenOptions contains options to VirtualFilesystem.OpenAt() and
|
||||
// FilesystemImpl.OpenAt().
|
||||
type OpenOptions struct {
|
||||
@@ -114,6 +120,12 @@ type StatOptions struct {
|
||||
Sync uint32
|
||||
}
|
||||
|
||||
// UmountOptions contains options to VirtualFilesystem.UmountAt().
|
||||
type UmountOptions struct {
|
||||
// Flags contains flags as specified for umount2(2).
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
// WriteOptions contains options to FileDescription.PWrite(),
|
||||
// FileDescriptionImpl.PWrite(), FileDescription.Write(), and
|
||||
// FileDescriptionImpl.Write().
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
// 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 vfs
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// PathOperation specifies the path operated on by a VFS method.
|
||||
//
|
||||
// PathOperation is passed to VFS methods by pointer to reduce memory copying:
|
||||
// it's somewhat large and should never escape. (Options structs are passed by
|
||||
// pointer to VFS and FileDescription methods for the same reason.)
|
||||
type PathOperation struct {
|
||||
// Root is the VFS root. References on Root are borrowed from the provider
|
||||
// of the PathOperation.
|
||||
//
|
||||
// Invariants: Root.Ok().
|
||||
Root VirtualDentry
|
||||
|
||||
// Start is the starting point for the path traversal. References on Start
|
||||
// are borrowed from the provider of the PathOperation (i.e. the caller of
|
||||
// the VFS method to which the PathOperation was passed).
|
||||
//
|
||||
// Invariants: Start.Ok(). If Pathname.Absolute, then Start == Root.
|
||||
Start VirtualDentry
|
||||
|
||||
// Path is the pathname traversed by this operation.
|
||||
Pathname string
|
||||
|
||||
// If FollowFinalSymlink is true, and the Dentry traversed by the final
|
||||
// path component represents a symbolic link, the symbolic link should be
|
||||
// followed.
|
||||
FollowFinalSymlink bool
|
||||
}
|
||||
|
||||
// GetDentryAt returns a VirtualDentry representing the given path, at which a
|
||||
// file must exist. A reference is taken on the returned VirtualDentry.
|
||||
func (vfs *VirtualFilesystem) GetDentryAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *GetDentryOptions) (VirtualDentry, error) {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return VirtualDentry{}, err
|
||||
}
|
||||
for {
|
||||
d, err := rp.mount.fs.impl.GetDentryAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vd := VirtualDentry{
|
||||
mount: rp.mount,
|
||||
dentry: d,
|
||||
}
|
||||
rp.mount.IncRef()
|
||||
vfs.putResolvingPath(rp)
|
||||
return vd, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return VirtualDentry{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MkdirAt creates a directory at the given path.
|
||||
func (vfs *VirtualFilesystem) MkdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MkdirOptions) error {
|
||||
// "Under Linux, apart from the permission bits, the S_ISVTX mode bit is
|
||||
// also honored." - mkdir(2)
|
||||
opts.Mode &= 01777
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.MkdirAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Remove:
|
||||
//
|
||||
// - O_LARGEFILE, which we always report in FileDescription status flags
|
||||
// since only 64-bit architectures are supported at this time.
|
||||
//
|
||||
// - O_CLOEXEC, which affects file descriptors and therefore must be
|
||||
// handled outside of VFS.
|
||||
//
|
||||
// - Unknown flags.
|
||||
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC | linux.O_APPEND | linux.O_NONBLOCK | linux.O_DSYNC | linux.O_ASYNC | linux.O_DIRECT | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NOATIME | linux.O_SYNC | linux.O_PATH | linux.O_TMPFILE
|
||||
// Linux's __O_SYNC (which we call linux.O_SYNC) implies O_DSYNC.
|
||||
if opts.Flags&linux.O_SYNC != 0 {
|
||||
opts.Flags |= linux.O_DSYNC
|
||||
}
|
||||
// Linux's __O_TMPFILE (which we call linux.O_TMPFILE) must be specified
|
||||
// with O_DIRECTORY and a writable access mode (to ensure that it fails on
|
||||
// filesystem implementations that do not support it).
|
||||
if opts.Flags&linux.O_TMPFILE != 0 {
|
||||
if opts.Flags&linux.O_DIRECTORY == 0 {
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
if opts.Flags&linux.O_CREAT != 0 {
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
if opts.Flags&linux.O_ACCMODE == linux.O_RDONLY {
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
}
|
||||
// O_PATH causes most other flags to be ignored.
|
||||
if opts.Flags&linux.O_PATH != 0 {
|
||||
opts.Flags &= linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_PATH
|
||||
}
|
||||
// "On Linux, the following bits are also honored in mode: [S_ISUID,
|
||||
// S_ISGID, S_ISVTX]" - open(2)
|
||||
opts.Mode &= 07777
|
||||
|
||||
if opts.Flags&linux.O_NOFOLLOW != 0 {
|
||||
pop.FollowFinalSymlink = false
|
||||
}
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.Flags&linux.O_DIRECTORY != 0 {
|
||||
rp.mustBeDir = true
|
||||
rp.mustBeDirOrig = true
|
||||
}
|
||||
for {
|
||||
fd, err := rp.mount.fs.impl.OpenAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return fd, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StatAt returns metadata for the file at the given path.
|
||||
func (vfs *VirtualFilesystem) StatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *StatOptions) (linux.Statx, error) {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
for {
|
||||
stat, err := rp.mount.fs.impl.StatAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return stat, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StatusFlags returns file description status flags.
|
||||
func (fd *FileDescription) StatusFlags(ctx context.Context) (uint32, error) {
|
||||
flags, err := fd.impl.StatusFlags(ctx)
|
||||
flags |= linux.O_LARGEFILE
|
||||
return flags, err
|
||||
}
|
||||
|
||||
// SetStatusFlags sets file description status flags.
|
||||
func (fd *FileDescription) SetStatusFlags(ctx context.Context, flags uint32) error {
|
||||
return fd.impl.SetStatusFlags(ctx, flags)
|
||||
}
|
||||
|
||||
// TODO:
|
||||
//
|
||||
// - VFS.SyncAllFilesystems() for sync(2)
|
||||
//
|
||||
// - Something for syncfs(2)
|
||||
//
|
||||
// - VFS.LinkAt()
|
||||
//
|
||||
// - VFS.ReadlinkAt()
|
||||
//
|
||||
// - VFS.RenameAt()
|
||||
//
|
||||
// - VFS.RmdirAt()
|
||||
//
|
||||
// - VFS.SetStatAt()
|
||||
//
|
||||
// - VFS.StatFSAt()
|
||||
//
|
||||
// - VFS.SymlinkAt()
|
||||
//
|
||||
// - VFS.UmountAt()
|
||||
//
|
||||
// - VFS.UnlinkAt()
|
||||
//
|
||||
// - FileDescription.(almost everything)
|
||||
@@ -20,6 +20,7 @@
|
||||
// VirtualFilesystem.mountMu
|
||||
// Dentry.mu
|
||||
// Locks acquired by FilesystemImpls between Prepare{Delete,Rename}Dentry and Commit{Delete,Rename*}Dentry
|
||||
// VirtualFilesystem.filesystemsMu
|
||||
// VirtualFilesystem.fsTypesMu
|
||||
//
|
||||
// Locking Dentry.mu in multiple Dentries requires holding
|
||||
@@ -28,6 +29,11 @@ package vfs
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// A VirtualFilesystem (VFS for short) combines Filesystems in trees of Mounts.
|
||||
@@ -67,6 +73,11 @@ type VirtualFilesystem struct {
|
||||
// mountpoints is analogous to Linux's mountpoint_hashtable.
|
||||
mountpoints map[*Dentry]map[*Mount]struct{}
|
||||
|
||||
// filesystems contains all Filesystems. filesystems is protected by
|
||||
// filesystemsMu.
|
||||
filesystemsMu sync.Mutex
|
||||
filesystems map[*Filesystem]struct{}
|
||||
|
||||
// fsTypes contains all FilesystemTypes that are usable in the
|
||||
// VirtualFilesystem. fsTypes is protected by fsTypesMu.
|
||||
fsTypesMu sync.RWMutex
|
||||
@@ -77,12 +88,379 @@ type VirtualFilesystem struct {
|
||||
func New() *VirtualFilesystem {
|
||||
vfs := &VirtualFilesystem{
|
||||
mountpoints: make(map[*Dentry]map[*Mount]struct{}),
|
||||
filesystems: make(map[*Filesystem]struct{}),
|
||||
fsTypes: make(map[string]FilesystemType),
|
||||
}
|
||||
vfs.mounts.Init()
|
||||
return vfs
|
||||
}
|
||||
|
||||
// PathOperation specifies the path operated on by a VFS method.
|
||||
//
|
||||
// PathOperation is passed to VFS methods by pointer to reduce memory copying:
|
||||
// it's somewhat large and should never escape. (Options structs are passed by
|
||||
// pointer to VFS and FileDescription methods for the same reason.)
|
||||
type PathOperation struct {
|
||||
// Root is the VFS root. References on Root are borrowed from the provider
|
||||
// of the PathOperation.
|
||||
//
|
||||
// Invariants: Root.Ok().
|
||||
Root VirtualDentry
|
||||
|
||||
// Start is the starting point for the path traversal. References on Start
|
||||
// are borrowed from the provider of the PathOperation (i.e. the caller of
|
||||
// the VFS method to which the PathOperation was passed).
|
||||
//
|
||||
// Invariants: Start.Ok(). If Pathname.Absolute, then Start == Root.
|
||||
Start VirtualDentry
|
||||
|
||||
// Path is the pathname traversed by this operation.
|
||||
Pathname string
|
||||
|
||||
// If FollowFinalSymlink is true, and the Dentry traversed by the final
|
||||
// path component represents a symbolic link, the symbolic link should be
|
||||
// followed.
|
||||
FollowFinalSymlink bool
|
||||
}
|
||||
|
||||
// GetDentryAt returns a VirtualDentry representing the given path, at which a
|
||||
// file must exist. A reference is taken on the returned VirtualDentry.
|
||||
func (vfs *VirtualFilesystem) GetDentryAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *GetDentryOptions) (VirtualDentry, error) {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return VirtualDentry{}, err
|
||||
}
|
||||
for {
|
||||
d, err := rp.mount.fs.impl.GetDentryAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vd := VirtualDentry{
|
||||
mount: rp.mount,
|
||||
dentry: d,
|
||||
}
|
||||
rp.mount.IncRef()
|
||||
vfs.putResolvingPath(rp)
|
||||
return vd, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return VirtualDentry{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LinkAt creates a hard link at newpop representing the existing file at
|
||||
// oldpop.
|
||||
func (vfs *VirtualFilesystem) LinkAt(ctx context.Context, creds *auth.Credentials, oldpop, newpop *PathOperation) error {
|
||||
oldVD, err := vfs.GetDentryAt(ctx, creds, oldpop, &GetDentryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rp, err := vfs.getResolvingPath(creds, newpop)
|
||||
if err != nil {
|
||||
oldVD.DecRef()
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.LinkAt(ctx, rp, oldVD)
|
||||
if err == nil {
|
||||
oldVD.DecRef()
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
oldVD.DecRef()
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MkdirAt creates a directory at the given path.
|
||||
func (vfs *VirtualFilesystem) MkdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *MkdirOptions) error {
|
||||
// "Under Linux, apart from the permission bits, the S_ISVTX mode bit is
|
||||
// also honored." - mkdir(2)
|
||||
opts.Mode &= 0777 | linux.S_ISVTX
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.MkdirAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Remove:
|
||||
//
|
||||
// - O_LARGEFILE, which we always report in FileDescription status flags
|
||||
// since only 64-bit architectures are supported at this time.
|
||||
//
|
||||
// - O_CLOEXEC, which affects file descriptors and therefore must be
|
||||
// handled outside of VFS.
|
||||
//
|
||||
// - Unknown flags.
|
||||
opts.Flags &= linux.O_ACCMODE | linux.O_CREAT | linux.O_EXCL | linux.O_NOCTTY | linux.O_TRUNC | linux.O_APPEND | linux.O_NONBLOCK | linux.O_DSYNC | linux.O_ASYNC | linux.O_DIRECT | linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_NOATIME | linux.O_SYNC | linux.O_PATH | linux.O_TMPFILE
|
||||
// Linux's __O_SYNC (which we call linux.O_SYNC) implies O_DSYNC.
|
||||
if opts.Flags&linux.O_SYNC != 0 {
|
||||
opts.Flags |= linux.O_DSYNC
|
||||
}
|
||||
// Linux's __O_TMPFILE (which we call linux.O_TMPFILE) must be specified
|
||||
// with O_DIRECTORY and a writable access mode (to ensure that it fails on
|
||||
// filesystem implementations that do not support it).
|
||||
if opts.Flags&linux.O_TMPFILE != 0 {
|
||||
if opts.Flags&linux.O_DIRECTORY == 0 {
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
if opts.Flags&linux.O_CREAT != 0 {
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
if opts.Flags&linux.O_ACCMODE == linux.O_RDONLY {
|
||||
return nil, syserror.EINVAL
|
||||
}
|
||||
}
|
||||
// O_PATH causes most other flags to be ignored.
|
||||
if opts.Flags&linux.O_PATH != 0 {
|
||||
opts.Flags &= linux.O_DIRECTORY | linux.O_NOFOLLOW | linux.O_PATH
|
||||
}
|
||||
// "On Linux, the following bits are also honored in mode: [S_ISUID,
|
||||
// S_ISGID, S_ISVTX]" - open(2)
|
||||
opts.Mode &= 0777 | linux.S_ISUID | linux.S_ISGID | linux.S_ISVTX
|
||||
|
||||
if opts.Flags&linux.O_NOFOLLOW != 0 {
|
||||
pop.FollowFinalSymlink = false
|
||||
}
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if opts.Flags&linux.O_DIRECTORY != 0 {
|
||||
rp.mustBeDir = true
|
||||
rp.mustBeDirOrig = true
|
||||
}
|
||||
for {
|
||||
fd, err := rp.mount.fs.impl.OpenAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return fd, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReadlinkAt returns the target of the symbolic link at the given path.
|
||||
func (vfs *VirtualFilesystem) ReadlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) (string, error) {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for {
|
||||
target, err := rp.mount.fs.impl.ReadlinkAt(ctx, rp)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return target, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RenameAt renames the file at oldpop to newpop.
|
||||
func (vfs *VirtualFilesystem) RenameAt(ctx context.Context, creds *auth.Credentials, oldpop, newpop *PathOperation, opts *RenameOptions) error {
|
||||
oldVD, err := vfs.GetDentryAt(ctx, creds, oldpop, &GetDentryOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rp, err := vfs.getResolvingPath(creds, newpop)
|
||||
if err != nil {
|
||||
oldVD.DecRef()
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.RenameAt(ctx, rp, oldVD, *opts)
|
||||
if err == nil {
|
||||
oldVD.DecRef()
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
oldVD.DecRef()
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RmdirAt removes the directory at the given path.
|
||||
func (vfs *VirtualFilesystem) RmdirAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) error {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.RmdirAt(ctx, rp)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetStatAt changes metadata for the file at the given path.
|
||||
func (vfs *VirtualFilesystem) SetStatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *SetStatOptions) error {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.SetStatAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StatAt returns metadata for the file at the given path.
|
||||
func (vfs *VirtualFilesystem) StatAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, opts *StatOptions) (linux.Statx, error) {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
for {
|
||||
stat, err := rp.mount.fs.impl.StatAt(ctx, rp, *opts)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return stat, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return linux.Statx{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StatFSAt returns metadata for the filesystem containing the file at the
|
||||
// given path.
|
||||
func (vfs *VirtualFilesystem) StatFSAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) (linux.Statfs, error) {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
for {
|
||||
statfs, err := rp.mount.fs.impl.StatFSAt(ctx, rp)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return statfs, nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return linux.Statfs{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SymlinkAt creates a symbolic link at the given path with the given target.
|
||||
func (vfs *VirtualFilesystem) SymlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation, target string) error {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.SymlinkAt(ctx, rp, target)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UnlinkAt deletes the non-directory file at the given path.
|
||||
func (vfs *VirtualFilesystem) UnlinkAt(ctx context.Context, creds *auth.Credentials, pop *PathOperation) error {
|
||||
rp, err := vfs.getResolvingPath(creds, pop)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
err := rp.mount.fs.impl.UnlinkAt(ctx, rp)
|
||||
if err == nil {
|
||||
vfs.putResolvingPath(rp)
|
||||
return nil
|
||||
}
|
||||
if !rp.handleError(err) {
|
||||
vfs.putResolvingPath(rp)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SyncAllFilesystems has the semantics of Linux's sync(2).
|
||||
func (vfs *VirtualFilesystem) SyncAllFilesystems(ctx context.Context) error {
|
||||
fss := make(map[*Filesystem]struct{})
|
||||
vfs.filesystemsMu.Lock()
|
||||
for fs := range vfs.filesystems {
|
||||
if !fs.TryIncRef() {
|
||||
continue
|
||||
}
|
||||
fss[fs] = struct{}{}
|
||||
}
|
||||
vfs.filesystemsMu.Unlock()
|
||||
var retErr error
|
||||
for fs := range fss {
|
||||
if err := fs.impl.Sync(ctx); err != nil && retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
fs.DecRef()
|
||||
}
|
||||
return retErr
|
||||
}
|
||||
|
||||
// A VirtualDentry represents a node in a VFS tree, by combining a Dentry
|
||||
// (which represents a node in a Filesystem's tree) and a Mount (which
|
||||
// represents the Filesystem's position in a VFS mount tree).
|
||||
|
||||
Reference in New Issue
Block a user