diff --git a/pkg/lisafs/client_file.go b/pkg/lisafs/client_file.go index 7024f0a6c..2e975f416 100644 --- a/pkg/lisafs/client_file.go +++ b/pkg/lisafs/client_file.go @@ -84,7 +84,7 @@ func (f *ClientFD) OpenAt(ctx context.Context, flags uint32) (FDID, int, error) ctx.UninterruptibleSleepStart(false) err := f.client.SndRcvMessage(OpenAt, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, respFD[:]) ctx.UninterruptibleSleepFinish(false) - return resp.NewFD, respFD[0], err + return resp.OpenFD, respFD[0], err } // OpenCreateAt makes the OpenCreateAt RPC. diff --git a/pkg/lisafs/fd.go b/pkg/lisafs/fd.go index eab21de04..0d743ae4a 100644 --- a/pkg/lisafs/fd.go +++ b/pkg/lisafs/fd.go @@ -333,25 +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 + Stat(c *Connection) (linux.Statx, error) + SetStat(c *Connection, stat SetStatReq) (uint32, error) + Walk(c *Connection, path StringArray, recordInode func(Inode)) (WalkStatus, error) + WalkStat(c *Connection, path StringArray, recordStat func(linux.Statx)) error + Open(c *Connection, flags uint32) (FDID, int, error) + OpenCreate(c *Connection, mode linux.FileMode, uid UID, gid GID, name string, flags uint32) (Inode, FDID, int, error) + Mkdir(c *Connection, mode linux.FileMode, uid UID, gid GID, name string) (Inode, error) + Mknod(c *Connection, mode linux.FileMode, uid UID, gid GID, name string, minor uint32, major uint32) (Inode, error) + Symlink(c *Connection, name string, target string, uid UID, gid GID) (Inode, error) + Link(c *Connection, dir ControlFDImpl, name string) (Inode, error) + StatFS(c *Connection) (StatFS, error) + Readlink(c *Connection, getLinkBuf func(uint32) []byte) (uint32, error) + Connect(c *Connection, sockType uint32) (int, 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) + GetXattr(c *Connection, name string, dataBuf []byte) (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 + ListXattr(c *Connection, size uint64) (StringArray, error) + RemoveXattr(c *Connection, name string) error } // OpenFDImpl contains implementation details for a OpenFD. Implementations of @@ -364,11 +364,11 @@ type ControlFDImpl interface { type OpenFDImpl interface { FD() *OpenFD Close(c *Connection) - Stat(c *Connection, comm Communicator) (uint32, error) + Stat(c *Connection) (linux.Statx, 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) + Write(c *Connection, buf []byte, off uint64) (uint64, error) + Read(c *Connection, off uint64, dataBuf []byte) (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) + Getdent64(c *Connection, count uint32, seek0 bool, recordDirent func(Dirent64)) error } diff --git a/pkg/lisafs/handlers.go b/pkg/lisafs/handlers.go index 4fa316fe5..502e13564 100644 --- a/pkg/lisafs/handlers.go +++ b/pkg/lisafs/handlers.go @@ -16,20 +16,25 @@ package lisafs import ( "fmt" + "math" "path" "path/filepath" "strings" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/flipcall" "gvisor.dev/gvisor/pkg/fspath" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/marshal/primitive" + "gvisor.dev/gvisor/pkg/p9" ) const ( allowedOpenFlags = unix.O_ACCMODE | unix.O_TRUNC setStatSupportedMask = unix.STATX_MODE | unix.STATX_UID | unix.STATX_GID | unix.STATX_SIZE | unix.STATX_ATIME | unix.STATX_MTIME + // unixDirentMaxSize is the maximum size of unix.Dirent for amd64. + unixDirentMaxSize = 280 ) // RPCHandler defines a handler that is invoked when the associated message is @@ -172,14 +177,21 @@ func FStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, } defer fd.DecRef(nil) + var resp linux.Statx switch t := fd.(type) { case *ControlFD: - return t.impl.Stat(c, comm) + resp, err = t.impl.Stat(c) case *OpenFD: - return t.impl.Stat(c, comm) + resp, err = t.impl.Stat(c) default: panic(fmt.Sprintf("unknown fd type %T", t)) } + if err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // SetStatHandler handles the SetStat RPC. @@ -203,7 +215,14 @@ func SetStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32 return 0, unix.EPERM } - return fd.impl.SetStat(c, comm, req) + failureMask, failureErr := fd.impl.SetStat(c, req) + resp := SetStatResp{ + FailureMask: failureMask, + FailureErrNo: uint32(p9.ExtractErrno(failureErr)), + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // WalkHandler handles the Walk RPC. @@ -227,7 +246,33 @@ func WalkHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, e } } - return fd.impl.Walk(c, comm, req.Path) + // We need to generate inodes for each component walked. We will manually + // marshal the inodes into the payload buffer as they are generated to avoid + // the slice allocation. The memory format should be WalkResp's. + var ( + status WalkStatus + numInodes primitive.Uint32 + ) + maxPayloadSize := status.SizeBytes() + numInodes.SizeBytes() + (len(req.Path) * (*Inode)(nil).SizeBytes()) + if maxPayloadSize > math.MaxUint32 { + // Too much to walk, can't do. + return 0, unix.EIO + } + payloadBuf := comm.PayloadBuf(uint32(maxPayloadSize)) + payloadPos := status.SizeBytes() + numInodes.SizeBytes() + if status, err = fd.impl.Walk(c, req.Path, func(i Inode) { + i.MarshalUnsafe(payloadBuf[payloadPos:]) + payloadPos += i.SizeBytes() + numInodes++ + }); err != nil { + return 0, err + } + + // WalkResp writes the walk status followed by the number of inodes in the + // beginning. + payloadBuf = status.MarshalUnsafe(payloadBuf) + numInodes.MarshalUnsafe(payloadBuf) + return uint32(payloadPos), nil } // WalkStatHandler handles the WalkStat RPC. @@ -260,7 +305,28 @@ func WalkStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint3 } } - return fd.impl.WalkStat(c, comm, req.Path) + // We will manually marshal the statx results into the payload buffer as they + // are generated to avoid the slice allocation. The memory format should be + // the same as WalkStatResp's. + var numStats primitive.Uint32 + maxPayloadSize := numStats.SizeBytes() + (len(req.Path) * linux.SizeOfStatx) + if maxPayloadSize > math.MaxUint32 { + // Too much to walk, can't do. + return 0, unix.EIO + } + payloadBuf := comm.PayloadBuf(uint32(maxPayloadSize)) + payloadPos := numStats.SizeBytes() + if err = fd.impl.WalkStat(c, req.Path, func(s linux.Statx) { + s.MarshalUnsafe(payloadBuf[payloadPos:]) + payloadPos += s.SizeBytes() + numStats++ + }); err != nil { + return 0, err + } + + // WalkStatResp writes the number of stats in the beginning. + numStats.MarshalUnsafe(payloadBuf) + return uint32(payloadPos), nil } // OpenAtHandler handles the OpenAt RPC. @@ -294,7 +360,22 @@ func OpenAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, } } - return fd.impl.Open(c, comm, req.Flags) + var ( + resp OpenAtResp + hostOpenFD int + ) + resp.OpenFD, hostOpenFD, err = fd.impl.Open(c, req.Flags) + if err != nil { + return 0, err + } + if hostOpenFD >= 0 { + if err := comm.DonateFD(hostOpenFD); err != nil { + return 0, err + } + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // OpenCreateAtHandler handles the OpenCreateAt RPC. @@ -327,7 +408,22 @@ func OpenCreateAtHandler(c *Connection, comm Communicator, payloadLen uint32) (u return 0, unix.ENOTDIR } - return fd.impl.OpenCreate(c, comm, req.Mode, req.UID, req.GID, name, uint32(req.Flags)) + var ( + resp OpenCreateAtResp + hostOpenFD int + ) + resp.Child, resp.NewFD, hostOpenFD, err = fd.impl.OpenCreate(c, req.Mode, req.UID, req.GID, name, uint32(req.Flags)) + if err != nil { + return 0, err + } + if hostOpenFD >= 0 { + if err := comm.DonateFD(hostOpenFD); err != nil { + return 0, err + } + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // CloseHandler handles the Close RPC. @@ -392,7 +488,14 @@ func PWriteHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, if !fd.writable { return 0, unix.EBADF } - return fd.impl.Write(c, comm, req.Buf, uint64(req.Offset)) + var resp PWriteResp + resp.Count, err = fd.impl.Write(c, req.Buf, uint64(req.Offset)) + if err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // PReadHandler handles the PRead RPC. @@ -410,7 +513,22 @@ func PReadHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, if !fd.readable { return 0, unix.EBADF } - return fd.impl.Read(c, comm, req.Offset, req.Count) + + // To save an allocation and a copy, we directly read into the payload + // buffer. The rest of the response message is manually marshalled. + var resp PReadResp + respMetaSize := uint32(resp.NumBytes.SizeBytes()) + payloadBuf := comm.PayloadBuf(respMetaSize + req.Count) + n, err := fd.impl.Read(c, req.Offset, payloadBuf[respMetaSize:]) + if err != nil { + return 0, err + } + + // Write the response metadata onto the payload buffer. The response contents + // already have been written immediately after it. + resp.NumBytes = primitive.Uint32(n) + resp.NumBytes.MarshalUnsafe(payloadBuf) + return respMetaSize + uint32(n), nil } // MkdirAtHandler handles the MkdirAt RPC. @@ -436,7 +554,14 @@ func MkdirAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32 if !fd.IsDir() { return 0, unix.ENOTDIR } - return fd.impl.Mkdir(c, comm, req.Mode, req.UID, req.GID, name) + var resp MkdirAtResp + resp.ChildDir, err = fd.impl.Mkdir(c, req.Mode, req.UID, req.GID, name) + if err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // MknodAtHandler handles the MknodAt RPC. @@ -462,7 +587,14 @@ func MknodAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32 if !fd.IsDir() { return 0, unix.ENOTDIR } - return fd.impl.Mknod(c, comm, req.Mode, req.UID, req.GID, name, uint32(req.Minor), uint32(req.Major)) + var resp MknodAtResp + resp.Child, err = fd.impl.Mknod(c, req.Mode, req.UID, req.GID, name, uint32(req.Minor), uint32(req.Major)) + if err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // SymlinkAtHandler handles the SymlinkAt RPC. @@ -488,7 +620,14 @@ func SymlinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint if !fd.IsDir() { return 0, unix.ENOTDIR } - return fd.impl.Symlink(c, comm, name, string(req.Target), req.UID, req.GID) + var resp SymlinkAtResp + resp.Symlink, err = fd.impl.Symlink(c, name, string(req.Target), req.UID, req.GID) + if err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // LinkAtHandler handles the LinkAt RPC. @@ -519,7 +658,14 @@ func LinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, if err != nil { return 0, err } - return targetFD.impl.Link(c, comm, fd.impl, name) + var resp LinkAtResp + resp.Link, err = targetFD.impl.Link(c, fd.impl, name) + if err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // FStatFSHandler handles the FStatFS RPC. @@ -534,7 +680,14 @@ func FStatFSHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32 return 0, err } defer fd.DecRef(nil) - return fd.impl.StatFS(c, comm) + var resp StatFS + resp, err = fd.impl.StatFS(c) + if err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalUnsafe(comm.PayloadBuf(respLen)) + return respLen, nil } // FAllocateHandler handles the FAllocate RPC. @@ -573,7 +726,21 @@ func ReadLinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uin if !fd.IsSymlink() { return 0, unix.EINVAL } - return fd.impl.Readlink(c, comm) + + // We will manually marshal ReadLinkAtResp, which just contains a + // SizedString. Let Readlinkat directly write into the payload buffer and + // manually write the string size before it. + var linkLen primitive.Uint32 + respMetaSize := uint32(linkLen.SizeBytes()) + n, err := fd.impl.Readlink(c, func(dataLen uint32) []byte { + return comm.PayloadBuf(dataLen + respMetaSize)[respMetaSize:] + }) + if err != nil { + return 0, err + } + linkLen = primitive.Uint32(n) + linkLen.MarshalUnsafe(comm.PayloadBuf(respMetaSize)) + return respMetaSize + n, nil } // FlushHandler handles the Flush RPC. @@ -607,7 +774,14 @@ func ConnectHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32 if !fd.IsSocket() { return 0, unix.ENOTSOCK } - return 0, fd.impl.Connect(c, comm, req.SockType) + sock, err := fd.impl.Connect(c, req.SockType) + if err != nil { + return 0, err + } + if err := comm.DonateFD(sock); err != nil { + return 0, err + } + return 0, nil } // UnlinkAtHandler handles the UnlinkAt RPC. @@ -745,7 +919,34 @@ func Getdents64Handler(c *Connection, comm Communicator, payloadLen uint32) (uin seek0 = true req.Count = -req.Count } - return fd.impl.Getdent64(c, comm, uint32(req.Count), seek0) + + // We will manually marshal the response Getdents64Resp. + + // numDirents is the number of dirents marshalled into the payload. + var numDirents primitive.Uint32 + // The payload starts with numDirents, dirents go right after that. + // payloadBufPos represents the position at which to write the next dirent. + payloadBufPos := uint32(numDirents.SizeBytes()) + // Request enough payloadBuf for 10 dirents, we will extend when needed. + // unix.Dirent is 280 bytes for amd64. + payloadBuf := comm.PayloadBuf(payloadBufPos + 10*unixDirentMaxSize) + if err := fd.impl.Getdent64(c, uint32(req.Count), seek0, func(dirent Dirent64) { + // Paste the dirent into the payload buffer without having the dirent + // escape. Request a larger buffer if needed. + if int(payloadBufPos)+dirent.SizeBytes() > len(payloadBuf) { + // Ask for 10 large dirents worth of more space. + payloadBuf = comm.PayloadBuf(payloadBufPos + 10*unixDirentMaxSize) + } + dirent.MarshalBytes(payloadBuf[payloadBufPos:]) + payloadBufPos += uint32(dirent.SizeBytes()) + numDirents++ + }); err != nil { + return 0, err + } + + // The number of dirents goes at the beginning of the payload. + numDirents.MarshalUnsafe(payloadBuf) + return payloadBufPos, nil } // FGetXattrHandler handles the FGetXattr RPC. @@ -760,7 +961,19 @@ func FGetXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint return 0, err } defer fd.DecRef(nil) - return fd.impl.GetXattr(c, comm, string(req.Name), uint32(req.BufSize)) + + // Manually marshal FGetXattrResp to avoid allocations and copying. + // FGetXattrResp simply is a wrapper around SizedString. + var valueLen primitive.Uint32 + respMetaSize := uint32(valueLen.SizeBytes()) + payloadBuf := comm.PayloadBuf(respMetaSize + uint32(req.BufSize)) + n, err := fd.impl.GetXattr(c, string(req.Name), payloadBuf[respMetaSize:]) + if err != nil { + return 0, err + } + valueLen = primitive.Uint32(n) + valueLen.MarshalBytes(payloadBuf) + return respMetaSize + n, nil } // FSetXattrHandler handles the FSetXattr RPC. @@ -793,7 +1006,13 @@ func FListXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uin return 0, err } defer fd.DecRef(nil) - return fd.impl.ListXattr(c, comm, req.Size) + var resp FListXattrResp + if resp.Xattrs, err = fd.impl.ListXattr(c, req.Size); err != nil { + return 0, err + } + respLen := uint32(resp.SizeBytes()) + resp.MarshalBytes(comm.PayloadBuf(respLen)) + return respLen, nil } // FRemoveXattrHandler handles the FRemoveXattr RPC. @@ -811,7 +1030,7 @@ func FRemoveXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (u return 0, err } defer fd.DecRef(nil) - return 0, fd.impl.RemoveXattr(c, comm, string(req.Name)) + return 0, fd.impl.RemoveXattr(c, string(req.Name)) } // checkSafeName validates the name and returns nil or returns an error. diff --git a/pkg/lisafs/message.go b/pkg/lisafs/message.go index bfcb9426a..ea5acae05 100644 --- a/pkg/lisafs/message.go +++ b/pkg/lisafs/message.go @@ -529,7 +529,7 @@ type OpenAtReq struct { // // +marshal boundCheck type OpenAtResp struct { - NewFD FDID + OpenFD FDID } // +marshal diff --git a/pkg/lisafs/testsuite/testsuite.go b/pkg/lisafs/testsuite/testsuite.go index 5fc7c364d..9b4d0f0a1 100644 --- a/pkg/lisafs/testsuite/testsuite.go +++ b/pkg/lisafs/testsuite/testsuite.go @@ -141,14 +141,14 @@ func openCreateFile(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, n } func openFile(ctx context.Context, t *testing.T, fdLisa lisafs.ClientFD, flags uint32, isReg bool) (lisafs.ClientFD, int) { - newFD, hostFD, err := fdLisa.OpenAt(ctx, flags) + openFD, hostFD, err := fdLisa.OpenAt(ctx, flags) if err != nil { t.Fatalf("OpenAt failed: %v", err) } if hostFD == -1 && isReg { t.Error("no host FD donated") } - return fdLisa.Client().NewFD(newFD), hostFD + return fdLisa.Client().NewFD(openFD), hostFD } func unlinkFile(ctx context.Context, t *testing.T, dir lisafs.ClientFD, name string, isDir bool) { @@ -592,7 +592,8 @@ func testGetdents(ctx context.Context, t *testing.T, tester Tester, root lisafs. } // Use opened directory FD for getdents. - openDirFile, _ := openFile(ctx, t, tempDir, unix.O_RDONLY, false /* isReg */) + openDirFile, dirHostFD := openFile(ctx, t, tempDir, unix.O_RDONLY, false /* isReg */) + unix.Close(dirHostFD) defer closeFD(ctx, t, openDirFile) dirents := make([]lisafs.Dirent64, 0, n) diff --git a/runsc/fsgofer/lisafs.go b/runsc/fsgofer/lisafs.go index 9e2d6aefc..d96fc5f20 100644 --- a/runsc/fsgofer/lisafs.go +++ b/runsc/fsgofer/lisafs.go @@ -16,7 +16,6 @@ package fsgofer import ( "io" - "math" "path" "strconv" "sync/atomic" @@ -28,7 +27,6 @@ import ( "gvisor.dev/gvisor/pkg/lisafs" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/p9" ) // LisafsServer implements lisafs.ServerImpl for fsgofer. @@ -174,25 +172,18 @@ func (fd *controlFDLisa) Close(c *lisafs.Connection) { } // Stat implements lisafs.ControlFDImpl.Stat. -func (fd *controlFDLisa) Stat(c *lisafs.Connection, comm lisafs.Communicator) (uint32, error) { +func (fd *controlFDLisa) Stat(c *lisafs.Connection) (linux.Statx, error) { var resp linux.Statx - if err := fstatTo(fd.hostFD, &resp); err != nil { - return 0, err - } - - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + return resp, fstatTo(fd.hostFD, &resp) } // SetStat implements lisafs.ControlFDImpl.SetStat. -func (fd *controlFDLisa) SetStat(c *lisafs.Connection, comm lisafs.Communicator, stat lisafs.SetStatReq) (uint32, error) { - var resp lisafs.SetStatResp +func (fd *controlFDLisa) SetStat(c *lisafs.Connection, stat lisafs.SetStatReq) (failureMask uint32, failureErr error) { if stat.Mask&unix.STATX_MODE != 0 { if err := unix.Fchmod(fd.hostFD, stat.Mode&^unix.S_IFMT); err != nil { - log.Debugf("SetStat fchmod failed %q, err: %v", fd.FilePath(), err) - resp.FailureMask |= unix.STATX_MODE - resp.FailureErrNo = uint32(p9.ExtractErrno(err)) + log.Warningf("SetStat fchmod failed %q, err: %v", fd.FilePath(), err) + failureMask |= unix.STATX_MODE + failureErr = err } } @@ -203,9 +194,9 @@ func (fd *controlFDLisa) SetStat(c *lisafs.Connection, comm lisafs.Communicator, err = unix.Ftruncate(writableFD, int64(stat.Size)) } if err != nil { - log.Debugf("SetStat ftruncate failed %q, err: %v", fd.FilePath(), err) - resp.FailureMask |= unix.STATX_SIZE - resp.FailureErrNo = uint32(p9.ExtractErrno(err)) + log.Warningf("SetStat ftruncate failed %q, err: %v", fd.FilePath(), err) + failureMask |= unix.STATX_SIZE + failureErr = err } } @@ -229,9 +220,9 @@ func (fd *controlFDLisa) SetStat(c *lisafs.Connection, comm lisafs.Communicator, // name. c.Server().WithRenameReadLock(func() error { if err := utimensat(fd.ParentLocked().(*controlFDLisa).hostFD, fd.NameLocked(), utimes, unix.AT_SYMLINK_NOFOLLOW); err != nil { - log.Debugf("SetStat utimens failed %q, err: %v", fd.FilePathLocked(), err) - resp.FailureMask |= (stat.Mask & (unix.STATX_ATIME | unix.STATX_MTIME)) - resp.FailureErrNo = uint32(p9.ExtractErrno(err)) + log.Warningf("SetStat utimens failed %q, err: %v", fd.FilePathLocked(), err) + failureMask |= (stat.Mask & (unix.STATX_ATIME | unix.STATX_MTIME)) + failureErr = err } return nil }) @@ -240,19 +231,19 @@ func (fd *controlFDLisa) SetStat(c *lisafs.Connection, comm lisafs.Communicator, if fd.IsRegular() { // For regular files, utimensat(2) requires the FD to be open for // writing, see BUGS section. - writableFD, err := fd.getWritableFD() - if err != nil { - return 0, err + if writableFD, err := fd.getWritableFD(); err == nil { + hostFD = writableFD + } else { + log.Warningf("SetStat getWritableFD failed %q, err: %v", fd.FilePath(), err) } - hostFD = writableFD } // Directories and regular files can operate directly on the fd // using empty name. err := utimensat(hostFD, "", utimes, 0) if err != nil { - log.Debugf("SetStat utimens failed %q, err: %v", fd.FilePath(), err) - resp.FailureMask |= (stat.Mask & (unix.STATX_ATIME | unix.STATX_MTIME)) - resp.FailureErrNo = uint32(p9.ExtractErrno(err)) + log.Warningf("SetStat utimens failed %q, err: %v", fd.FilePath(), err) + failureMask |= (stat.Mask & (unix.STATX_ATIME | unix.STATX_MTIME)) + failureErr = err } } } @@ -269,36 +260,22 @@ func (fd *controlFDLisa) SetStat(c *lisafs.Connection, comm lisafs.Communicator, gid = int(stat.GID) } if err := unix.Fchownat(fd.hostFD, "", uid, gid, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW); err != nil { - log.Debugf("SetStat fchown failed %q, err: %v", fd.FilePath(), err) - resp.FailureMask |= stat.Mask & (unix.STATX_UID | unix.STATX_GID) - resp.FailureErrNo = uint32(p9.ExtractErrno(err)) + log.Warningf("SetStat fchown failed %q, err: %v", fd.FilePath(), err) + failureMask |= stat.Mask & (unix.STATX_UID | unix.STATX_GID) + failureErr = err } } - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + return } // Walk implements lisafs.ControlFDImpl.Walk. -func (fd *controlFDLisa) Walk(c *lisafs.Connection, comm lisafs.Communicator, path lisafs.StringArray) (uint32, error) { - // We need to generate inodes for each component walked. We will manually - // marshal the inodes into the payload buffer as they are generated to avoid - // the slice allocation. The memory format should be lisafs.WalkResp's. - var numInodes primitive.Uint32 - var status lisafs.WalkStatus - maxPayloadSize := status.SizeBytes() + numInodes.SizeBytes() + (len(path) * (*lisafs.Inode)(nil).SizeBytes()) - if maxPayloadSize > math.MaxUint32 { - // Too much to walk, can't do. - return 0, unix.EIO - } - payloadBuf := comm.PayloadBuf(uint32(maxPayloadSize)) - payloadPos := status.SizeBytes() + numInodes.SizeBytes() - +func (fd *controlFDLisa) Walk(c *lisafs.Connection, path lisafs.StringArray, recordInode func(lisafs.Inode)) (lisafs.WalkStatus, error) { s := c.Server() s.RenameMu.RLock() defer s.RenameMu.RUnlock() + status := lisafs.WalkSuccess curDirFD := fd cu := cleanup.Make(func() { // Destroy all newly created FDs until now. Walk upward from curDirFD to @@ -329,38 +306,18 @@ func (fd *controlFDLisa) Walk(c *lisafs.Connection, comm lisafs.Communicator, pa return 0, err } - // Write inode to payloadBuf and update state. var childInode lisafs.Inode child.initInodeWithStat(&childInode, &childStat) - childInode.MarshalUnsafe(payloadBuf[payloadPos:]) - payloadPos += childInode.SizeBytes() - numInodes++ + recordInode(childInode) curDirFD = child } cu.Release() - // lisafs.WalkResp writes the walk status followed by the number of inodes in - // the beginning. - status.MarshalUnsafe(payloadBuf) - numInodes.MarshalUnsafe(payloadBuf[status.SizeBytes():]) - return uint32(payloadPos), nil + return status, nil } // WalkStat implements lisafs.ControlFDImpl.WalkStat. -func (fd *controlFDLisa) WalkStat(c *lisafs.Connection, comm lisafs.Communicator, path lisafs.StringArray) (uint32, error) { - // We may need to generate statx for dirFD + each component walked. We will - // manually marshal the statx results into the payload buffer as they are - // generated to avoid the slice allocation. The memory format should be the - // same as lisafs.WalkStatResp's. - var numStats primitive.Uint32 - maxPayloadSize := numStats.SizeBytes() + (len(path) * linux.SizeOfStatx) - if maxPayloadSize > math.MaxUint32 { - // Too much to walk, can't do. - return 0, unix.EIO - } - payloadBuf := comm.PayloadBuf(uint32(maxPayloadSize)) - payloadPos := numStats.SizeBytes() - +func (fd *controlFDLisa) WalkStat(c *lisafs.Connection, path lisafs.StringArray, recordStat func(linux.Statx)) error { s := c.Server() s.RenameMu.RLock() defer s.RenameMu.RUnlock() @@ -379,18 +336,16 @@ func (fd *controlFDLisa) WalkStat(c *lisafs.Connection, comm lisafs.Communicator if len(path) > 0 && len(path[0]) == 0 { // Write stat results for dirFD if the first path component is "". if err := unix.Fstat(fd.hostFD, &unixStat); err != nil { - return 0, err + return err } unixToLinuxStat(&unixStat, &stat) - stat.MarshalUnsafe(payloadBuf[payloadPos:]) - payloadPos += stat.SizeBytes() + recordStat(stat) path = path[1:] - numStats++ } // Don't attempt walking if parent is a symlink. if fd.IsSymlink() { - return 0, nil + return nil } for _, name := range path { curFD, err := unix.Openat(curDirFD, name, unix.O_PATH|openFlags, 0) @@ -400,19 +355,16 @@ func (fd *controlFDLisa) WalkStat(c *lisafs.Connection, comm lisafs.Communicator break } if err != nil { - return 0, err + return err } closeCurDirFD() curDirFD = curFD - // Write stat results for curFD. if err := unix.Fstat(curFD, &unixStat); err != nil { - return 0, err + return err } unixToLinuxStat(&unixStat, &stat) - stat.MarshalUnsafe(payloadBuf[payloadPos:]) - payloadPos += stat.SizeBytes() - numStats++ + recordStat(stat) // Symlinks terminate walk. This client gets the symlink stat result, but // will have to invoke Walk again with the resolved path. @@ -421,42 +373,40 @@ func (fd *controlFDLisa) WalkStat(c *lisafs.Connection, comm lisafs.Communicator } } - // lisafs.WalkStatResp writes the number of stats in the beginning. - numStats.MarshalUnsafe(payloadBuf) - return uint32(payloadPos), nil + return nil } // Open implements lisafs.ControlFDImpl.Open. -func (fd *controlFDLisa) Open(c *lisafs.Connection, comm lisafs.Communicator, flags uint32) (uint32, error) { +func (fd *controlFDLisa) Open(c *lisafs.Connection, flags uint32) (lisafs.FDID, int, error) { flags |= openFlags newHostFD, err := unix.Openat(int(procSelfFD.FD()), strconv.Itoa(fd.hostFD), int(flags)&^unix.O_NOFOLLOW, 0) if err != nil { - return 0, err + return lisafs.InvalidFDID, -1, err } - newFD := fd.newOpenFDLisa(newHostFD, flags) + openFD := fd.newOpenFDLisa(newHostFD, flags) + hostOpenFD := -1 if fd.IsRegular() { // Donate FD for regular files only. Since FD donation is a destructive // operation, we should duplicate the to-be-donated FD. Eat the error if // one occurs, it is better to have an FD without a host FD, than failing // the Open attempt. - if dupFD, err := unix.Dup(newFD.hostFD); err == nil { - _ = comm.DonateFD(dupFD) + if dupFD, err := unix.Dup(openFD.hostFD); err == nil { + hostOpenFD = dupFD } } - resp := lisafs.OpenAtResp{NewFD: newFD.ID()} - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + return openFD.ID(), hostOpenFD, nil } // OpenCreate implements lisafs.ControlFDImpl.OpenCreate. -func (fd *controlFDLisa) OpenCreate(c *lisafs.Connection, comm lisafs.Communicator, mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string, flags uint32) (uint32, error) { +func (fd *controlFDLisa) OpenCreate(c *lisafs.Connection, mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string, flags uint32) (lisafs.Inode, lisafs.FDID, int, error) { // Need to hold rename mutex for reading while performing the walk. Also keep // holding it while the cleanup is still possible. - var resp lisafs.OpenCreateAtResp - var newFD *openFDLisa + var ( + childIno lisafs.Inode + newFD *openFDLisa + ) if err := c.Server().WithRenameReadLock(func() error { createFlags := unix.O_CREAT | unix.O_EXCL | unix.O_RDONLY | unix.O_NONBLOCK | openFlags childHostFD, err := unix.Openat(fd.hostFD, name, createFlags, uint32(mode&^linux.FileTypeMask)) @@ -481,7 +431,7 @@ func (fd *controlFDLisa) OpenCreate(c *lisafs.Connection, comm lisafs.Communicat // Do not use the stat result from tryOpen because the owners might have // changed. initInode() will stat the FD again and use fresh results. - if err := childFD.initInode(&resp.Child); err != nil { + if err := childFD.initInode(&childIno); err != nil { return err } @@ -494,29 +444,26 @@ func (fd *controlFDLisa) OpenCreate(c *lisafs.Connection, comm lisafs.Communicat cu.Release() newFD = childFD.newOpenFDLisa(newHostFD, uint32(flags)) - resp.NewFD = newFD.ID() return nil }); err != nil { - return 0, err + return lisafs.Inode{}, lisafs.InvalidFDID, -1, err } // Donate FD because open(O_CREAT|O_EXCL) always creates a regular file. // Since FD donation is a destructive operation, we should duplicate the // to-be-donated FD. Eat the error if one occurs, it is better to have an FD // without a host FD, than failing the Open attempt. + hostOpenFD := -1 if dupFD, err := unix.Dup(newFD.hostFD); err == nil { - _ = comm.DonateFD(dupFD) + hostOpenFD = dupFD } - - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + return childIno, newFD.ID(), hostOpenFD, nil } // Mkdir implements lisafs.ControlFDImpl.Mkdir. -func (fd *controlFDLisa) Mkdir(c *lisafs.Connection, comm lisafs.Communicator, mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string) (uint32, error) { - var resp lisafs.MkdirAtResp - if err := c.Server().WithRenameReadLock(func() error { +func (fd *controlFDLisa) Mkdir(c *lisafs.Connection, mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string) (lisafs.Inode, error) { + var childIno lisafs.Inode + return childIno, c.Server().WithRenameReadLock(func() error { if err := unix.Mkdirat(fd.hostFD, name, uint32(mode&^linux.FileTypeMask)); err != nil { return err } @@ -539,33 +486,27 @@ func (fd *controlFDLisa) Mkdir(c *lisafs.Connection, comm lisafs.Communicator, m } childDir := newControlFDLisaLocked(c, childDirFd, fd, name, linux.ModeDirectory) - if err := childDir.initInode(&resp.ChildDir); err != nil { + if err := childDir.initInode(&childIno); err != nil { c.RemoveControlFDLocked(childDir.ID()) return err } cu.Release() return nil - }); err != nil { - return 0, err - } - - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + }) } // Mknod implements lisafs.ControlFDImpl.Mknod. -func (fd *controlFDLisa) Mknod(c *lisafs.Connection, comm lisafs.Communicator, mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string, minor uint32, major uint32) (uint32, error) { +func (fd *controlFDLisa) Mknod(c *lisafs.Connection, mode linux.FileMode, uid lisafs.UID, gid lisafs.GID, name string, minor uint32, major uint32) (lisafs.Inode, error) { // From mknod(2) man page: // "EPERM: [...] if the filesystem containing pathname does not support // the type of node requested." if mode.FileType() != linux.ModeRegular { - return 0, unix.EPERM + return lisafs.Inode{}, unix.EPERM } - var resp lisafs.MknodAtResp - if err := c.Server().WithRenameReadLock(func() error { + var childIno lisafs.Inode + return childIno, c.Server().WithRenameReadLock(func() error { if err := unix.Mknodat(fd.hostFD, name, uint32(mode), 0); err != nil { return err } @@ -588,25 +529,19 @@ func (fd *controlFDLisa) Mknod(c *lisafs.Connection, comm lisafs.Communicator, m } child := newControlFDLisaLocked(c, childFD, fd, name, mode) - if err := child.initInode(&resp.Child); err != nil { + if err := child.initInode(&childIno); err != nil { c.RemoveControlFDLocked(child.ID()) return err } cu.Release() return nil - }); err != nil { - return 0, err - } - - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + }) } // Symlink implements lisafs.ControlFDImpl.Symlink. -func (fd *controlFDLisa) Symlink(c *lisafs.Connection, comm lisafs.Communicator, name string, target string, uid lisafs.UID, gid lisafs.GID) (uint32, error) { - var resp lisafs.SymlinkAtResp - if err := c.Server().WithRenameReadLock(func() error { +func (fd *controlFDLisa) Symlink(c *lisafs.Connection, name string, target string, uid lisafs.UID, gid lisafs.GID) (lisafs.Inode, error) { + var childIno lisafs.Inode + return childIno, c.Server().WithRenameReadLock(func() error { if err := unix.Symlinkat(target, fd.hostFD, name); err != nil { return err } @@ -629,25 +564,19 @@ func (fd *controlFDLisa) Symlink(c *lisafs.Connection, comm lisafs.Communicator, } symlink := newControlFDLisaLocked(c, symlinkFD, fd, name, linux.ModeSymlink) - if err := symlink.initInode(&resp.Symlink); err != nil { + if err := symlink.initInode(&childIno); err != nil { c.RemoveControlFDLocked(symlink.ID()) return err } cu.Release() return nil - }); err != nil { - return 0, err - } - - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + }) } // Link implements lisafs.ControlFDImpl.Link. -func (fd *controlFDLisa) Link(c *lisafs.Connection, comm lisafs.Communicator, dir lisafs.ControlFDImpl, name string) (uint32, error) { - var resp lisafs.LinkAtResp - if err := c.Server().WithRenameReadLock(func() error { +func (fd *controlFDLisa) Link(c *lisafs.Connection, dir lisafs.ControlFDImpl, name string) (lisafs.Inode, error) { + var childIno lisafs.Inode + return childIno, c.Server().WithRenameReadLock(func() error { dirFD := dir.(*controlFDLisa) if err := unix.Linkat(fd.hostFD, "", dirFD.hostFD, name, unix.AT_EMPTY_PATH); err != nil { return err @@ -668,25 +597,19 @@ func (fd *controlFDLisa) Link(c *lisafs.Connection, comm lisafs.Communicator, di } cu.Release() - linkFD.initInodeWithStat(&resp.Link, &linkStat) + linkFD.initInodeWithStat(&childIno, &linkStat) return nil - }); err != nil { - return 0, err - } - - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + }) } // StatFS implements lisafs.ControlFDImpl.StatFS. -func (fd *controlFDLisa) StatFS(c *lisafs.Connection, comm lisafs.Communicator) (uint32, error) { +func (fd *controlFDLisa) StatFS(c *lisafs.Connection) (lisafs.StatFS, error) { var s unix.Statfs_t if err := unix.Fstatfs(fd.hostFD, &s); err != nil { - return 0, err + return lisafs.StatFS{}, err } - resp := lisafs.StatFS{ + return lisafs.StatFS{ Type: uint64(s.Type), BlockSize: s.Bsize, Blocks: s.Blocks, @@ -695,40 +618,31 @@ func (fd *controlFDLisa) StatFS(c *lisafs.Connection, comm lisafs.Communicator) Files: s.Files, FilesFree: s.Ffree, NameLength: uint64(s.Namelen), - } - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + }, nil } // Readlink implements lisafs.ControlFDImpl.Readlink. -func (fd *controlFDLisa) Readlink(c *lisafs.Connection, comm lisafs.Communicator) (uint32, error) { - // We will manually marshal lisafs.ReadLinkAtResp, which just contains a - // lisafs.SizedString. Let unix.Readlinkat directly write into the payload - // buffer and manually write the string size before it. - +func (fd *controlFDLisa) Readlink(c *lisafs.Connection, getLinkBuf func(uint32) []byte) (uint32, error) { // This is similar to what os.Readlink does. - const limit = primitive.Uint32(1024 * 1024) - for linkLen := primitive.Uint32(128); linkLen < limit; linkLen *= 2 { - b := comm.PayloadBuf(uint32(linkLen) + uint32(linkLen.SizeBytes())) - n, err := unix.Readlinkat(fd.hostFD, "", b[linkLen.SizeBytes():]) + const limit = uint32(1024 * 1024) + for linkLen := uint32(128); linkLen < limit; linkLen *= 2 { + b := getLinkBuf(linkLen) + n, err := unix.Readlinkat(fd.hostFD, "", b) if err != nil { return 0, err } if n < int(linkLen) { - linkLen = primitive.Uint32(n) - linkLen.MarshalUnsafe(b[:linkLen.SizeBytes()]) - return uint32(linkLen) + uint32(linkLen.SizeBytes()), nil + return uint32(n), nil } } return 0, unix.ENOMEM } // Connect implements lisafs.ControlFDImpl.Connect. -func (fd *controlFDLisa) Connect(c *lisafs.Connection, comm lisafs.Communicator, sockType uint32) error { +func (fd *controlFDLisa) Connect(c *lisafs.Connection, sockType uint32) (int, error) { s := c.ServerImpl().(*LisafsServer) if !s.config.HostUDS { - return unix.ECONNREFUSED + return -1, unix.ECONNREFUSED } // Lock RenameMu so that the hostPath read stays valid and is not tampered @@ -742,29 +656,27 @@ func (fd *controlFDLisa) Connect(c *lisafs.Connection, comm lisafs.Communicator, // in order to actually connect to this socket. hostPath := fd.FilePathLocked() if len(hostPath) > 108 { // UNIX_PATH_MAX = 108 is defined in afunix.h. - return unix.ECONNREFUSED + return -1, unix.ECONNREFUSED } // Only the following types are supported. switch sockType { case unix.SOCK_STREAM, unix.SOCK_DGRAM, unix.SOCK_SEQPACKET: default: - return unix.ENXIO + return -1, unix.ENXIO } sock, err := unix.Socket(unix.AF_UNIX, int(sockType), 0) if err != nil { - return err - } - if err := comm.DonateFD(sock); err != nil { - return err + return -1, err } sa := unix.SockaddrUnix{Name: hostPath} if err := unix.Connect(sock, &sa); err != nil { - return err + unix.Close(sock) + return -1, err } - return nil + return sock, nil } // Unlink implements lisafs.ControlFDImpl.Unlink. @@ -781,25 +693,15 @@ func (fd *controlFDLisa) RenameLocked(c *lisafs.Connection, newDir lisafs.Contro } // GetXattr implements lisafs.ControlFDImpl.GetXattr. -func (fd *controlFDLisa) GetXattr(c *lisafs.Connection, comm lisafs.Communicator, name string, size uint32) (uint32, error) { +func (fd *controlFDLisa) GetXattr(c *lisafs.Connection, name string, dataBuf []byte) (uint32, error) { if !c.ServerImpl().(*LisafsServer).config.EnableVerityXattr { return 0, unix.EOPNOTSUPP } if _, ok := verityXattrs[name]; !ok { return 0, unix.EOPNOTSUPP } - - // Manually marshal lisafs.FGetXattrResp to avoid allocations and copying. - var valueLen primitive.Uint32 - buf := comm.PayloadBuf(uint32(valueLen.SizeBytes()) + size) - n, err := unix.Fgetxattr(fd.hostFD, name, buf[valueLen.SizeBytes():]) - if err != nil { - return 0, err - } - valueLen = primitive.Uint32(n) - valueLen.MarshalBytes(buf[:valueLen.SizeBytes()]) - - return uint32(valueLen.SizeBytes() + n), nil + n, err := unix.Fgetxattr(fd.hostFD, name, dataBuf) + return uint32(n), err } // SetXattr implements lisafs.ControlFDImpl.SetXattr. @@ -814,12 +716,12 @@ func (fd *controlFDLisa) SetXattr(c *lisafs.Connection, name string, value strin } // ListXattr implements lisafs.ControlFDImpl.ListXattr. -func (fd *controlFDLisa) ListXattr(c *lisafs.Connection, comm lisafs.Communicator, size uint64) (uint32, error) { - return 0, unix.EOPNOTSUPP +func (fd *controlFDLisa) ListXattr(c *lisafs.Connection, size uint64) (lisafs.StringArray, error) { + return nil, unix.EOPNOTSUPP } // RemoveXattr implements lisafs.ControlFDImpl.RemoveXattr. -func (fd *controlFDLisa) RemoveXattr(c *lisafs.Connection, comm lisafs.Communicator, name string) error { +func (fd *controlFDLisa) RemoveXattr(c *lisafs.Connection, name string) error { return unix.EOPNOTSUPP } @@ -858,15 +760,9 @@ func (fd *openFDLisa) Close(c *lisafs.Connection) { } // Stat implements lisafs.OpenFDImpl.Stat. -func (fd *openFDLisa) Stat(c *lisafs.Connection, comm lisafs.Communicator) (uint32, error) { +func (fd *openFDLisa) Stat(c *lisafs.Connection) (linux.Statx, error) { var resp linux.Statx - if err := fstatTo(fd.hostFD, &resp); err != nil { - return 0, err - } - - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + return resp, fstatTo(fd.hostFD, &resp) } // Sync implements lisafs.OpenFDImpl.Sync. @@ -875,39 +771,20 @@ func (fd *openFDLisa) Sync(c *lisafs.Connection) error { } // Write implements lisafs.OpenFDImpl.Write. -func (fd *openFDLisa) Write(c *lisafs.Connection, comm lisafs.Communicator, buf []byte, off uint64) (uint32, error) { +func (fd *openFDLisa) Write(c *lisafs.Connection, buf []byte, off uint64) (uint64, error) { rw := rwfd.NewReadWriter(fd.hostFD) n, err := rw.WriteAt(buf, int64(off)) - if err != nil { - return 0, err - } - - resp := &lisafs.PWriteResp{Count: uint64(n)} - respLen := uint32(resp.SizeBytes()) - resp.MarshalUnsafe(comm.PayloadBuf(respLen)) - return respLen, nil + return uint64(n), err } // Read implements lisafs.OpenFDImpl.Read. -func (fd *openFDLisa) Read(c *lisafs.Connection, comm lisafs.Communicator, off uint64, count uint32) (uint32, error) { - // To save an allocation and a copy, we directly read into the payload - // buffer. The rest of the response message is manually marshalled. - var resp lisafs.PReadResp - respMetaSize := uint32(resp.NumBytes.SizeBytes()) - maxRespLen := respMetaSize + count - - payloadBuf := comm.PayloadBuf(maxRespLen) +func (fd *openFDLisa) Read(c *lisafs.Connection, off uint64, dataBuf []byte) (uint32, error) { rw := rwfd.NewReadWriter(fd.hostFD) - n, err := rw.ReadAt(payloadBuf[respMetaSize:], int64(off)) + n, err := rw.ReadAt(dataBuf, int64(off)) if err != nil && err != io.EOF { return 0, err } - - // Write the response metadata onto the payload buffer. The response contents - // already have been written immediately after it. - resp.NumBytes = primitive.Uint32(n) - resp.NumBytes.MarshalUnsafe(payloadBuf[:respMetaSize]) - return respMetaSize + uint32(n), nil + return uint32(n), nil } // Allocate implements lisafs.OpenFDImpl.Allocate. @@ -921,23 +798,13 @@ func (fd *openFDLisa) Flush(c *lisafs.Connection) error { } // Getdent64 implements lisafs.OpenFDImpl.Getdent64. -func (fd *openFDLisa) Getdent64(c *lisafs.Connection, comm lisafs.Communicator, count uint32, seek0 bool) (uint32, error) { +func (fd *openFDLisa) Getdent64(c *lisafs.Connection, count uint32, seek0 bool, recordDirent func(lisafs.Dirent64)) error { if seek0 { if _, err := unix.Seek(fd.hostFD, 0, 0); err != nil { - return 0, err + return err } } - // We will manually marshal the response lisafs.Getdents64Resp. - - // numDirents is the number of dirents marshalled into the payload. - var numDirents primitive.Uint32 - // The payload starts with numDirents, dirents go right after that. - // payloadBufPos represents the position at which to write the next dirent. - payloadBufPos := uint32(numDirents.SizeBytes()) - // Request enough payloadBuf for 10 dirents, we will extend when needed. - payloadBuf := comm.PayloadBuf(payloadBufPos + 10*unixDirentMaxSize) - var direntsBuf [8192]byte var bytesRead int for bytesRead < int(count) { @@ -954,7 +821,7 @@ func (fd *openFDLisa) Getdent64(c *lisafs.Connection, comm lisafs.Communicator, // dirents collected till now. break } - return 0, err + return err } if n <= 0 { break @@ -979,26 +846,14 @@ func (fd *openFDLisa) Getdent64(c *lisafs.Connection, comm lisafs.Communicator, } dirent.DevMinor = primitive.Uint32(unix.Minor(stat.Dev)) dirent.DevMajor = primitive.Uint32(unix.Major(stat.Dev)) - - // Paste the dirent into the payload buffer without having the dirent - // escape. Request a larger buffer if needed. - if int(payloadBufPos)+dirent.SizeBytes() > len(payloadBuf) { - // Ask for 10 large dirents worth of more space. - payloadBuf = comm.PayloadBuf(payloadBufPos + 10*unixDirentMaxSize) - } - dirent.MarshalBytes(payloadBuf[payloadBufPos:]) - payloadBufPos += uint32(dirent.SizeBytes()) - numDirents++ + recordDirent(dirent) return true }) if statErr != nil { - return 0, statErr + return statErr } } - - // The number of dirents goes at the beginning of the payload. - numDirents.MarshalUnsafe(payloadBuf) - return payloadBufPos, nil + return nil } // tryStepLocked tries to walk via open() with different modes as documented.