mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
[lisa] Implement lisafs protocol methods in VFS2 gofer client and fsgofer.
Introduces RPC methods in lisafs. Makes that gofer client use lisafs RPCs instead of p9 when lisafs is enabled. Implements the handlers for those methods in fsgofer. Fixes #5465 PiperOrigin-RevId: 398080310
This commit is contained in:
@@ -242,7 +242,7 @@ const (
|
||||
|
||||
// Statx represents struct statx.
|
||||
//
|
||||
// +marshal
|
||||
// +marshal slice:StatxSlice
|
||||
type Statx struct {
|
||||
Mask uint32
|
||||
Blksize uint32
|
||||
@@ -270,6 +270,8 @@ type Statx struct {
|
||||
var SizeOfStatx = (*Statx)(nil).SizeBytes()
|
||||
|
||||
// FileMode represents a mode_t.
|
||||
//
|
||||
// +marshal
|
||||
type FileMode uint16
|
||||
|
||||
// Permissions returns just the permission bits.
|
||||
|
||||
@@ -57,6 +57,7 @@ go_library(
|
||||
srcs = [
|
||||
"channel.go",
|
||||
"client.go",
|
||||
"client_file.go",
|
||||
"communicator.go",
|
||||
"connection.go",
|
||||
"control_fd_list.go",
|
||||
|
||||
@@ -20,12 +20,19 @@ import (
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/cleanup"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/flipcall"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/unet"
|
||||
)
|
||||
|
||||
const (
|
||||
// fdsToCloseBatchSize is the number of closed FDs batched before an Close
|
||||
// RPC is made to close them all. fdsToCloseBatchSize is immutable.
|
||||
fdsToCloseBatchSize = 100
|
||||
)
|
||||
|
||||
// Client helps manage a connection to the lisafs server and pass messages
|
||||
// efficiently. There is a 1:1 mapping between a Connection and a Client.
|
||||
type Client struct {
|
||||
@@ -53,6 +60,12 @@ type Client struct {
|
||||
// maxMessageSize is the maximum payload length (in bytes) that can be sent.
|
||||
// It is initialized on Mount and is immutable.
|
||||
maxMessageSize uint32
|
||||
|
||||
// fdsToClose tracks the FDs to close. It caches the FDs no longer being used
|
||||
// by the client and closes them in one shot. It is not preserved across
|
||||
// checkpoint/restore as FDIDs are not preserved.
|
||||
fdsMu sync.Mutex
|
||||
fdsToClose []FDID
|
||||
}
|
||||
|
||||
// NewClient creates a new client for communication with the server. It mounts
|
||||
@@ -66,6 +79,7 @@ func NewClient(sock *unet.Socket, mountPath string) (*Client, *Inode, error) {
|
||||
channels: make([]*channel, 0, maxChans),
|
||||
availableChannels: make([]*channel, 0, maxChans),
|
||||
maxMessageSize: 1 << 20, // 1 MB for now.
|
||||
fdsToClose: make([]FDID, 0, fdsToCloseBatchSize),
|
||||
}
|
||||
|
||||
// Start a goroutine to check socket health. This goroutine is also
|
||||
@@ -245,6 +259,47 @@ func (c *Client) IsSupported(m MID) bool {
|
||||
return int(m) < len(c.supported) && c.supported[m]
|
||||
}
|
||||
|
||||
// CloseFDBatched either queues the passed FD to be closed or makes a batch
|
||||
// RPC to close all the accumulated FDs-to-close.
|
||||
func (c *Client) CloseFDBatched(ctx context.Context, fd FDID) {
|
||||
c.fdsMu.Lock()
|
||||
c.fdsToClose = append(c.fdsToClose, fd)
|
||||
if len(c.fdsToClose) < fdsToCloseBatchSize {
|
||||
c.fdsMu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Flush the cache. We should not hold fdsMu while making an RPC, so be sure
|
||||
// to copy the fdsToClose to another buffer before unlocking fdsMu.
|
||||
var toCloseArr [fdsToCloseBatchSize]FDID
|
||||
toClose := toCloseArr[:len(c.fdsToClose)]
|
||||
copy(toClose, c.fdsToClose)
|
||||
|
||||
// Clear fdsToClose so other FDIDs can be appended.
|
||||
c.fdsToClose = c.fdsToClose[:0]
|
||||
c.fdsMu.Unlock()
|
||||
|
||||
req := CloseReq{FDs: toClose}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := c.SndRcvMessage(Close, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
log.Warningf("lisafs: batch closing FDs returned error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// SyncFDs makes a Fsync RPC to sync multiple FDs.
|
||||
func (c *Client) SyncFDs(ctx context.Context, fds []FDID) error {
|
||||
if len(fds) == 0 {
|
||||
return nil
|
||||
}
|
||||
req := FsyncReq{FDs: fds}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := c.SndRcvMessage(FSync, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// SndRcvMessage invokes reqMarshal to marshal the request onto the payload
|
||||
// buffer, wakes up the server to process the request, waits for the response
|
||||
// and invokes respUnmarshal with the response payload. respFDs is populated
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
// Copyright 2021 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 lisafs
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
||||
)
|
||||
|
||||
// ClientFD is a wrapper around FDID that provides client-side utilities
|
||||
// so that RPC making is easier.
|
||||
type ClientFD struct {
|
||||
fd FDID
|
||||
client *Client
|
||||
}
|
||||
|
||||
// ID returns the underlying FDID.
|
||||
func (f *ClientFD) ID() FDID {
|
||||
return f.fd
|
||||
}
|
||||
|
||||
// Client returns the backing Client.
|
||||
func (f *ClientFD) Client() *Client {
|
||||
return f.client
|
||||
}
|
||||
|
||||
// NewFD initializes a new ClientFD.
|
||||
func (c *Client) NewFD(fd FDID) ClientFD {
|
||||
return ClientFD{
|
||||
client: c,
|
||||
fd: fd,
|
||||
}
|
||||
}
|
||||
|
||||
// Ok returns true if the underlying FD is ok.
|
||||
func (f *ClientFD) Ok() bool {
|
||||
return f.fd.Ok()
|
||||
}
|
||||
|
||||
// CloseBatched queues this FD to be closed on the server and resets f.fd.
|
||||
// This maybe invoke the Close RPC if the queue is full.
|
||||
func (f *ClientFD) CloseBatched(ctx context.Context) {
|
||||
f.client.CloseFDBatched(ctx, f.fd)
|
||||
f.fd = InvalidFDID
|
||||
}
|
||||
|
||||
// Close closes this FD immediately (invoking a Close RPC). Consider using
|
||||
// CloseBatched if closing this FD on remote right away is not critical.
|
||||
func (f *ClientFD) Close(ctx context.Context) error {
|
||||
fdArr := [1]FDID{f.fd}
|
||||
req := CloseReq{FDs: fdArr[:]}
|
||||
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Close, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// OpenAt makes the OpenAt RPC.
|
||||
func (f *ClientFD) OpenAt(ctx context.Context, flags uint32) (FDID, int, error) {
|
||||
req := OpenAtReq{
|
||||
FD: f.fd,
|
||||
Flags: flags,
|
||||
}
|
||||
var respFD [1]int
|
||||
var resp OpenAtResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(OpenAt, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.UnmarshalUnsafe, respFD[:])
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.NewFD, respFD[0], err
|
||||
}
|
||||
|
||||
// OpenCreateAt makes the OpenCreateAt RPC.
|
||||
func (f *ClientFD) OpenCreateAt(ctx context.Context, name string, flags uint32, mode linux.FileMode, uid UID, gid GID) (Inode, FDID, int, error) {
|
||||
var req OpenCreateAtReq
|
||||
req.DirFD = f.fd
|
||||
req.Name = SizedString(name)
|
||||
req.Flags = primitive.Uint32(flags)
|
||||
req.Mode = mode
|
||||
req.UID = uid
|
||||
req.GID = gid
|
||||
|
||||
var respFD [1]int
|
||||
var resp OpenCreateAtResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(OpenCreateAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalUnsafe, respFD[:])
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.Child, resp.NewFD, respFD[0], err
|
||||
}
|
||||
|
||||
// StatTo makes the Fstat RPC and populates stat with the result.
|
||||
func (f *ClientFD) StatTo(ctx context.Context, stat *linux.Statx) error {
|
||||
req := StatReq{FD: f.fd}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FStat, uint32(req.SizeBytes()), req.MarshalUnsafe, stat.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Sync makes the Fsync RPC.
|
||||
func (f *ClientFD) Sync(ctx context.Context) error {
|
||||
req := FsyncReq{FDs: []FDID{f.fd}}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FSync, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Read makes the PRead RPC.
|
||||
func (f *ClientFD) Read(ctx context.Context, dst []byte, offset uint64) (uint64, error) {
|
||||
req := PReadReq{
|
||||
Offset: offset,
|
||||
FD: f.fd,
|
||||
Count: uint32(len(dst)),
|
||||
}
|
||||
|
||||
resp := PReadResp{
|
||||
// This will be unmarshalled into. Already set Buf so that we don't need to
|
||||
// allocate a temporary buffer during unmarshalling.
|
||||
// PReadResp.UnmarshalBytes expects this to be set.
|
||||
Buf: dst,
|
||||
}
|
||||
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(PRead, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return uint64(resp.NumBytes), err
|
||||
}
|
||||
|
||||
// Write makes the PWrite RPC.
|
||||
func (f *ClientFD) Write(ctx context.Context, src []byte, offset uint64) (uint64, error) {
|
||||
req := PWriteReq{
|
||||
Offset: primitive.Uint64(offset),
|
||||
FD: f.fd,
|
||||
NumBytes: primitive.Uint32(len(src)),
|
||||
Buf: src,
|
||||
}
|
||||
|
||||
var resp PWriteResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(PWrite, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.Count, err
|
||||
}
|
||||
|
||||
// MkdirAt makes the MkdirAt RPC.
|
||||
func (f *ClientFD) MkdirAt(ctx context.Context, name string, mode linux.FileMode, uid UID, gid GID) (*Inode, error) {
|
||||
var req MkdirAtReq
|
||||
req.DirFD = f.fd
|
||||
req.Name = SizedString(name)
|
||||
req.Mode = mode
|
||||
req.UID = uid
|
||||
req.GID = gid
|
||||
|
||||
var resp MkdirAtResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(MkdirAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return &resp.ChildDir, err
|
||||
}
|
||||
|
||||
// SymlinkAt makes the SymlinkAt RPC.
|
||||
func (f *ClientFD) SymlinkAt(ctx context.Context, name, target string, uid UID, gid GID) (*Inode, error) {
|
||||
req := SymlinkAtReq{
|
||||
DirFD: f.fd,
|
||||
Name: SizedString(name),
|
||||
Target: SizedString(target),
|
||||
UID: uid,
|
||||
GID: gid,
|
||||
}
|
||||
|
||||
var resp SymlinkAtResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(SymlinkAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return &resp.Symlink, err
|
||||
}
|
||||
|
||||
// LinkAt makes the LinkAt RPC.
|
||||
func (f *ClientFD) LinkAt(ctx context.Context, targetFD FDID, name string) (*Inode, error) {
|
||||
req := LinkAtReq{
|
||||
DirFD: f.fd,
|
||||
Target: targetFD,
|
||||
Name: SizedString(name),
|
||||
}
|
||||
|
||||
var resp LinkAtResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(LinkAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return &resp.Link, err
|
||||
}
|
||||
|
||||
// MknodAt makes the MknodAt RPC.
|
||||
func (f *ClientFD) MknodAt(ctx context.Context, name string, mode linux.FileMode, uid UID, gid GID, minor, major uint32) (*Inode, error) {
|
||||
var req MknodAtReq
|
||||
req.DirFD = f.fd
|
||||
req.Name = SizedString(name)
|
||||
req.Mode = mode
|
||||
req.UID = uid
|
||||
req.GID = gid
|
||||
req.Minor = primitive.Uint32(minor)
|
||||
req.Major = primitive.Uint32(major)
|
||||
|
||||
var resp MknodAtResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(MknodAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return &resp.Child, err
|
||||
}
|
||||
|
||||
// SetStat makes the SetStat RPC.
|
||||
func (f *ClientFD) SetStat(ctx context.Context, stat *linux.Statx) (uint32, error, error) {
|
||||
req := SetStatReq{
|
||||
FD: f.fd,
|
||||
Mask: stat.Mask,
|
||||
Mode: uint32(stat.Mode),
|
||||
UID: UID(stat.UID),
|
||||
GID: GID(stat.GID),
|
||||
Size: stat.Size,
|
||||
Atime: linux.Timespec{
|
||||
Sec: stat.Atime.Sec,
|
||||
Nsec: int64(stat.Atime.Nsec),
|
||||
},
|
||||
Mtime: linux.Timespec{
|
||||
Sec: stat.Mtime.Sec,
|
||||
Nsec: int64(stat.Mtime.Nsec),
|
||||
},
|
||||
}
|
||||
|
||||
var resp SetStatResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(SetStat, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.FailureMask, unix.Errno(resp.FailureErrNo), err
|
||||
}
|
||||
|
||||
// WalkMultiple makes the Walk RPC with multiple path components.
|
||||
func (f *ClientFD) WalkMultiple(ctx context.Context, names []string) (WalkStatus, []Inode, error) {
|
||||
req := WalkReq{
|
||||
DirFD: f.fd,
|
||||
Path: StringArray(names),
|
||||
}
|
||||
|
||||
var resp WalkResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Walk, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.Status, resp.Inodes, err
|
||||
}
|
||||
|
||||
// Walk makes the Walk RPC with just one path component to walk.
|
||||
func (f *ClientFD) Walk(ctx context.Context, name string) (*Inode, error) {
|
||||
req := WalkReq{
|
||||
DirFD: f.fd,
|
||||
Path: []string{name},
|
||||
}
|
||||
|
||||
var inode [1]Inode
|
||||
resp := WalkResp{Inodes: inode[:]}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Walk, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch resp.Status {
|
||||
case WalkComponentDoesNotExist:
|
||||
return nil, unix.ENOENT
|
||||
case WalkComponentSymlink:
|
||||
// f is not a directory which can be walked on.
|
||||
return nil, unix.ENOTDIR
|
||||
}
|
||||
|
||||
if n := len(resp.Inodes); n > 1 {
|
||||
for i := range resp.Inodes {
|
||||
f.client.CloseFDBatched(ctx, resp.Inodes[i].ControlFD)
|
||||
}
|
||||
log.Warningf("requested to walk one component, but got %d results", n)
|
||||
return nil, unix.EIO
|
||||
} else if n == 0 {
|
||||
log.Warningf("walk has success status but no results returned")
|
||||
return nil, unix.ENOENT
|
||||
}
|
||||
return &inode[0], err
|
||||
}
|
||||
|
||||
// WalkStat makes the WalkStat RPC with multiple path components to walk.
|
||||
func (f *ClientFD) WalkStat(ctx context.Context, names []string) ([]linux.Statx, error) {
|
||||
req := WalkReq{
|
||||
DirFD: f.fd,
|
||||
Path: StringArray(names),
|
||||
}
|
||||
|
||||
var resp WalkStatResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(WalkStat, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.Stats, err
|
||||
}
|
||||
|
||||
// StatFSTo makes the FStatFS RPC and populates statFS with the result.
|
||||
func (f *ClientFD) StatFSTo(ctx context.Context, statFS *StatFS) error {
|
||||
req := FStatFSReq{FD: f.fd}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FStatFS, uint32(req.SizeBytes()), req.MarshalUnsafe, statFS.UnmarshalUnsafe, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Allocate makes the FAllocate RPC.
|
||||
func (f *ClientFD) Allocate(ctx context.Context, mode, offset, length uint64) error {
|
||||
req := FAllocateReq{
|
||||
FD: f.fd,
|
||||
Mode: mode,
|
||||
Offset: offset,
|
||||
Length: length,
|
||||
}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FAllocate, uint32(req.SizeBytes()), req.MarshalUnsafe, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadLinkAt makes the ReadLinkAt RPC.
|
||||
func (f *ClientFD) ReadLinkAt(ctx context.Context) (string, error) {
|
||||
req := ReadLinkAtReq{FD: f.fd}
|
||||
var resp ReadLinkAtResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(ReadLinkAt, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return string(resp.Target), err
|
||||
}
|
||||
|
||||
// Flush makes the Flush RPC.
|
||||
func (f *ClientFD) Flush(ctx context.Context) error {
|
||||
if !f.client.IsSupported(Flush) {
|
||||
// If Flush is not supported, it probably means that it would be a noop.
|
||||
return nil
|
||||
}
|
||||
req := FlushReq{FD: f.fd}
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Flush, uint32(req.SizeBytes()), req.MarshalUnsafe, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Connect makes the Connect RPC.
|
||||
func (f *ClientFD) Connect(ctx context.Context, sockType linux.SockType) (int, error) {
|
||||
req := ConnectReq{FD: f.fd, SockType: uint32(sockType)}
|
||||
var sockFD [1]int
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Connect, uint32(req.SizeBytes()), req.MarshalUnsafe, NoopUnmarshal, sockFD[:])
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err == nil && sockFD[0] < 0 {
|
||||
err = unix.EBADF
|
||||
}
|
||||
return sockFD[0], err
|
||||
}
|
||||
|
||||
// UnlinkAt makes the UnlinkAt RPC.
|
||||
func (f *ClientFD) UnlinkAt(ctx context.Context, name string, flags uint32) error {
|
||||
req := UnlinkAtReq{
|
||||
DirFD: f.fd,
|
||||
Name: SizedString(name),
|
||||
Flags: primitive.Uint32(flags),
|
||||
}
|
||||
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(UnlinkAt, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// RenameTo makes the RenameAt RPC which renames f to newDirFD directory with
|
||||
// name newName.
|
||||
func (f *ClientFD) RenameTo(ctx context.Context, newDirFD FDID, newName string) error {
|
||||
req := RenameAtReq{
|
||||
Renamed: f.fd,
|
||||
NewDir: newDirFD,
|
||||
NewName: SizedString(newName),
|
||||
}
|
||||
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(RenameAt, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// Getdents64 makes the Getdents64 RPC.
|
||||
func (f *ClientFD) Getdents64(ctx context.Context, count int32) ([]Dirent64, error) {
|
||||
req := Getdents64Req{
|
||||
DirFD: f.fd,
|
||||
Count: count,
|
||||
}
|
||||
|
||||
var resp Getdents64Resp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(Getdents64, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.Dirents, err
|
||||
}
|
||||
|
||||
// ListXattr makes the FListXattr RPC.
|
||||
func (f *ClientFD) ListXattr(ctx context.Context, size uint64) ([]string, error) {
|
||||
req := FListXattrReq{
|
||||
FD: f.fd,
|
||||
Size: size,
|
||||
}
|
||||
|
||||
var resp FListXattrResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FListXattr, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return resp.Xattrs, err
|
||||
}
|
||||
|
||||
// GetXattr makes the FGetXattr RPC.
|
||||
func (f *ClientFD) GetXattr(ctx context.Context, name string, size uint64) (string, error) {
|
||||
req := FGetXattrReq{
|
||||
FD: f.fd,
|
||||
Name: SizedString(name),
|
||||
BufSize: primitive.Uint32(size),
|
||||
}
|
||||
|
||||
var resp FGetXattrResp
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FGetXattr, uint32(req.SizeBytes()), req.MarshalBytes, resp.UnmarshalBytes, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return string(resp.Value), err
|
||||
}
|
||||
|
||||
// SetXattr makes the FSetXattr RPC.
|
||||
func (f *ClientFD) SetXattr(ctx context.Context, name string, value string, flags uint32) error {
|
||||
req := FSetXattrReq{
|
||||
FD: f.fd,
|
||||
Name: SizedString(name),
|
||||
Value: SizedString(value),
|
||||
Flags: primitive.Uint32(flags),
|
||||
}
|
||||
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FSetXattr, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
|
||||
// RemoveXattr makes the FRemoveXattr RPC.
|
||||
func (f *ClientFD) RemoveXattr(ctx context.Context, name string) error {
|
||||
req := FRemoveXattrReq{
|
||||
FD: f.fd,
|
||||
Name: SizedString(name),
|
||||
}
|
||||
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
err := f.client.SndRcvMessage(FRemoveXattr, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return err
|
||||
}
|
||||
@@ -289,6 +289,22 @@ func (c *Connection) RemoveFD(id FDID) {
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveControlFDLocked is the same as RemoveFD with added preconditions.
|
||||
//
|
||||
// Preconditions:
|
||||
// * server's rename mutex must at least be read locked.
|
||||
// * id must be pointing to a control FD.
|
||||
func (c *Connection) RemoveControlFDLocked(id FDID) {
|
||||
c.fdsMu.Lock()
|
||||
fd := c.removeFDLocked(id)
|
||||
c.fdsMu.Unlock()
|
||||
if fd != nil {
|
||||
// Drop the ref held by c. This can take arbitrarily long. So do not hold
|
||||
// c.fdsMu while calling it.
|
||||
fd.(*ControlFD).DecRefLocked()
|
||||
}
|
||||
}
|
||||
|
||||
// removeFDLocked makes c stop tracking the passed FDID. Note that the caller
|
||||
// must drop ref on the returned fd (preferably without holding c.fdsMu).
|
||||
//
|
||||
|
||||
+27
-1
@@ -231,7 +231,7 @@ func (fd *ControlFD) FilePath() string {
|
||||
return fd.FilePathLocked()
|
||||
}
|
||||
|
||||
// FilePathLocked is the same as FilePath with the additonal precondition.
|
||||
// FilePathLocked is the same as FilePath with the additional precondition.
|
||||
//
|
||||
// Precondition: server's rename mutex must be at least read locked.
|
||||
func (fd *ControlFD) FilePathLocked() string {
|
||||
@@ -333,6 +333,25 @@ func (fd *OpenFD) Init(cfd *ControlFD, flags uint32, impl OpenFDImpl) {
|
||||
type ControlFDImpl interface {
|
||||
FD() *ControlFD
|
||||
Close(c *Connection)
|
||||
Stat(c *Connection, comm Communicator) (uint32, error)
|
||||
SetStat(c *Connection, comm Communicator, stat SetStatReq) (uint32, error)
|
||||
Walk(c *Connection, comm Communicator, path StringArray) (uint32, error)
|
||||
WalkStat(c *Connection, comm Communicator, path StringArray) (uint32, error)
|
||||
Open(c *Connection, comm Communicator, flags uint32) (uint32, error)
|
||||
OpenCreate(c *Connection, comm Communicator, mode linux.FileMode, uid UID, gid GID, name string, flags uint32) (uint32, error)
|
||||
Mkdir(c *Connection, comm Communicator, mode linux.FileMode, uid UID, gid GID, name string) (uint32, error)
|
||||
Mknod(c *Connection, comm Communicator, mode linux.FileMode, uid UID, gid GID, name string, minor uint32, major uint32) (uint32, error)
|
||||
Symlink(c *Connection, comm Communicator, name string, target string, uid UID, gid GID) (uint32, error)
|
||||
Link(c *Connection, comm Communicator, dir ControlFDImpl, name string) (uint32, error)
|
||||
StatFS(c *Connection, comm Communicator) (uint32, error)
|
||||
Readlink(c *Connection, comm Communicator) (uint32, error)
|
||||
Connect(c *Connection, comm Communicator, sockType uint32) error
|
||||
Unlink(c *Connection, name string, flags uint32) error
|
||||
RenameLocked(c *Connection, newDir ControlFDImpl, newName string) (func(ControlFDImpl), func(), error)
|
||||
GetXattr(c *Connection, comm Communicator, name string, size uint32) (uint32, error)
|
||||
SetXattr(c *Connection, name string, value string, flags uint32) error
|
||||
ListXattr(c *Connection, comm Communicator, size uint64) (uint32, error)
|
||||
RemoveXattr(c *Connection, comm Communicator, name string) error
|
||||
}
|
||||
|
||||
// OpenFDImpl contains implementation details for a OpenFD. Implementations of
|
||||
@@ -345,4 +364,11 @@ type ControlFDImpl interface {
|
||||
type OpenFDImpl interface {
|
||||
FD() *OpenFD
|
||||
Close(c *Connection)
|
||||
Stat(c *Connection, comm Communicator) (uint32, error)
|
||||
Sync(c *Connection) error
|
||||
Write(c *Connection, comm Communicator, buf []byte, off uint64) (uint32, error)
|
||||
Read(c *Connection, comm Communicator, off uint64, count uint32) (uint32, error)
|
||||
Allocate(c *Connection, mode, off, length uint64) error
|
||||
Flush(c *Connection) error
|
||||
Getdent64(c *Connection, comm Communicator, count uint32, seek0 bool) (uint32, error)
|
||||
}
|
||||
|
||||
+647
-3
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
package(
|
||||
default_visibility = ["//visibility:public"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "testsuite",
|
||||
testonly = True,
|
||||
srcs = ["testsuite.go"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/context",
|
||||
"//pkg/lisafs",
|
||||
"//pkg/unet",
|
||||
"@com_github_syndtr_gocapability//capability:go_default_library",
|
||||
"@org_golang_x_sys//unix:go_default_library",
|
||||
],
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,7 +54,10 @@ go_library(
|
||||
"//pkg/fdnotifier",
|
||||
"//pkg/fspath",
|
||||
"//pkg/hostarch",
|
||||
"//pkg/lisafs",
|
||||
"//pkg/log",
|
||||
"//pkg/marshal",
|
||||
"//pkg/marshal/primitive",
|
||||
"//pkg/metric",
|
||||
"//pkg/p9",
|
||||
"//pkg/refs",
|
||||
|
||||
@@ -222,47 +222,88 @@ func (d *dentry) getDirents(ctx context.Context) ([]vfs.Dirent, error) {
|
||||
off := uint64(0)
|
||||
const count = 64 * 1024 // for consistency with the vfs1 client
|
||||
d.handleMu.RLock()
|
||||
if d.readFile.isNil() {
|
||||
if !d.isReadFileOk() {
|
||||
// This should not be possible because a readable handle should
|
||||
// have been opened when the calling directoryFD was opened.
|
||||
d.handleMu.RUnlock()
|
||||
panic("gofer.dentry.getDirents called without a readable handle")
|
||||
}
|
||||
// shouldSeek0 indicates whether the server should SEEK to 0 before reading
|
||||
// directory entries.
|
||||
shouldSeek0 := true
|
||||
for {
|
||||
p9ds, err := d.readFile.readdir(ctx, off, count)
|
||||
if err != nil {
|
||||
d.handleMu.RUnlock()
|
||||
return nil, err
|
||||
if d.fs.opts.lisaEnabled {
|
||||
countLisa := int32(count)
|
||||
if shouldSeek0 {
|
||||
// See lisafs.Getdents64Req.Count.
|
||||
countLisa = -countLisa
|
||||
shouldSeek0 = false
|
||||
}
|
||||
lisafsDs, err := d.readFDLisa.Getdents64(ctx, countLisa)
|
||||
if err != nil {
|
||||
d.handleMu.RUnlock()
|
||||
return nil, err
|
||||
}
|
||||
if len(lisafsDs) == 0 {
|
||||
d.handleMu.RUnlock()
|
||||
break
|
||||
}
|
||||
for i := range lisafsDs {
|
||||
name := string(lisafsDs[i].Name)
|
||||
if name == "." || name == ".." {
|
||||
continue
|
||||
}
|
||||
dirent := vfs.Dirent{
|
||||
Name: name,
|
||||
Ino: d.fs.inoFromKey(inoKey{
|
||||
ino: uint64(lisafsDs[i].Ino),
|
||||
devMinor: uint32(lisafsDs[i].DevMinor),
|
||||
devMajor: uint32(lisafsDs[i].DevMajor),
|
||||
}),
|
||||
NextOff: int64(len(dirents) + 1),
|
||||
Type: uint8(lisafsDs[i].Type),
|
||||
}
|
||||
dirents = append(dirents, dirent)
|
||||
if realChildren != nil {
|
||||
realChildren[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
p9ds, err := d.readFile.readdir(ctx, off, count)
|
||||
if err != nil {
|
||||
d.handleMu.RUnlock()
|
||||
return nil, err
|
||||
}
|
||||
if len(p9ds) == 0 {
|
||||
d.handleMu.RUnlock()
|
||||
break
|
||||
}
|
||||
for _, p9d := range p9ds {
|
||||
if p9d.Name == "." || p9d.Name == ".." {
|
||||
continue
|
||||
}
|
||||
dirent := vfs.Dirent{
|
||||
Name: p9d.Name,
|
||||
Ino: d.fs.inoFromQIDPath(p9d.QID.Path),
|
||||
NextOff: int64(len(dirents) + 1),
|
||||
}
|
||||
// p9 does not expose 9P2000.U's DMDEVICE, DMNAMEDPIPE, or
|
||||
// DMSOCKET.
|
||||
switch p9d.Type {
|
||||
case p9.TypeSymlink:
|
||||
dirent.Type = linux.DT_LNK
|
||||
case p9.TypeDir:
|
||||
dirent.Type = linux.DT_DIR
|
||||
default:
|
||||
dirent.Type = linux.DT_REG
|
||||
}
|
||||
dirents = append(dirents, dirent)
|
||||
if realChildren != nil {
|
||||
realChildren[p9d.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
off = p9ds[len(p9ds)-1].Offset
|
||||
}
|
||||
if len(p9ds) == 0 {
|
||||
d.handleMu.RUnlock()
|
||||
break
|
||||
}
|
||||
for _, p9d := range p9ds {
|
||||
if p9d.Name == "." || p9d.Name == ".." {
|
||||
continue
|
||||
}
|
||||
dirent := vfs.Dirent{
|
||||
Name: p9d.Name,
|
||||
Ino: d.fs.inoFromQIDPath(p9d.QID.Path),
|
||||
NextOff: int64(len(dirents) + 1),
|
||||
}
|
||||
// p9 does not expose 9P2000.U's DMDEVICE, DMNAMEDPIPE, or
|
||||
// DMSOCKET.
|
||||
switch p9d.Type {
|
||||
case p9.TypeSymlink:
|
||||
dirent.Type = linux.DT_LNK
|
||||
case p9.TypeDir:
|
||||
dirent.Type = linux.DT_DIR
|
||||
default:
|
||||
dirent.Type = linux.DT_REG
|
||||
}
|
||||
dirents = append(dirents, dirent)
|
||||
if realChildren != nil {
|
||||
realChildren[p9d.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
off = p9ds[len(p9ds)-1].Offset
|
||||
}
|
||||
}
|
||||
// Emit entries for synthetic children.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+546
-124
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,7 @@ func TestDestroyIdempotent(t *testing.T) {
|
||||
},
|
||||
syncableDentries: make(map[*dentry]struct{}),
|
||||
inoByQIDPath: make(map[uint64]uint64),
|
||||
inoByKey: make(map[inoKey]uint64),
|
||||
}
|
||||
|
||||
attr := &p9.Attr{
|
||||
|
||||
@@ -17,6 +17,7 @@ package gofer
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/lisafs"
|
||||
"gvisor.dev/gvisor/pkg/p9"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
"gvisor.dev/gvisor/pkg/sentry/hostfd"
|
||||
@@ -26,10 +27,13 @@ import (
|
||||
// handle represents a remote "open file descriptor", consisting of an opened
|
||||
// fid (p9.File) and optionally a host file descriptor.
|
||||
//
|
||||
// If lisafs is being used, fdLisa points to an open file on the server.
|
||||
//
|
||||
// These are explicitly not savable.
|
||||
type handle struct {
|
||||
file p9file
|
||||
fd int32 // -1 if unavailable
|
||||
fdLisa lisafs.ClientFD
|
||||
file p9file
|
||||
fd int32 // -1 if unavailable
|
||||
}
|
||||
|
||||
// Preconditions: read || write.
|
||||
@@ -65,13 +69,47 @@ func openHandle(ctx context.Context, file p9file, read, write, trunc bool) (hand
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Preconditions: read || write.
|
||||
func openHandleLisa(ctx context.Context, fdLisa lisafs.ClientFD, read, write, trunc bool) (handle, error) {
|
||||
var flags uint32
|
||||
switch {
|
||||
case read && write:
|
||||
flags = unix.O_RDWR
|
||||
case read:
|
||||
flags = unix.O_RDONLY
|
||||
case write:
|
||||
flags = unix.O_WRONLY
|
||||
default:
|
||||
panic("tried to open unreadable and unwritable handle")
|
||||
}
|
||||
if trunc {
|
||||
flags |= unix.O_TRUNC
|
||||
}
|
||||
openFD, hostFD, err := fdLisa.OpenAt(ctx, flags)
|
||||
if err != nil {
|
||||
return handle{fd: -1}, err
|
||||
}
|
||||
h := handle{
|
||||
fdLisa: fdLisa.Client().NewFD(openFD),
|
||||
fd: int32(hostFD),
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h *handle) isOpen() bool {
|
||||
if h.fdLisa.Client() != nil {
|
||||
return h.fdLisa.Ok()
|
||||
}
|
||||
return !h.file.isNil()
|
||||
}
|
||||
|
||||
func (h *handle) close(ctx context.Context) {
|
||||
h.file.close(ctx)
|
||||
h.file = p9file{}
|
||||
if h.fdLisa.Client() != nil {
|
||||
h.fdLisa.CloseBatched(ctx)
|
||||
} else {
|
||||
h.file.close(ctx)
|
||||
h.file = p9file{}
|
||||
}
|
||||
if h.fd >= 0 {
|
||||
unix.Close(int(h.fd))
|
||||
h.fd = -1
|
||||
@@ -89,19 +127,27 @@ func (h *handle) readToBlocksAt(ctx context.Context, dsts safemem.BlockSeq, offs
|
||||
return n, err
|
||||
}
|
||||
if dsts.NumBlocks() == 1 && !dsts.Head().NeedSafecopy() {
|
||||
n, err := h.file.readAt(ctx, dsts.Head().ToSlice(), offset)
|
||||
return uint64(n), err
|
||||
if h.fdLisa.Client() != nil {
|
||||
return h.fdLisa.Read(ctx, dsts.Head().ToSlice(), offset)
|
||||
}
|
||||
return h.file.readAt(ctx, dsts.Head().ToSlice(), offset)
|
||||
}
|
||||
// Buffer the read since p9.File.ReadAt() takes []byte.
|
||||
buf := make([]byte, dsts.NumBytes())
|
||||
n, err := h.file.readAt(ctx, buf, offset)
|
||||
var n uint64
|
||||
var err error
|
||||
if h.fdLisa.Client() != nil {
|
||||
n, err = h.fdLisa.Read(ctx, buf, offset)
|
||||
} else {
|
||||
n, err = h.file.readAt(ctx, buf, offset)
|
||||
}
|
||||
if n == 0 {
|
||||
return 0, err
|
||||
}
|
||||
if cp, cperr := safemem.CopySeq(dsts, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(buf[:n]))); cperr != nil {
|
||||
return cp, cperr
|
||||
}
|
||||
return uint64(n), err
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (h *handle) writeFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, offset uint64) (uint64, error) {
|
||||
@@ -115,8 +161,10 @@ func (h *handle) writeFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, o
|
||||
return n, err
|
||||
}
|
||||
if srcs.NumBlocks() == 1 && !srcs.Head().NeedSafecopy() {
|
||||
n, err := h.file.writeAt(ctx, srcs.Head().ToSlice(), offset)
|
||||
return uint64(n), err
|
||||
if h.fdLisa.Client() != nil {
|
||||
return h.fdLisa.Write(ctx, srcs.Head().ToSlice(), offset)
|
||||
}
|
||||
return h.file.writeAt(ctx, srcs.Head().ToSlice(), offset)
|
||||
}
|
||||
// Buffer the write since p9.File.WriteAt() takes []byte.
|
||||
buf := make([]byte, srcs.NumBytes())
|
||||
@@ -124,12 +172,18 @@ func (h *handle) writeFromBlocksAt(ctx context.Context, srcs safemem.BlockSeq, o
|
||||
if cp == 0 {
|
||||
return 0, cperr
|
||||
}
|
||||
n, err := h.file.writeAt(ctx, buf[:cp], offset)
|
||||
var n uint64
|
||||
var err error
|
||||
if h.fdLisa.Client() != nil {
|
||||
n, err = h.fdLisa.Write(ctx, buf[:cp], offset)
|
||||
} else {
|
||||
n, err = h.file.writeAt(ctx, buf[:cp], offset)
|
||||
}
|
||||
// err takes precedence over cperr.
|
||||
if err != nil {
|
||||
return uint64(n), err
|
||||
return n, err
|
||||
}
|
||||
return uint64(n), cperr
|
||||
return n, cperr
|
||||
}
|
||||
|
||||
type handleReadWriter struct {
|
||||
|
||||
@@ -141,18 +141,18 @@ func (f p9file) open(ctx context.Context, flags p9.OpenFlags) (*fd.FD, p9.QID, u
|
||||
return fdobj, qid, iounit, err
|
||||
}
|
||||
|
||||
func (f p9file) readAt(ctx context.Context, p []byte, offset uint64) (int, error) {
|
||||
func (f p9file) readAt(ctx context.Context, p []byte, offset uint64) (uint64, error) {
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
n, err := f.file.ReadAt(p, offset)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return n, err
|
||||
return uint64(n), err
|
||||
}
|
||||
|
||||
func (f p9file) writeAt(ctx context.Context, p []byte, offset uint64) (int, error) {
|
||||
func (f p9file) writeAt(ctx context.Context, p []byte, offset uint64) (uint64, error) {
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
n, err := f.file.WriteAt(p, offset)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
return n, err
|
||||
return uint64(n), err
|
||||
}
|
||||
|
||||
func (f p9file) fsync(ctx context.Context) error {
|
||||
|
||||
@@ -98,6 +98,12 @@ func (fd *regularFileFD) OnClose(ctx context.Context) error {
|
||||
}
|
||||
d.handleMu.RLock()
|
||||
defer d.handleMu.RUnlock()
|
||||
if d.fs.opts.lisaEnabled {
|
||||
if !d.writeFDLisa.Ok() {
|
||||
return nil
|
||||
}
|
||||
return d.writeFDLisa.Flush(ctx)
|
||||
}
|
||||
if d.writeFile.isNil() {
|
||||
return nil
|
||||
}
|
||||
@@ -110,6 +116,9 @@ func (fd *regularFileFD) Allocate(ctx context.Context, mode, offset, length uint
|
||||
return d.doAllocate(ctx, offset, length, func() error {
|
||||
d.handleMu.RLock()
|
||||
defer d.handleMu.RUnlock()
|
||||
if d.fs.opts.lisaEnabled {
|
||||
return d.writeFDLisa.Allocate(ctx, mode, offset, length)
|
||||
}
|
||||
return d.writeFile.allocate(ctx, p9.ToAllocateMode(mode), offset, length)
|
||||
})
|
||||
}
|
||||
@@ -282,8 +291,19 @@ func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, off
|
||||
// changes to the host.
|
||||
if newMode := vfs.ClearSUIDAndSGID(oldMode); newMode != oldMode {
|
||||
atomic.StoreUint32(&d.mode, newMode)
|
||||
if err := d.file.setAttr(ctx, p9.SetAttrMask{Permissions: true}, p9.SetAttr{Permissions: p9.FileMode(newMode)}); err != nil {
|
||||
return 0, offset, err
|
||||
if d.fs.opts.lisaEnabled {
|
||||
stat := linux.Statx{Mask: linux.STATX_MODE, Mode: uint16(newMode)}
|
||||
failureMask, failureErr, err := d.controlFDLisa.SetStat(ctx, &stat)
|
||||
if err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
if failureMask != 0 {
|
||||
return 0, offset, failureErr
|
||||
}
|
||||
} else {
|
||||
if err := d.file.setAttr(ctx, p9.SetAttrMask{Permissions: true}, p9.SetAttr{Permissions: p9.FileMode(newMode)}); err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -677,7 +697,7 @@ func regularFileSeekLocked(ctx context.Context, d *dentry, fdOffset, offset int6
|
||||
|
||||
// Sync implements vfs.FileDescriptionImpl.Sync.
|
||||
func (fd *regularFileFD) Sync(ctx context.Context) error {
|
||||
return fd.dentry().syncCachedFile(ctx, false /* lowSyncExpectations */)
|
||||
return fd.dentry().syncCachedFile(ctx, false /* forFilesystemSync */, nil /* accFsyncFDIDsLisa */)
|
||||
}
|
||||
|
||||
// ConfigureMMap implements vfs.FileDescriptionImpl.ConfigureMMap.
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
package gofer
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/p9"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
)
|
||||
@@ -234,28 +236,54 @@ func (fs *filesystem) revalidateHelper(ctx context.Context, vfsObj *vfs.VirtualF
|
||||
}
|
||||
// Lock metadata on all dentries *before* getting attributes for them.
|
||||
state.lockAllMetadata()
|
||||
stats, err := state.start.file.multiGetAttr(ctx, state.names)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
var (
|
||||
stats []p9.FullStat
|
||||
statsLisa []linux.Statx
|
||||
numStats int
|
||||
)
|
||||
if fs.opts.lisaEnabled {
|
||||
var err error
|
||||
statsLisa, err = state.start.controlFDLisa.WalkStat(ctx, state.names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numStats = len(statsLisa)
|
||||
} else {
|
||||
var err error
|
||||
stats, err = state.start.file.multiGetAttr(ctx, state.names)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numStats = len(stats)
|
||||
}
|
||||
|
||||
i := -1
|
||||
for d := state.popFront(); d != nil; d = state.popFront() {
|
||||
i++
|
||||
found := i < len(stats)
|
||||
found := i < numStats
|
||||
if i == 0 && len(state.names[0]) == 0 {
|
||||
if found && !d.isSynthetic() {
|
||||
// First dentry is where the search is starting, just update attributes
|
||||
// since it cannot be replaced.
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: acquired by lockAllMetadata.
|
||||
if fs.opts.lisaEnabled {
|
||||
d.updateFromLisaStatLocked(&statsLisa[i]) // +checklocksforce: acquired by lockAllMetadata.
|
||||
} else {
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: acquired by lockAllMetadata.
|
||||
}
|
||||
}
|
||||
d.metadataMu.Unlock() // +checklocksforce: see above.
|
||||
continue
|
||||
}
|
||||
|
||||
// Note that synthetic dentries will always fails the comparison check
|
||||
// below.
|
||||
if !found || d.qidPath != stats[i].QID.Path {
|
||||
// Note that synthetic dentries will always fail this comparison check.
|
||||
var shouldInvalidate bool
|
||||
if fs.opts.lisaEnabled {
|
||||
shouldInvalidate = !found || d.inoKey != inoKeyFromStat(&statsLisa[i])
|
||||
} else {
|
||||
shouldInvalidate = !found || d.qidPath != stats[i].QID.Path
|
||||
}
|
||||
if shouldInvalidate {
|
||||
d.metadataMu.Unlock() // +checklocksforce: see above.
|
||||
if !found && d.isSynthetic() {
|
||||
// We have a synthetic file, and no remote file has arisen to replace
|
||||
@@ -298,7 +326,11 @@ func (fs *filesystem) revalidateHelper(ctx context.Context, vfsObj *vfs.VirtualF
|
||||
}
|
||||
|
||||
// The file at this path hasn't changed. Just update cached metadata.
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: see above.
|
||||
if fs.opts.lisaEnabled {
|
||||
d.updateFromLisaStatLocked(&statsLisa[i]) // +checklocksforce: see above.
|
||||
} else {
|
||||
d.updateFromP9AttrsLocked(stats[i].Valid, &stats[i].Attr) // +checklocksforce: see above.
|
||||
}
|
||||
d.metadataMu.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
||||
"gvisor.dev/gvisor/pkg/fdnotifier"
|
||||
"gvisor.dev/gvisor/pkg/hostarch"
|
||||
"gvisor.dev/gvisor/pkg/lisafs"
|
||||
"gvisor.dev/gvisor/pkg/p9"
|
||||
"gvisor.dev/gvisor/pkg/refsvfs2"
|
||||
"gvisor.dev/gvisor/pkg/safemem"
|
||||
@@ -112,10 +113,19 @@ func (d *dentry) prepareSaveRecursive(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !d.readFile.isNil() || !d.writeFile.isNil() {
|
||||
d.fs.savedDentryRW[d] = savedDentryRW{
|
||||
read: !d.readFile.isNil(),
|
||||
write: !d.writeFile.isNil(),
|
||||
if d.fs.opts.lisaEnabled {
|
||||
if d.readFDLisa.Ok() || d.writeFDLisa.Ok() {
|
||||
d.fs.savedDentryRW[d] = savedDentryRW{
|
||||
read: d.readFDLisa.Ok(),
|
||||
write: d.writeFDLisa.Ok(),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !d.readFile.isNil() || !d.writeFile.isNil() {
|
||||
d.fs.savedDentryRW[d] = savedDentryRW{
|
||||
read: !d.readFile.isNil(),
|
||||
write: !d.writeFile.isNil(),
|
||||
}
|
||||
}
|
||||
}
|
||||
d.dirMu.Lock()
|
||||
@@ -177,25 +187,37 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
|
||||
return fmt.Errorf("no server FD available for filesystem with unique ID %q", fs.iopts.UniqueID)
|
||||
}
|
||||
fs.opts.fd = fd
|
||||
if err := fs.dial(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
fs.inoByQIDPath = make(map[uint64]uint64)
|
||||
fs.inoByKey = make(map[inoKey]uint64)
|
||||
|
||||
// Restore the filesystem root.
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
attached, err := fs.client.Attach(fs.opts.aname)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attachFile := p9file{attached}
|
||||
qid, attrMask, attr, err := attachFile.getAttr(ctx, dentryAttrMask())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.root.restoreFile(ctx, attachFile, qid, attrMask, &attr, &opts); err != nil {
|
||||
return err
|
||||
if fs.opts.lisaEnabled {
|
||||
rootInode, err := fs.initClientLisa(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.root.restoreFileLisa(ctx, rootInode, &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := fs.dial(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore the filesystem root.
|
||||
ctx.UninterruptibleSleepStart(false)
|
||||
attached, err := fs.client.Attach(fs.opts.aname)
|
||||
ctx.UninterruptibleSleepFinish(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attachFile := p9file{attached}
|
||||
qid, attrMask, attr, err := attachFile.getAttr(ctx, dentryAttrMask())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := fs.root.restoreFile(ctx, attachFile, qid, attrMask, &attr, &opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Restore remaining dentries.
|
||||
@@ -283,6 +305,55 @@ func (d *dentry) restoreFile(ctx context.Context, file p9file, qid p9.QID, attrM
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *dentry) restoreFileLisa(ctx context.Context, inode *lisafs.Inode, opts *vfs.CompleteRestoreOptions) error {
|
||||
d.controlFDLisa = d.fs.clientLisa.NewFD(inode.ControlFD)
|
||||
|
||||
// Gofers do not preserve inoKey across checkpoint/restore, so:
|
||||
//
|
||||
// - We must assume that the remote filesystem did not change in a way that
|
||||
// would invalidate dentries, since we can't revalidate dentries by
|
||||
// checking inoKey.
|
||||
//
|
||||
// - We need to associate the new inoKey with the existing d.ino.
|
||||
d.inoKey = inoKeyFromStat(&inode.Stat)
|
||||
d.fs.inoMu.Lock()
|
||||
d.fs.inoByKey[d.inoKey] = d.ino
|
||||
d.fs.inoMu.Unlock()
|
||||
|
||||
// Check metadata stability before updating metadata.
|
||||
d.metadataMu.Lock()
|
||||
defer d.metadataMu.Unlock()
|
||||
if d.isRegularFile() {
|
||||
if opts.ValidateFileSizes {
|
||||
if inode.Stat.Mask&linux.STATX_SIZE != 0 {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(d))
|
||||
}
|
||||
if d.size != inode.Stat.Size {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d), d.size, inode.Stat.Size)
|
||||
}
|
||||
}
|
||||
if opts.ValidateFileModificationTimestamps {
|
||||
if inode.Stat.Mask&linux.STATX_MTIME != 0 {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(d))
|
||||
}
|
||||
if want := dentryTimestampFromLisa(inode.Stat.Mtime); d.mtime != want {
|
||||
return fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d), linux.NsecToStatxTimestamp(d.mtime), linux.NsecToStatxTimestamp(want))
|
||||
}
|
||||
}
|
||||
}
|
||||
if !d.cachedMetadataAuthoritative() {
|
||||
d.updateFromLisaStatLocked(&inode.Stat)
|
||||
}
|
||||
|
||||
if rw, ok := d.fs.savedDentryRW[d]; ok {
|
||||
if err := d.ensureSharedHandle(ctx, rw.read, rw.write, false /* trunc */); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preconditions: d is not synthetic.
|
||||
func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
|
||||
for _, child := range d.children {
|
||||
@@ -305,19 +376,35 @@ func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.Comp
|
||||
// only be detected by checking filesystem.syncableDentries). d.parent has been
|
||||
// restored.
|
||||
func (d *dentry) restoreRecursive(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
|
||||
qid, file, attrMask, attr, err := d.parent.file.walkGetAttrOne(ctx, d.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.restoreFile(ctx, file, qid, attrMask, &attr, opts); err != nil {
|
||||
return err
|
||||
if d.fs.opts.lisaEnabled {
|
||||
inode, err := d.parent.controlFDLisa.Walk(ctx, d.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.restoreFileLisa(ctx, inode, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
qid, file, attrMask, attr, err := d.parent.file.walkGetAttrOne(ctx, d.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.restoreFile(ctx, file, qid, attrMask, &attr, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return d.restoreDescendantsRecursive(ctx, opts)
|
||||
}
|
||||
|
||||
func (fd *specialFileFD) completeRestore(ctx context.Context) error {
|
||||
d := fd.dentry()
|
||||
h, err := openHandle(ctx, d.file, fd.vfsfd.IsReadable(), fd.vfsfd.IsWritable(), false /* trunc */)
|
||||
var h handle
|
||||
var err error
|
||||
if d.fs.opts.lisaEnabled {
|
||||
h, err = openHandleLisa(ctx, d.controlFDLisa, fd.vfsfd.IsReadable(), fd.vfsfd.IsWritable(), false /* trunc */)
|
||||
} else {
|
||||
h, err = openHandle(ctx, d.file, fd.vfsfd.IsReadable(), fd.vfsfd.IsWritable(), false /* trunc */)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user