[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:
Ayush Ranjan
2021-09-21 14:09:18 -07:00
committed by gVisor bot
parent e819029f3a
commit 6fccc18560
30 changed files with 5356 additions and 368 deletions
+3 -1
View File
@@ -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.
+1
View File
@@ -57,6 +57,7 @@ go_library(
srcs = [
"channel.go",
"client.go",
"client_file.go",
"communicator.go",
"connection.go",
"control_fd_list.go",
+55
View File
@@ -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
+475
View File
@@ -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
}
+16
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+20
View File
@@ -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
+3
View File
@@ -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",
+75 -34
View File
@@ -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
File diff suppressed because it is too large Load Diff
+1
View File
@@ -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{
+67 -13
View File
@@ -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 {
+4 -4
View File
@@ -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 {
+23 -3
View File
@@ -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.
+41 -9
View File
@@ -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()
}
+115 -28
View File
@@ -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