From 0f7cbc8ecf628e178513ea280c0af5d678fb763e Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Sat, 12 Feb 2022 16:16:45 -0800 Subject: [PATCH] Add debug logging for lisafs. This change adds debug log messages for lisafs RPC messages just like the p9 package does. This is needed to make lisafs more production ready. Note that we can not simply call fmt.Sprintf("%+v", message) because fmt methods accept arguments as interface{} and hence escape them to the heap. Doing so would cause all messages (which have a temporary lifetime) to unnecessarily escape to heap hence eroding memory performance. So the String implementations in this change only escape the struct's fields. Like this, the struct's fields are heap allocated only when String() is called and in the common case where debug logging is disabled, String() is not called. In some cases, there has also been made an effort to not let slice fields escape because some callers use statically sized arrays as slices which are not intended to escape. PiperOrigin-RevId: 428255508 --- pkg/abi/linux/file.go | 6 + pkg/lisafs/channel.go | 6 + pkg/lisafs/client.go | 44 ++-- pkg/lisafs/client_file.go | 67 +++--- pkg/lisafs/communicator.go | 8 +- pkg/lisafs/connection_test.go | 7 +- pkg/lisafs/message.go | 401 +++++++++++++++++++++++++++++++++- pkg/lisafs/sample_message.go | 11 + pkg/lisafs/sock.go | 6 + pkg/lisafs/sock_test.go | 2 +- 10 files changed, 503 insertions(+), 55 deletions(-) diff --git a/pkg/abi/linux/file.go b/pkg/abi/linux/file.go index 540991b57..899c0ce52 100644 --- a/pkg/abi/linux/file.go +++ b/pkg/abi/linux/file.go @@ -271,6 +271,12 @@ type Statx struct { DevMinor uint32 } +// String implements fmt.Stringer.String. +func (s *Statx) String() string { + return fmt.Sprintf("Statx{Mask: %d, Blksize: %d, Attributes: %d, Nlink: %d, UID: %d, GID: %d, Mode: %d, Ino: %d, Size: %d, Blocks: %d, AttributesMask: %d, Atime: %d, Btime: %d, Ctime: %d, Mtime: %d, RdevMajor: %d, RdevMinor: %d, DevMajor: %d, DevMinor: %d}", + s.Mask, s.Blksize, s.Attributes, s.Nlink, s.UID, s.GID, s.Mode, s.Ino, s.Size, s.Blocks, s.AttributesMask, s.Atime, s.Btime, s.Ctime, s.Mtime, s.RdevMajor, s.RdevMinor, s.DevMajor, s.DevMinor) +} + // SizeOfStatx is the size of a Statx struct. var SizeOfStatx = (*Statx)(nil).SizeBytes() diff --git a/pkg/lisafs/channel.go b/pkg/lisafs/channel.go index 301212e51..f7ae2b7b4 100644 --- a/pkg/lisafs/channel.go +++ b/pkg/lisafs/channel.go @@ -15,6 +15,7 @@ package lisafs import ( + "fmt" "math" "runtime" @@ -80,6 +81,11 @@ func (ch *channel) SndRcvMessage(m MID, payloadLen uint32, wantFDs uint8) (MID, return ch.rcvMsg(rcvDataLen) } +// String implements fmt.Stringer.String. +func (ch *channel) String() string { + return fmt.Sprintf("channel %p", ch) +} + func (ch *channel) shutdown() { ch.data.Shutdown() } diff --git a/pkg/lisafs/client.go b/pkg/lisafs/client.go index 92997b930..bb741bdee 100644 --- a/pkg/lisafs/client.go +++ b/pkg/lisafs/client.go @@ -97,8 +97,11 @@ func NewClient(sock *unet.Socket) (*Client, Inode, error) { // Mount RPC below. c.supported = make([]bool, Mount+1) c.supported[Mount] = true - var mountResp MountResp - if err := c.SndRcvMessage(Mount, 0, NoopMarshal, mountResp.CheckedUnmarshal, nil); err != nil { + var ( + mountReq MountReq + mountResp MountResp + ) + if err := c.SndRcvMessage(Mount, uint32(mountReq.SizeBytes()), mountReq.MarshalBytes, mountResp.CheckedUnmarshal, nil, mountResp.String, mountResp.String); err != nil { return nil, Inode{}, err } @@ -218,9 +221,12 @@ func (c *Client) Close() { } func (c *Client) createChannel() (*channel, error) { - var chanResp ChannelResp + var ( + chanReq ChannelReq + chanResp ChannelResp + ) var fds [2]int - if err := c.SndRcvMessage(Channel, 0, NoopMarshal, chanResp.CheckedUnmarshal, fds[:]); err != nil { + if err := c.SndRcvMessage(Channel, uint32(chanReq.SizeBytes()), chanReq.MarshalBytes, chanResp.CheckedUnmarshal, fds[:], chanReq.String, chanResp.String); err != nil { return nil, err } if fds[0] < 0 || fds[1] < 0 { @@ -277,8 +283,9 @@ func (c *Client) CloseFDBatched(ctx context.Context, fd FDID) { c.fdsMu.Unlock() req := CloseReq{FDs: toClose} + var resp CloseResp ctx.UninterruptibleSleepStart(false) - err := c.SndRcvMessage(Close, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := c.SndRcvMessage(Close, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) if err != nil { log.Warningf("lisafs: batch closing FDs returned error: %v", err) @@ -291,8 +298,9 @@ func (c *Client) SyncFDs(ctx context.Context, fds []FDID) error { return nil } req := FsyncReq{FDs: fds} + var resp FsyncResp ctx.UninterruptibleSleepStart(false) - err := c.SndRcvMessage(FSync, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := c.SndRcvMessage(FSync, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -302,15 +310,11 @@ func (c *Client) SyncFDs(ctx context.Context, fds []FDID) error { // and invokes respUnmarshal with the response payload. respFDs is populated // with the received FDs, extra fields are set to -1. // -// Note that the function arguments intentionally accept marshal.Marshallable -// functions like Marshal{Bytes/Unsafe} and Unmarshal{Bytes/Unsafe} instead of -// directly accepting the marshal.Marshallable interface. Even though just -// accepting marshal.Marshallable is cleaner, it leads to a heap allocation -// (even if that interface variable itself does not escape). In other words, -// implicit conversion to an interface leads to an allocation. +// See messages.go to understand why function arguments are used instead of +// combining these functions into an interface type. // -// Precondition: reqMarshal and respUnmarshal must be non-nil. -func (c *Client) SndRcvMessage(m MID, payloadLen uint32, reqMarshal func(dst []byte) []byte, respUnmarshal func(src []byte) ([]byte, bool), respFDs []int) error { +// Precondition: function arguments must be non-nil. +func (c *Client) SndRcvMessage(m MID, payloadLen uint32, reqMarshal marshalFunc, respUnmarshal unmarshalFunc, respFDs []int, reqString debugStringer, respString debugStringer) error { if !c.IsSupported(m) { return unix.EOPNOTSUPP } @@ -328,6 +332,8 @@ func (c *Client) SndRcvMessage(m MID, payloadLen uint32, reqMarshal func(dst []b comm := c.acquireCommunicator() defer c.releaseCommunicator(comm) + debugf("send", comm, reqString) + // Marshal the request into comm's payload buffer and make the RPC. reqMarshal(comm.PayloadBuf(payloadLen)) respM, respPayloadLen, err := comm.SndRcvMessage(m, payloadLen, uint8(wantFDs)) @@ -363,6 +369,7 @@ func (c *Client) SndRcvMessage(m MID, payloadLen uint32, reqMarshal func(dst []b closeFDs(respFDs) var resp ErrorResp resp.UnmarshalUnsafe(comm.PayloadBuf(respPayloadLen)) + debugf("recv", comm, resp.String) return unix.Errno(resp.errno) } if respM != m { @@ -376,9 +383,18 @@ func (c *Client) SndRcvMessage(m MID, payloadLen uint32, reqMarshal func(dst []b log.Warningf("server response unmarshalling for %d message failed", respM) return unix.EIO } + debugf("recv", comm, respString) return nil } +func debugf(action string, comm Communicator, debugMsg debugStringer) { + // Replicate the log.IsLogging(log.Debug) check to avoid having to call + // debugMsg() on the hot path. + if log.IsLogging(log.Debug) { + log.Debugf("%s [%s] %s", action, comm, debugMsg()) + } +} + // Postcondition: releaseCommunicator() must be called on the returned value. func (c *Client) acquireCommunicator() Communicator { // Prefer using channel over socket because: diff --git a/pkg/lisafs/client_file.go b/pkg/lisafs/client_file.go index 6ec2a657a..76ea68576 100644 --- a/pkg/lisafs/client_file.go +++ b/pkg/lisafs/client_file.go @@ -66,9 +66,10 @@ func (f *ClientFD) CloseBatched(ctx context.Context) { func (f *ClientFD) Close(ctx context.Context) error { fdArr := [1]FDID{f.fd} req := CloseReq{FDs: fdArr[:]} + var resp CloseResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(Close, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(Close, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -82,7 +83,7 @@ func (f *ClientFD) OpenAt(ctx context.Context, flags uint32) (FDID, int, error) var respFD [1]int var resp OpenAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(OpenAt, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, respFD[:]) + err := f.client.SndRcvMessage(OpenAt, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, respFD[:], req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.OpenFD, respFD[0], err } @@ -100,7 +101,7 @@ func (f *ClientFD) OpenCreateAt(ctx context.Context, name string, flags uint32, var respFD [1]int var resp OpenCreateAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(OpenCreateAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, respFD[:]) + err := f.client.SndRcvMessage(OpenCreateAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, respFD[:], req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Child, resp.NewFD, respFD[0], err } @@ -109,7 +110,7 @@ func (f *ClientFD) OpenCreateAt(ctx context.Context, name string, flags uint32, 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.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(FStat, uint32(req.SizeBytes()), req.MarshalUnsafe, stat.CheckedUnmarshal, nil, req.String, stat.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -117,8 +118,9 @@ func (f *ClientFD) StatTo(ctx context.Context, stat *linux.Statx) error { // Sync makes the Fsync RPC. func (f *ClientFD) Sync(ctx context.Context) error { req := FsyncReq{FDs: []FDID{f.fd}} + var resp FsyncResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(FSync, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(FSync, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -181,7 +183,7 @@ func (f *ClientFD) Read(ctx context.Context, dst []byte, offset uint64) (uint64, // PReadResp.CheckedUnmarshal expects this to be set. resp.Buf = buf ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(PRead, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(PRead, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return uint64(resp.NumBytes), err }) @@ -205,7 +207,7 @@ func (f *ClientFD) Write(ctx context.Context, src []byte, offset uint64) (uint64 var resp PWriteResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(PWrite, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(PWrite, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Count, err }) @@ -222,7 +224,7 @@ func (f *ClientFD) MkdirAt(ctx context.Context, name string, mode linux.FileMode var resp MkdirAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(MkdirAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(MkdirAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.ChildDir, err } @@ -239,7 +241,7 @@ func (f *ClientFD) SymlinkAt(ctx context.Context, name, target string, uid UID, var resp SymlinkAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(SymlinkAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(SymlinkAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Symlink, err } @@ -254,7 +256,7 @@ func (f *ClientFD) LinkAt(ctx context.Context, targetFD FDID, name string) (Inod var resp LinkAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(LinkAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(LinkAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Link, err } @@ -272,7 +274,7 @@ func (f *ClientFD) MknodAt(ctx context.Context, name string, mode linux.FileMode var resp MknodAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(MknodAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(MknodAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Child, err } @@ -298,7 +300,7 @@ func (f *ClientFD) SetStat(ctx context.Context, stat *linux.Statx) (uint32, erro var resp SetStatResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(SetStat, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(SetStat, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.FailureMask, unix.Errno(resp.FailureErrNo), err } @@ -312,7 +314,7 @@ func (f *ClientFD) WalkMultiple(ctx context.Context, names []string) (WalkStatus var resp WalkResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(Walk, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(Walk, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Status, resp.Inodes, err } @@ -327,7 +329,7 @@ func (f *ClientFD) Walk(ctx context.Context, name string) (Inode, error) { var inode [1]Inode resp := WalkResp{Inodes: inode[:]} ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(Walk, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(Walk, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) if err != nil { return Inode{}, err @@ -363,7 +365,7 @@ func (f *ClientFD) WalkStat(ctx context.Context, names []string) ([]linux.Statx, var resp WalkStatResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(WalkStat, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(WalkStat, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Stats, err } @@ -372,7 +374,7 @@ func (f *ClientFD) WalkStat(ctx context.Context, names []string) ([]linux.Statx, 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.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(FStatFS, uint32(req.SizeBytes()), req.MarshalUnsafe, statFS.CheckedUnmarshal, nil, req.String, statFS.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -385,8 +387,9 @@ func (f *ClientFD) Allocate(ctx context.Context, mode, offset, length uint64) er Offset: offset, Length: length, } + var resp FAllocateResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(FAllocate, uint32(req.SizeBytes()), req.MarshalUnsafe, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(FAllocate, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -396,7 +399,7 @@ 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.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(ReadLinkAt, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return string(resp.Target), err } @@ -408,8 +411,9 @@ func (f *ClientFD) Flush(ctx context.Context) error { return nil } req := FlushReq{FD: f.fd} + var resp FlushResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(Flush, uint32(req.SizeBytes()), req.MarshalUnsafe, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(Flush, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -417,9 +421,10 @@ func (f *ClientFD) Flush(ctx context.Context) error { // 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 resp ConnectResp var sockFD [1]int ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(Connect, uint32(req.SizeBytes()), req.MarshalUnsafe, NoopUnmarshal, sockFD[:]) + err := f.client.SndRcvMessage(Connect, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, sockFD[:], req.String, resp.String) ctx.UninterruptibleSleepFinish(false) if err == nil && sockFD[0] < 0 { err = unix.EBADF @@ -434,9 +439,9 @@ func (f *ClientFD) UnlinkAt(ctx context.Context, name string, flags uint32) erro Name: SizedString(name), Flags: primitive.Uint32(flags), } - + var resp UnlinkAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(UnlinkAt, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(UnlinkAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -450,9 +455,9 @@ func (f *ClientFD) RenameAt(ctx context.Context, oldName string, newDirFD FDID, NewDir: newDirFD, NewName: SizedString(newName), } - + var resp RenameAtResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(RenameAt, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(RenameAt, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -466,7 +471,7 @@ func (f *ClientFD) Getdents64(ctx context.Context, count int32) ([]Dirent64, err var resp Getdents64Resp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(Getdents64, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(Getdents64, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Dirents, err } @@ -480,7 +485,7 @@ func (f *ClientFD) ListXattr(ctx context.Context, size uint64) ([]string, error) var resp FListXattrResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(FListXattr, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(FListXattr, uint32(req.SizeBytes()), req.MarshalUnsafe, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return resp.Xattrs, err } @@ -495,7 +500,7 @@ func (f *ClientFD) GetXattr(ctx context.Context, name string, size uint64) (stri var resp FGetXattrResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(FGetXattr, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil) + err := f.client.SndRcvMessage(FGetXattr, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return string(resp.Value), err } @@ -508,9 +513,9 @@ func (f *ClientFD) SetXattr(ctx context.Context, name string, value string, flag Value: SizedString(value), Flags: primitive.Uint32(flags), } - + var resp FSetXattrResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(FSetXattr, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(FSetXattr, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } @@ -521,9 +526,9 @@ func (f *ClientFD) RemoveXattr(ctx context.Context, name string) error { FD: f.fd, Name: SizedString(name), } - + var resp FRemoveXattrResp ctx.UninterruptibleSleepStart(false) - err := f.client.SndRcvMessage(FRemoveXattr, uint32(req.SizeBytes()), req.MarshalBytes, NoopUnmarshal, nil) + err := f.client.SndRcvMessage(FRemoveXattr, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String) ctx.UninterruptibleSleepFinish(false) return err } diff --git a/pkg/lisafs/communicator.go b/pkg/lisafs/communicator.go index ec2035158..0946ec8bb 100644 --- a/pkg/lisafs/communicator.go +++ b/pkg/lisafs/communicator.go @@ -14,11 +14,17 @@ package lisafs -import "golang.org/x/sys/unix" +import ( + "fmt" + + "golang.org/x/sys/unix" +) // Communicator is a server side utility which represents exactly how the // server is communicating with the client. type Communicator interface { + fmt.Stringer + // PayloadBuf returns a slice to the payload section of its internal buffer // where the message can be marshalled. The handlers should use this to // populate the payload buffer with the message. diff --git a/pkg/lisafs/connection_test.go b/pkg/lisafs/connection_test.go index 616f40ef2..b15413702 100644 --- a/pkg/lisafs/connection_test.go +++ b/pkg/lisafs/connection_test.go @@ -118,8 +118,9 @@ func TestStartUp(t *testing.T) { func TestUnsupportedMessage(t *testing.T) { unsupportedM := lisafs.MID(len(handlers)) + var em lisafs.EmptyMessage runServerClient(t, func(c *lisafs.Client) { - if err := c.SndRcvMessage(unsupportedM, 0, lisafs.NoopMarshal, lisafs.NoopUnmarshal, nil); err != unix.EOPNOTSUPP { + if err := c.SndRcvMessage(unsupportedM, uint32(em.SizeBytes()), em.MarshalBytes, em.CheckedUnmarshal, nil, em.String, em.String); err != unix.EOPNOTSUPP { t.Errorf("expected EOPNOTSUPP but got err: %v", err) } }) @@ -154,7 +155,7 @@ func TestStress(t *testing.T) { req.Randomize(100) var resp lisafs.MsgDynamic - if err := c.SndRcvMessage(dynamicMsgID, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil); err != nil { + if err := c.SndRcvMessage(dynamicMsgID, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String); err != nil { t.Errorf("SndRcvMessage: received unexpected error %v", err) return } @@ -198,7 +199,7 @@ func BenchmarkSendRecv(b *testing.B) { var recvV lisafs.P9Version runServerClient(b, func(c *lisafs.Client) { for i := 0; i < b.N; i++ { - if err := c.SndRcvMessage(versionMsgID, uint32(sendV.SizeBytes()), sendV.MarshalBytes, recvV.CheckedUnmarshal, nil); err != nil { + if err := c.SndRcvMessage(versionMsgID, uint32(sendV.SizeBytes()), sendV.MarshalBytes, recvV.CheckedUnmarshal, nil, sendV.String, recvV.String); err != nil { b.Fatalf("unexpected error occurred: %v", err) } } diff --git a/pkg/lisafs/message.go b/pkg/lisafs/message.go index d0370647c..a04f84a05 100644 --- a/pkg/lisafs/message.go +++ b/pkg/lisafs/message.go @@ -15,8 +15,10 @@ package lisafs import ( + "fmt" "math" "os" + "strings" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/hostarch" @@ -26,8 +28,35 @@ import ( // Messages have two parts: // * A transport header used to decipher received messages. // * A byte array referred to as "payload" which contains the actual message. -// // "dataLen" refers to the size of both combined. +// +// All messages must implement the following functions: +// * marshal.Marshallable.SizeBytes +// * marshal.Marshallable.Marshal{Unsafe/Bytes} +// * marshal.CheckedMarshallable.CheckedUnmarshal +// * fmt.Stringer.String +// +// There is no explicit interface definition for this because that definition +// will not be used anywhere. If a concrete type is passed into a function +// which receives it as an interface, the struct is moved to the heap. This +// erodes memory performance. Message structs are be short lived - they are +// initialized, marshalled into a buffer and not used after that. So heap +// allocating these message structs is wasteful. Don't define Message interface +// so it's not used. Instead use function arguments. See Client.SndRcvMessage. +// +// Unmarshalling code should use the Checked variant of the Unmarshal functions +// because a malicious encoder could have manipulated payload bytes to make the +// unchecked unmarshal variants panic due to the lack of bound checking. +// Marshalling code does not need additional bound checking because the caller +// itself intializes the struct being marshalled, so it is trusted. +// +// String() implementations must ensure that the message struct doesn't escape. +// For instance, directly passing the struct to fmt.Sprintf() escapes it +// because of the implicit conversion to interface{}. + +type marshalFunc func([]byte) []byte +type unmarshalFunc func([]byte) ([]byte, bool) +type debugStringer func() string // MID (message ID) is used to identify messages to parse from payload. // @@ -174,11 +203,24 @@ func (gid GID) Ok() bool { return gid != NoGID } -// NoopMarshal is a noop implementation of marshal.Marshallable.MarshalBytes. -func NoopMarshal(b []byte) []byte { return b } +// EmptyMessage is an empty message. +type EmptyMessage struct{} -// NoopUnmarshal is a noop implementation of marshal.Marshallable.UnmarshalBytes. -func NoopUnmarshal(b []byte) ([]byte, bool) { return b, true } +// String implements fmt.Stringer.String. +func (*EmptyMessage) String() string { + return "EmptyMessage{}" +} + +// SizeBytes implements marshal.Marshallable.SizeBytes. +func (*EmptyMessage) SizeBytes() int { + return 0 +} + +// MarshalBytes implements marshal.Marshallable.MarshalBytes. +func (*EmptyMessage) MarshalBytes(dst []byte) []byte { return dst } + +// CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal. +func (*EmptyMessage) CheckedUnmarshal(src []byte) ([]byte, bool) { return src, true } // SizedString represents a string in memory. The marshalled string bytes are // preceded by a uint16 signifying the string length. @@ -213,6 +255,19 @@ func (s *SizedString) CheckedUnmarshal(src []byte) ([]byte, bool) { // array data is preceded by a uint16 signifying the array length. type StringArray []string +// String implements fmt.Stringer.String. This ensures that the string slice is +// not escaped so that callers that use a statically sized string array do not +// incur an unnecessary allocation. +func (s *StringArray) String() string { + var b strings.Builder + b.WriteString("[") + for _, str := range *s { + b.WriteString(fmt.Sprintf("%s, ", str)) + } + b.WriteString("]") + return b.String() +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (s *StringArray) SizeBytes() int { size := (*primitive.Uint16)(nil).SizeBytes() @@ -268,6 +323,14 @@ type Inode struct { Stat linux.Statx } +// MountReq is an empty requent to Mount on the connection. +type MountReq struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*MountReq) String() string { + return "MountReq{}" +} + // MountResp represents a Mount response. type MountResp struct { Root Inode @@ -278,6 +341,11 @@ type MountResp struct { SupportedMs []MID } +// String implements fmt.Stringer.String. +func (m *MountResp) String() string { + return fmt.Sprintf("MountResp{Root: %+v, MaxMessageSize: %d, SupportedMs: %+v}", m.Root, m.MaxMessageSize, m.SupportedMs) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (m *MountResp) SizeBytes() int { return m.Root.SizeBytes() + @@ -316,6 +384,14 @@ func (m *MountResp) CheckedUnmarshal(src []byte) ([]byte, bool) { return UnmarshalUnsafeMIDSlice(m.SupportedMs, srcRemain), true } +// ChannelReq is an empty requent to create a Channel. +type ChannelReq struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*ChannelReq) String() string { + return "ChannelReq{}" +} + // ChannelResp is the response to the create channel request. // // +marshal boundCheck @@ -324,6 +400,11 @@ type ChannelResp struct { dataLength uint64 } +// String implements fmt.Stringer.String. +func (c *ChannelResp) String() string { + return fmt.Sprintf("ChannelResp{dataOffset: %d, dataLength: %d}", c.dataOffset, c.dataLength) +} + // ErrorResp is returned to represent an error while handling a request. // // +marshal @@ -331,6 +412,11 @@ type ErrorResp struct { errno uint32 } +// String implements fmt.Stringer.String. +func (e *ErrorResp) String() string { + return fmt.Sprintf("ErrorResp{errno: %d}", e.errno) +} + // StatReq requests the stat results for the specified FD. // // +marshal boundCheck @@ -338,6 +424,11 @@ type StatReq struct { FD FDID } +// String implements fmt.Stringer.String. +func (s *StatReq) String() string { + return fmt.Sprintf("StatReq{FD: %d}", s.FD) +} + // SetStatReq is used to set attributeds on FDs. // // +marshal boundCheck @@ -353,6 +444,12 @@ type SetStatReq struct { Mtime linux.Timespec } +// String implements fmt.Stringer.String. +func (s *SetStatReq) String() string { + return fmt.Sprintf("SetStatReq{FD: %d, Mask: %d, Mode: %d, UID: %d, GID: %d, Size: %d, Atime: %+v, Mtime: %+v}", + s.FD, s.Mask, s.Mode, s.UID, s.GID, s.Size, s.Atime, s.Mtime) +} + // SetStatResp is used to communicate SetStat results. It contains a mask // representing the failed changes. It also contains the errno of the failed // set attribute operation. If multiple operations failed then any of those @@ -364,6 +461,11 @@ type SetStatResp struct { FailureErrNo uint32 } +// String implements fmt.Stringer.String. +func (s *SetStatResp) String() string { + return fmt.Sprintf("SetStatResp{FailureMask: %d, FailureErrNo: %d}", s.FailureMask, s.FailureErrNo) +} + // WalkReq is used to request to walk multiple path components at once. This // is used for both Walk and WalkStat. type WalkReq struct { @@ -371,6 +473,11 @@ type WalkReq struct { Path StringArray } +// String implements fmt.Stringer.String. +func (w *WalkReq) String() string { + return fmt.Sprintf("WalkReq{DirFD: %d, Path: %s}", w.DirFD, w.Path.String()) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (w *WalkReq) SizeBytes() int { return w.DirFD.SizeBytes() + w.Path.SizeBytes() @@ -423,6 +530,19 @@ type WalkResp struct { Inodes []Inode } +// String implements fmt.Stringer.String. This ensures that the Inode slice is +// not escaped so that callers that use a statically sized Inode array do not +// incur an unnecessary allocation. +func (w *WalkResp) String() string { + var arrB strings.Builder + arrB.WriteString("[") + for i := range w.Inodes { + arrB.WriteString(fmt.Sprintf("%+v, ", w.Inodes[i])) + } + arrB.WriteString("]") + return fmt.Sprintf("WalkResp{Status: %d, Inodes: %s}", w.Status, arrB.String()) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (w *WalkResp) SizeBytes() int { return w.Status.SizeBytes() + @@ -466,6 +586,11 @@ type WalkStatResp struct { Stats []linux.Statx } +// String implements fmt.Stringer.String. +func (w *WalkStatResp) String() string { + return fmt.Sprintf("WalkStatResp{Stats: %+v}", w.Stats) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (w *WalkStatResp) SizeBytes() int { return (*primitive.Uint16)(nil).SizeBytes() + (len(w.Stats) * linux.SizeOfStatx) @@ -507,6 +632,11 @@ type OpenAtReq struct { Flags uint32 } +// String implements fmt.Stringer.String. +func (o *OpenAtReq) String() string { + return fmt.Sprintf("OpenAtReq{FD: %d, Flags: %d}", o.FD, o.Flags) +} + // OpenAtResp is used to communicate the newly created FD. // // +marshal boundCheck @@ -514,6 +644,11 @@ type OpenAtResp struct { OpenFD FDID } +// String implements fmt.Stringer.String. +func (o *OpenAtResp) String() string { + return fmt.Sprintf("OpenAtResp{OpenFD: %d}", o.OpenFD) +} + // +marshal type createCommon struct { DirFD FDID @@ -530,6 +665,11 @@ type OpenCreateAtReq struct { Name SizedString } +// String implements fmt.Stringer.String. +func (o *OpenCreateAtReq) String() string { + return fmt.Sprintf("OpenCreateAtReq{DirFD: %d, Mode: %s, UID: %d, GID: %d, Flags: %d, Name: %s}", o.DirFD, o.Mode, o.UID, o.GID, o.Flags, o.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (o *OpenCreateAtReq) SizeBytes() int { return o.createCommon.SizeBytes() + o.Flags.SizeBytes() + o.Name.SizeBytes() @@ -565,11 +705,29 @@ type OpenCreateAtResp struct { _ uint32 // Need to make struct packed. } +// String implements fmt.Stringer.String. +func (o *OpenCreateAtResp) String() string { + return fmt.Sprintf("OpenCreateAtResp{Child: %+v, NewFD: %d}", o.Child, o.NewFD) +} + // FdArray is a utility struct which implements a marshallable type for // communicating an array of FDIDs. In memory, the array data is preceded by a // uint16 denoting the array length. type FdArray []FDID +// String implements fmt.Stringer.String. This ensures that the FDID slice is +// not escaped so that callers that use a statically sized FDID array do not +// incur an unnecessary allocation. +func (f *FdArray) String() string { + var b strings.Builder + b.WriteString("[") + for _, fd := range *f { + b.WriteString(fmt.Sprintf("%d, ", fd)) + } + b.WriteString("]") + return b.String() +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (f *FdArray) SizeBytes() int { return (*primitive.Uint16)(nil).SizeBytes() + (len(*f) * (*FDID)(nil).SizeBytes()) @@ -606,6 +764,11 @@ type CloseReq struct { FDs FdArray } +// String implements fmt.Stringer.String. +func (c *CloseReq) String() string { + return fmt.Sprintf("CloseReq{FDs: %s}", c.FDs.String()) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (c *CloseReq) SizeBytes() int { return c.FDs.SizeBytes() @@ -621,11 +784,24 @@ func (c *CloseReq) CheckedUnmarshal(src []byte) ([]byte, bool) { return c.FDs.CheckedUnmarshal(src) } +// CloseResp is an empty response to CloseReq. +type CloseResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*CloseResp) String() string { + return "CloseResp{}" +} + // FsyncReq is used to fsync(2) FDs. type FsyncReq struct { FDs FdArray } +// String implements fmt.Stringer.String. +func (f *FsyncReq) String() string { + return fmt.Sprintf("FsyncReq{FDs: %s}", f.FDs.String()) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (f *FsyncReq) SizeBytes() int { return f.FDs.SizeBytes() @@ -641,6 +817,14 @@ func (f *FsyncReq) CheckedUnmarshal(src []byte) ([]byte, bool) { return f.FDs.CheckedUnmarshal(src) } +// FsyncResp is an empty response to FsyncReq. +type FsyncResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*FsyncResp) String() string { + return "FsyncResp{}" +} + // PReadReq is used to pread(2) on an FD. // // +marshal boundCheck @@ -650,12 +834,22 @@ type PReadReq struct { Count uint32 } +// String implements fmt.Stringer.String. +func (r *PReadReq) String() string { + return fmt.Sprintf("PReadReq{Offset: %d, FD: %d, Count: %d}", r.Offset, r.FD, r.Count) +} + // PReadResp is used to return the result of pread(2). type PReadResp struct { NumBytes primitive.Uint64 Buf []byte } +// String implements fmt.Stringer.String. +func (r *PReadResp) String() string { + return fmt.Sprintf("PReadResp{NumBytes: %d, Buf: %v}", r.NumBytes, r.Buf) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (r *PReadResp) SizeBytes() int { return r.NumBytes.SizeBytes() + int(r.NumBytes) @@ -687,6 +881,11 @@ type PWriteReq struct { Buf []byte } +// String implements fmt.Stringer.String. +func (w *PWriteReq) String() string { + return fmt.Sprintf("PWriteReq{Offset: %d, FD: %d, NumBytes: %d, Buf: %v}", w.Offset, w.FD, w.NumBytes, w.Buf) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (w *PWriteReq) SizeBytes() int { return w.Offset.SizeBytes() + w.FD.SizeBytes() + w.NumBytes.SizeBytes() + int(w.NumBytes) @@ -726,12 +925,22 @@ type PWriteResp struct { Count uint64 } +// String implements fmt.Stringer.String. +func (w *PWriteResp) String() string { + return fmt.Sprintf("PWriteResp{Count: %d}", w.Count) +} + // MkdirAtReq is used to make MkdirAt requests. type MkdirAtReq struct { createCommon Name SizedString } +// String implements fmt.Stringer.String. +func (m *MkdirAtReq) String() string { + return fmt.Sprintf("MkdirAtReq{DirFD: %d, Mode: %s, UID: %d, GID: %d, Name: %s}", m.DirFD, m.Mode, m.UID, m.GID, m.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (m *MkdirAtReq) SizeBytes() int { return m.createCommon.SizeBytes() + m.Name.SizeBytes() @@ -763,6 +972,11 @@ type MkdirAtResp struct { ChildDir Inode } +// String implements fmt.Stringer.String. +func (m *MkdirAtResp) String() string { + return fmt.Sprintf("MkdirAtResp{ChildDir: %+v}", m.ChildDir) +} + // MknodAtReq is used to make MknodAt requests. type MknodAtReq struct { createCommon @@ -771,6 +985,11 @@ type MknodAtReq struct { Name SizedString } +// String implements fmt.Stringer.String. +func (m *MknodAtReq) String() string { + return fmt.Sprintf("MknodAtReq{DirFD: %d, Mode: %s, UID: %d, GID: %d, Minor: %d, Major: %d, Name: %s}", m.DirFD, m.Mode, m.UID, m.GID, m.Minor, m.Major, m.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (m *MknodAtReq) SizeBytes() int { return m.createCommon.SizeBytes() + m.Minor.SizeBytes() + m.Major.SizeBytes() + m.Name.SizeBytes() @@ -806,6 +1025,11 @@ type MknodAtResp struct { Child Inode } +// String implements fmt.Stringer.String. +func (m *MknodAtResp) String() string { + return fmt.Sprintf("MknodAtResp{Child: %+v}", m.Child) +} + // SymlinkAtReq is used to make SymlinkAt request. type SymlinkAtReq struct { DirFD FDID @@ -815,6 +1039,11 @@ type SymlinkAtReq struct { Target SizedString } +// String implements fmt.Stringer.String. +func (s *SymlinkAtReq) String() string { + return fmt.Sprintf("SymlinkAtReq{DirFD: %d, UID: %d, GID: %d, Name: %s, Target: %s}", s.DirFD, s.UID, s.GID, s.Name, s.Target) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (s *SymlinkAtReq) SizeBytes() int { return s.DirFD.SizeBytes() + s.UID.SizeBytes() + s.GID.SizeBytes() + s.Name.SizeBytes() + s.Target.SizeBytes() @@ -856,6 +1085,11 @@ type SymlinkAtResp struct { Symlink Inode } +// String implements fmt.Stringer.String. +func (s *SymlinkAtResp) String() string { + return fmt.Sprintf("SymlinkAtResp{Symlink: %+v}", s.Symlink) +} + // LinkAtReq is used to make LinkAt requests. type LinkAtReq struct { DirFD FDID @@ -863,6 +1097,11 @@ type LinkAtReq struct { Name SizedString } +// String implements fmt.Stringer.String. +func (l *LinkAtReq) String() string { + return fmt.Sprintf("LinkAtReq{DirFD: %d, Target: %d, Name: %s}", l.DirFD, l.Target, l.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (l *LinkAtReq) SizeBytes() int { return l.DirFD.SizeBytes() + l.Target.SizeBytes() + l.Name.SizeBytes() @@ -896,6 +1135,11 @@ type LinkAtResp struct { Link Inode } +// String implements fmt.Stringer.String. +func (l *LinkAtResp) String() string { + return fmt.Sprintf("LinkAtResp{Link: %+v}", l.Link) +} + // FStatFSReq is used to request StatFS results for the specified FD. // // +marshal boundCheck @@ -903,6 +1147,11 @@ type FStatFSReq struct { FD FDID } +// String implements fmt.Stringer.String. +func (s *FStatFSReq) String() string { + return fmt.Sprintf("FStatFSReq{FD: %d}", s.FD) +} + // StatFS is responded to a successful FStatFS request. // // +marshal boundCheck @@ -917,6 +1166,12 @@ type StatFS struct { NameLength uint64 } +// String implements fmt.Stringer.String. +func (s *StatFS) String() string { + return fmt.Sprintf("StatFS{Type: %d, BlockSize: %d, Blocks: %d, BlocksFree: %d, BlocksAvailable: %d, Files: %d, FilesFree: %d, NameLength: %d}", + s.Type, s.BlockSize, s.Blocks, s.BlocksFree, s.BlocksAvailable, s.Files, s.FilesFree, s.NameLength) +} + // FAllocateReq is used to request to fallocate(2) an FD. This has no response. // // +marshal boundCheck @@ -928,6 +1183,19 @@ type FAllocateReq struct { Length uint64 } +// String implements fmt.Stringer.String. +func (a *FAllocateReq) String() string { + return fmt.Sprintf("FAllocateReq{FD: %d, Mode: %d, Offset: %d, Length: %d}", a.FD, a.Mode, a.Offset, a.Length) +} + +// FAllocateResp is an empty response to FAllocateReq. +type FAllocateResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*FAllocateResp) String() string { + return "FAllocateResp{}" +} + // ReadLinkAtReq is used to readlinkat(2) at the specified FD. // // +marshal boundCheck @@ -935,11 +1203,21 @@ type ReadLinkAtReq struct { FD FDID } +// String implements fmt.Stringer.String. +func (r *ReadLinkAtReq) String() string { + return fmt.Sprintf("ReadLinkAtReq{FD: %d}", r.FD) +} + // ReadLinkAtResp is used to communicate ReadLinkAt results. type ReadLinkAtResp struct { Target SizedString } +// String implements fmt.Stringer.String. +func (r *ReadLinkAtResp) String() string { + return fmt.Sprintf("ReadLinkAtResp{Target: %s}", r.Target) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (r *ReadLinkAtResp) SizeBytes() int { return r.Target.SizeBytes() @@ -962,6 +1240,19 @@ type FlushReq struct { FD FDID } +// String implements fmt.Stringer.String. +func (f *FlushReq) String() string { + return fmt.Sprintf("FlushReq{FD: %d}", f.FD) +} + +// FlushResp is an empty response to FlushReq. +type FlushResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*FlushResp) String() string { + return "FlushResp{}" +} + // ConnectReq is used to make a Connect request. // // +marshal boundCheck @@ -973,6 +1264,19 @@ type ConnectReq struct { SockType uint32 } +// String implements fmt.Stringer.String. +func (c *ConnectReq) String() string { + return fmt.Sprintf("ConnectReq{FD: %d, SockType: %d}", c.FD, c.SockType) +} + +// ConnectResp is an empty response to ConnectReq. +type ConnectResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*ConnectResp) String() string { + return "ConnectResp{}" +} + // UnlinkAtReq is used to make UnlinkAt request. type UnlinkAtReq struct { DirFD FDID @@ -980,6 +1284,11 @@ type UnlinkAtReq struct { Name SizedString } +// String implements fmt.Stringer.String. +func (u *UnlinkAtReq) String() string { + return fmt.Sprintf("UnlinkAtReq{DirFD: %d, Flags: %d, Name: %s}", u.DirFD, u.Flags, u.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (u *UnlinkAtReq) SizeBytes() int { return u.DirFD.SizeBytes() + u.Flags.SizeBytes() + u.Name.SizeBytes() @@ -1006,6 +1315,14 @@ func (u *UnlinkAtReq) CheckedUnmarshal(src []byte) ([]byte, bool) { return src, false } +// UnlinkAtResp is an empty response to UnlinkAtReq. +type UnlinkAtResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*UnlinkAtResp) String() string { + return "UnlinkAtResp{}" +} + // RenameAtReq is used to make RenameAt requests. Note that the request takes in // the to-be-renamed file's FD instead of oldDir and oldName like renameat(2). type RenameAtReq struct { @@ -1015,6 +1332,11 @@ type RenameAtReq struct { NewName SizedString } +// String implements fmt.Stringer.String. +func (r *RenameAtReq) String() string { + return fmt.Sprintf("RenameAtReq{OldDir: %d, NewDir: %d, OldName: %s, NewName: %s}", r.OldDir, r.NewDir, r.OldName, r.NewName) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (r *RenameAtReq) SizeBytes() int { return r.OldDir.SizeBytes() + r.NewDir.SizeBytes() + r.OldName.SizeBytes() + r.NewName.SizeBytes() @@ -1047,6 +1369,14 @@ func (r *RenameAtReq) CheckedUnmarshal(src []byte) ([]byte, bool) { return srcRemain, true } +// RenameAtResp is an empty response to RenameAtReq. +type RenameAtResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*RenameAtResp) String() string { + return "RenameAtResp{}" +} + // Getdents64Req is used to make Getdents64 requests. // // +marshal boundCheck @@ -1059,6 +1389,11 @@ type Getdents64Req struct { Count int32 } +// String implements fmt.Stringer.String. +func (g *Getdents64Req) String() string { + return fmt.Sprintf("Getdents64Req{DirFD: %d, Count: %d}", g.DirFD, g.Count) +} + // Dirent64 is analogous to struct linux_dirent64. type Dirent64 struct { Ino primitive.Uint64 @@ -1069,6 +1404,11 @@ type Dirent64 struct { Name SizedString } +// String implements fmt.Stringer.String. +func (d *Dirent64) String() string { + return fmt.Sprintf("Dirent64{Ino: %d, DevMinor: %d, DevMajor: %d, Off: %d, Type: %d, Name: %s}", d.Ino, d.DevMinor, d.DevMajor, d.Off, d.Type, d.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (d *Dirent64) SizeBytes() int { return d.Ino.SizeBytes() + d.DevMinor.SizeBytes() + d.DevMajor.SizeBytes() + d.Off.SizeBytes() + d.Type.SizeBytes() + d.Name.SizeBytes() @@ -1107,6 +1447,11 @@ type Getdents64Resp struct { Dirents []Dirent64 } +// String implements fmt.Stringer.String. +func (g *Getdents64Resp) String() string { + return fmt.Sprintf("Getdents64Resp{Dirents: %+v}", g.Dirents) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (g *Getdents64Resp) SizeBytes() int { ret := (*primitive.Uint16)(nil).SizeBytes() @@ -1157,6 +1502,11 @@ type FGetXattrReq struct { Name SizedString } +// String implements fmt.Stringer.String. +func (g *FGetXattrReq) String() string { + return fmt.Sprintf("FGetXattrReq{FD: %d, BufSize: %d, Name: %s}", g.FD, g.BufSize, g.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (g *FGetXattrReq) SizeBytes() int { return g.FD.SizeBytes() + g.BufSize.SizeBytes() + g.Name.SizeBytes() @@ -1188,6 +1538,11 @@ type FGetXattrResp struct { Value SizedString } +// String implements fmt.Stringer.String. +func (g *FGetXattrResp) String() string { + return fmt.Sprintf("FGetXattrResp{Value: %s}", g.Value) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (g *FGetXattrResp) SizeBytes() int { return g.Value.SizeBytes() @@ -1211,6 +1566,11 @@ type FSetXattrReq struct { Value SizedString } +// String implements fmt.Stringer.String. +func (s *FSetXattrReq) String() string { + return fmt.Sprintf("FSetXattrReq{FD: %d, Flags: %d, Name: %s, Value: %s}", s.FD, s.Flags, s.Name, s.Value) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (s *FSetXattrReq) SizeBytes() int { return s.FD.SizeBytes() + s.Flags.SizeBytes() + s.Name.SizeBytes() + s.Value.SizeBytes() @@ -1243,12 +1603,25 @@ func (s *FSetXattrReq) CheckedUnmarshal(src []byte) ([]byte, bool) { return srcRemain, true } +// FSetXattrResp is an empty response to FSetXattrReq. +type FSetXattrResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*FSetXattrResp) String() string { + return "FSetXattrResp{}" +} + // FRemoveXattrReq is used to make FRemoveXattr requests. It has no response. type FRemoveXattrReq struct { FD FDID Name SizedString } +// String implements fmt.Stringer.String. +func (r *FRemoveXattrReq) String() string { + return fmt.Sprintf("FRemoveXattrReq{FD: %d, Name: %s}", r.FD, r.Name) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (r *FRemoveXattrReq) SizeBytes() int { return r.FD.SizeBytes() + r.Name.SizeBytes() @@ -1273,6 +1646,14 @@ func (r *FRemoveXattrReq) CheckedUnmarshal(src []byte) ([]byte, bool) { return src, false } +// FRemoveXattrResp is an empty response to FRemoveXattrReq. +type FRemoveXattrResp struct{ EmptyMessage } + +// String implements fmt.Stringer.String. +func (*FRemoveXattrResp) String() string { + return "FRemoveXattrResp{}" +} + // FListXattrReq is used to make FListXattr requests. // // +marshal boundCheck @@ -1282,11 +1663,21 @@ type FListXattrReq struct { Size uint64 } +// String implements fmt.Stringer.String. +func (l *FListXattrReq) String() string { + return fmt.Sprintf("FListXattrReq{FD: %d, Size: %d}", l.FD, l.Size) +} + // FListXattrResp is used to respond to FListXattr requests. type FListXattrResp struct { Xattrs StringArray } +// String implements fmt.Stringer.String. +func (l *FListXattrResp) String() string { + return fmt.Sprintf("FListXattrResp{Xattrs: %s}", l.Xattrs.String()) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (l *FListXattrResp) SizeBytes() int { return l.Xattrs.SizeBytes() diff --git a/pkg/lisafs/sample_message.go b/pkg/lisafs/sample_message.go index 25ee031cf..00919ed0d 100644 --- a/pkg/lisafs/sample_message.go +++ b/pkg/lisafs/sample_message.go @@ -15,6 +15,7 @@ package lisafs import ( + "fmt" "math/rand" "gvisor.dev/gvisor/pkg/marshal/primitive" @@ -46,6 +47,11 @@ type MsgDynamic struct { Arr []MsgSimple } +// String implements fmt.Stringer.String. +func (m *MsgDynamic) String() string { + return fmt.Sprintf("MsgDynamic{N: %d, Arr: %v}", m.N, m.Arr) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (m *MsgDynamic) SizeBytes() int { return m.N.SizeBytes() + @@ -98,6 +104,11 @@ type P9Version struct { Version string } +// String implements fmt.Stringer.String. +func (v *P9Version) String() string { + return fmt.Sprintf("P9Version{MSize: %d, Version: %s}", v.MSize, v.Version) +} + // SizeBytes implements marshal.Marshallable.SizeBytes. func (v *P9Version) SizeBytes() int { return (*primitive.Uint32)(nil).SizeBytes() + (*primitive.Uint16)(nil).SizeBytes() + len(v.Version) diff --git a/pkg/lisafs/sock.go b/pkg/lisafs/sock.go index 88210242f..8f554f9ed 100644 --- a/pkg/lisafs/sock.go +++ b/pkg/lisafs/sock.go @@ -15,6 +15,7 @@ package lisafs import ( + "fmt" "io" "golang.org/x/sys/unix" @@ -89,6 +90,11 @@ func (s *sockCommunicator) SndRcvMessage(m MID, payloadLen uint32, wantFDs uint8 return s.rcvMsg(wantFDs) } +// String implements fmt.Stringer.String. +func (s *sockCommunicator) String() string { + return fmt.Sprintf("sockComm %d", s.sock.FD()) +} + // sndPrepopulatedMsg assumes that s.buf has already been populated with // `payloadLen` bytes of data. func (s *sockCommunicator) sndPrepopulatedMsg(m MID, payloadLen uint32, fds []int) error { diff --git a/pkg/lisafs/sock_test.go b/pkg/lisafs/sock_test.go index 2819a72b0..387f4b7a8 100644 --- a/pkg/lisafs/sock_test.go +++ b/pkg/lisafs/sock_test.go @@ -195,7 +195,7 @@ func TestSndRcvMessageNoPayload(t *testing.T) { }) } -func checkMessageReceive(t *testing.T, comm *sockCommunicator, wantM MID, wantMsg interface{}) { +func checkMessageReceive(t *testing.T, comm *sockCommunicator, wantM MID, wantMsg marshal.Marshallable) { gotM, payloadLen, err := comm.rcvMsg(0) if err != nil { t.Fatalf("readMessageFrom failed: %v", err)