mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
1615 lines
42 KiB
Go
1615 lines
42 KiB
Go
// Copyright 2021 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package lisafs
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"gvisor.dev/gvisor/pkg/abi/linux"
|
|
"gvisor.dev/gvisor/pkg/cleanup"
|
|
"gvisor.dev/gvisor/pkg/errors"
|
|
"gvisor.dev/gvisor/pkg/errors/linuxerr"
|
|
"gvisor.dev/gvisor/pkg/flipcall"
|
|
"gvisor.dev/gvisor/pkg/fspath"
|
|
"gvisor.dev/gvisor/pkg/log"
|
|
"gvisor.dev/gvisor/pkg/marshal/primitive"
|
|
)
|
|
|
|
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
|
|
// received. The handler is responsible for:
|
|
//
|
|
// - Unmarshalling the request from the passed payload and interpreting it.
|
|
// - Marshalling the response into the communicator's payload buffer.
|
|
// - Return the number of payload bytes written.
|
|
// - Donate any FDs (if needed) to comm which will in turn donate it to client.
|
|
type RPCHandler func(c *Connection, comm Communicator, payloadLen uint32) (uint32, error)
|
|
|
|
var handlers = [...]RPCHandler{
|
|
Error: ErrorHandler,
|
|
Mount: MountHandler,
|
|
Channel: ChannelHandler,
|
|
FStat: FStatHandler,
|
|
SetStat: SetStatHandler,
|
|
Walk: WalkHandler,
|
|
WalkStat: WalkStatHandler,
|
|
OpenAt: OpenAtHandler,
|
|
OpenCreateAt: OpenCreateAtHandler,
|
|
Close: CloseHandler,
|
|
FSync: FSyncHandler,
|
|
PWrite: PWriteHandler,
|
|
PRead: PReadHandler,
|
|
MkdirAt: MkdirAtHandler,
|
|
MknodAt: MknodAtHandler,
|
|
SymlinkAt: SymlinkAtHandler,
|
|
LinkAt: LinkAtHandler,
|
|
FStatFS: FStatFSHandler,
|
|
FAllocate: FAllocateHandler,
|
|
ReadLinkAt: ReadLinkAtHandler,
|
|
Flush: FlushHandler,
|
|
UnlinkAt: UnlinkAtHandler,
|
|
RenameAt: RenameAtHandler,
|
|
Getdents64: Getdents64Handler,
|
|
FGetXattr: FGetXattrHandler,
|
|
FSetXattr: FSetXattrHandler,
|
|
FListXattr: FListXattrHandler,
|
|
FRemoveXattr: FRemoveXattrHandler,
|
|
Connect: ConnectHandler,
|
|
BindAt: BindAtHandler,
|
|
Listen: ListenHandler,
|
|
Accept: AcceptHandler,
|
|
ConnectWithCreds: ConnectWithCredsHandler,
|
|
RenameAt2: RenameAt2Handler,
|
|
}
|
|
|
|
// ErrorHandler handles Error message.
|
|
func ErrorHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
// Client should never send Error.
|
|
return 0, unix.EINVAL
|
|
}
|
|
|
|
// MountHandler handles the Mount RPC. Note that there can not be concurrent
|
|
// executions of MountHandler on a connection because the connection enforces
|
|
// that Mount is the first message on the connection. Only after the connection
|
|
// has been successfully mounted can other channels be created.
|
|
func MountHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var (
|
|
mountPointFD *ControlFD
|
|
mountPointHostFD = -1
|
|
mountPointStat Statx
|
|
mountNode = c.server.root
|
|
)
|
|
if err := c.server.withRenameReadLock(func() (err error) {
|
|
// Maintain extra ref on mountNode to ensure existence during walk.
|
|
mountNode.IncRef()
|
|
defer func() {
|
|
// Drop extra ref on mountNode. Wrap the defer call with a func so that
|
|
// mountNode is evaluated on execution, not on defer itself.
|
|
mountNode.DecRef(nil)
|
|
}()
|
|
|
|
// Walk to the mountpoint.
|
|
pit := fspath.Parse(c.mountPath).Begin
|
|
for pit.Ok() {
|
|
curName := pit.String()
|
|
if err := checkSafeName(curName); err != nil {
|
|
return err
|
|
}
|
|
mountNode.opMu.RLock()
|
|
if mountNode.isDeleted() {
|
|
mountNode.opMu.RUnlock()
|
|
return unix.ENOENT
|
|
}
|
|
mountNode.childrenMu.Lock()
|
|
next := mountNode.LookupChildLocked(curName)
|
|
if next == nil {
|
|
next = &Node{}
|
|
next.InitLocked(curName, mountNode)
|
|
} else {
|
|
next.IncRef()
|
|
}
|
|
mountNode.childrenMu.Unlock()
|
|
mountNode.opMu.RUnlock()
|
|
// next has an extra ref as needed. Drop extra ref on mountNode.
|
|
mountNode.DecRef(nil)
|
|
pit = pit.Next()
|
|
mountNode = next
|
|
}
|
|
|
|
// Provide Mount with read concurrency guarantee.
|
|
mountNode.opMu.RLock()
|
|
defer mountNode.opMu.RUnlock()
|
|
if mountNode.isDeleted() {
|
|
return unix.ENOENT
|
|
}
|
|
mountPointFD, mountPointStat, mountPointHostFD, err = c.impl.Mount(c, mountNode)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if mountPointHostFD >= 0 {
|
|
comm.DonateFD(mountPointHostFD)
|
|
}
|
|
resp := MountResp{
|
|
Root: Inode{
|
|
ControlFD: mountPointFD.id,
|
|
Stat: mountPointStat,
|
|
},
|
|
SupportedMs: c.impl.SupportedMessages(),
|
|
MaxMessageSize: primitive.Uint32(c.maxMessageSize),
|
|
}
|
|
respPayloadLen := uint32(resp.SizeBytes())
|
|
resp.MarshalBytes(comm.PayloadBuf(respPayloadLen))
|
|
return respPayloadLen, nil
|
|
}
|
|
|
|
// ChannelHandler handles the Channel RPC.
|
|
func ChannelHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
ch, desc, fdSock, err := c.createChannel(c.maxMessageSize)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Start servicing the channel in a separate goroutine.
|
|
c.activeWg.Add(1)
|
|
go func() {
|
|
if err := c.service(ch); err != nil {
|
|
// Don't log shutdown error which is expected during server shutdown.
|
|
if _, ok := err.(flipcall.ShutdownError); !ok {
|
|
log.Warningf("lisafs.Connection.service(channel = @%p): %v", ch, err)
|
|
}
|
|
}
|
|
c.activeWg.Done()
|
|
}()
|
|
|
|
clientDataFD, err := unix.Dup(desc.FD)
|
|
if err != nil {
|
|
unix.Close(fdSock)
|
|
ch.shutdown()
|
|
return 0, err
|
|
}
|
|
|
|
// Respond to client with successful channel creation message.
|
|
comm.DonateFD(clientDataFD)
|
|
comm.DonateFD(fdSock)
|
|
resp := ChannelResp{
|
|
dataOffset: desc.Offset,
|
|
dataLength: uint64(desc.Length),
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// FStatHandler handles the FStat RPC.
|
|
func FStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req StatReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
|
|
var resp Statx
|
|
switch t := fd.(type) {
|
|
case *ControlFD:
|
|
t.safelyRead(func() error {
|
|
resp, err = t.impl.Stat()
|
|
return err
|
|
})
|
|
case *OpenFD:
|
|
t.controlFD.safelyRead(func() error {
|
|
resp, err = t.impl.Stat()
|
|
return err
|
|
})
|
|
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.
|
|
func SetStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
|
|
var req SetStatReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
|
|
if req.Mask&^setStatSupportedMask != 0 {
|
|
return 0, unix.EPERM
|
|
}
|
|
|
|
var resp SetStatResp
|
|
if err := fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() && !c.opts.SetAttrOnDeleted {
|
|
return unix.EINVAL
|
|
}
|
|
failureMask, failureErr := fd.impl.SetStat(req)
|
|
resp.FailureMask = failureMask
|
|
if failureErr != nil {
|
|
resp.FailureErrNo = uint32(ExtractErrno(failureErr))
|
|
}
|
|
return nil
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// WalkHandler handles the Walk RPC.
|
|
func WalkHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req WalkReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
startDir, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer startDir.DecRef(nil)
|
|
if !startDir.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
|
|
// Manually marshal the inodes into the payload buffer during walk to avoid
|
|
// the slice allocation. The memory format should be WalkResp's.
|
|
var (
|
|
numInodes primitive.Uint16
|
|
status = WalkSuccess
|
|
)
|
|
respMetaSize := status.SizeBytes() + numInodes.SizeBytes()
|
|
maxPayloadSize := respMetaSize + (len(req.Path) * (*Inode)(nil).SizeBytes())
|
|
if maxPayloadSize > int(c.maxMessageSize) {
|
|
// Too much to walk, can't do.
|
|
return 0, unix.EIO
|
|
}
|
|
payloadBuf := comm.PayloadBuf(uint32(maxPayloadSize))
|
|
payloadPos := respMetaSize
|
|
if err := c.server.withRenameReadLock(func() error {
|
|
curDir := startDir
|
|
cu := cleanup.Make(func() {
|
|
// Destroy all newly created FDs until now. Read the new FDIDs from the
|
|
// payload buffer.
|
|
buf := comm.PayloadBuf(uint32(maxPayloadSize))[respMetaSize:]
|
|
var curIno Inode
|
|
for i := 0; i < int(numInodes); i++ {
|
|
buf = curIno.UnmarshalBytes(buf)
|
|
c.removeControlFDLocked(curIno.ControlFD)
|
|
}
|
|
})
|
|
defer cu.Clean()
|
|
|
|
for _, name := range req.Path {
|
|
if err := checkSafeName(name); err != nil {
|
|
return err
|
|
}
|
|
// Symlinks terminate walk. This client gets the symlink inode, but will
|
|
// have to invoke Walk again with the resolved path.
|
|
if curDir.IsSymlink() {
|
|
status = WalkComponentSymlink
|
|
break
|
|
}
|
|
curDir.node.opMu.RLock()
|
|
if curDir.node.isDeleted() {
|
|
// It is not safe to walk on a deleted directory. It could have been
|
|
// replaced with a malicious symlink.
|
|
curDir.node.opMu.RUnlock()
|
|
status = WalkComponentDoesNotExist
|
|
break
|
|
}
|
|
child, childStat, err := curDir.impl.Walk(name)
|
|
curDir.node.opMu.RUnlock()
|
|
if err == unix.ENOENT {
|
|
status = WalkComponentDoesNotExist
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Write inode into payload buffer.
|
|
i := Inode{ControlFD: child.id, Stat: childStat}
|
|
i.MarshalUnsafe(payloadBuf[payloadPos:])
|
|
payloadPos += i.SizeBytes()
|
|
numInodes++
|
|
curDir = child
|
|
}
|
|
cu.Release()
|
|
return nil
|
|
}); 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.
|
|
func WalkStatHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req WalkReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
startDir, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer startDir.DecRef(nil)
|
|
|
|
// Note that this fd is allowed to not actually be a directory when the
|
|
// only path component to walk is "" (self).
|
|
if !startDir.IsDir() {
|
|
if len(req.Path) > 1 || (len(req.Path) == 1 && len(req.Path[0]) > 0) {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
}
|
|
for i, name := range req.Path {
|
|
// First component is allowed to be "".
|
|
if i == 0 && len(name) == 0 {
|
|
continue
|
|
}
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
// 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.Uint16
|
|
maxPayloadSize := numStats.SizeBytes() + (len(req.Path) * SizeOfStatx)
|
|
if maxPayloadSize > int(c.maxMessageSize) {
|
|
// Too much to walk, can't do.
|
|
return 0, unix.EIO
|
|
}
|
|
payloadBuf := comm.PayloadBuf(uint32(maxPayloadSize))
|
|
payloadPos := numStats.SizeBytes()
|
|
|
|
if c.opts.WalkStatSupported {
|
|
if err = startDir.safelyRead(func() error {
|
|
return startDir.impl.WalkStat(req.Path, func(s 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
|
|
}
|
|
|
|
if err = c.server.withRenameReadLock(func() error {
|
|
if len(req.Path) > 0 && len(req.Path[0]) == 0 {
|
|
startDir.node.opMu.RLock()
|
|
stat, err := startDir.impl.Stat()
|
|
startDir.node.opMu.RUnlock()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stat.MarshalUnsafe(payloadBuf[payloadPos:])
|
|
payloadPos += stat.SizeBytes()
|
|
numStats++
|
|
req.Path = req.Path[1:]
|
|
}
|
|
|
|
parent := startDir
|
|
closeParent := func() {
|
|
if parent != startDir {
|
|
c.removeControlFDLocked(parent.id)
|
|
}
|
|
}
|
|
defer closeParent()
|
|
|
|
for _, name := range req.Path {
|
|
parent.node.opMu.RLock()
|
|
if parent.node.isDeleted() {
|
|
// It is not safe to walk on a deleted directory. It could have been
|
|
// replaced with a malicious symlink.
|
|
parent.node.opMu.RUnlock()
|
|
break
|
|
}
|
|
child, childStat, err := parent.impl.Walk(name)
|
|
parent.node.opMu.RUnlock()
|
|
if err != nil {
|
|
if err == unix.ENOENT {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Update with next generation.
|
|
closeParent()
|
|
parent = child
|
|
|
|
// Write results.
|
|
childStat.MarshalUnsafe(payloadBuf[payloadPos:])
|
|
payloadPos += childStat.SizeBytes()
|
|
numStats++
|
|
|
|
// Symlinks terminate walk. This client gets the symlink stat result, but
|
|
// will have to invoke Walk again with the resolved path.
|
|
if childStat.Mode&unix.S_IFMT == unix.S_IFLNK {
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}); 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.
|
|
func OpenAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req OpenAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
// Only keep allowed open flags.
|
|
if allowedFlags := req.Flags & allowedOpenFlags; allowedFlags != req.Flags {
|
|
log.Debugf("discarding open flags that are not allowed: old open flags = %d, new open flags = %d", req.Flags, allowedFlags)
|
|
req.Flags = allowedFlags
|
|
}
|
|
|
|
accessMode := req.Flags & unix.O_ACCMODE
|
|
trunc := req.Flags&unix.O_TRUNC != 0
|
|
if c.opts.Readonly && (accessMode != unix.O_RDONLY || trunc) {
|
|
return 0, unix.EROFS
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if fd.IsDir() {
|
|
// Directory is not truncatable and must be opened with O_RDONLY.
|
|
if accessMode != unix.O_RDONLY || trunc {
|
|
return 0, unix.EISDIR
|
|
}
|
|
}
|
|
|
|
var (
|
|
openFD *OpenFD
|
|
hostOpenFD int
|
|
)
|
|
if err := fd.safelyRead(func() error {
|
|
if fd.node.isDeleted() && !c.opts.OpenOnDeleted {
|
|
return unix.EINVAL
|
|
}
|
|
if fd.IsSymlink() {
|
|
return unix.EINVAL
|
|
}
|
|
openFD, hostOpenFD, err = fd.impl.Open(req.Flags)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if hostOpenFD >= 0 {
|
|
comm.DonateFD(hostOpenFD)
|
|
}
|
|
resp := OpenAtResp{OpenFD: openFD.id}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// OpenCreateAtHandler handles the OpenCreateAt RPC.
|
|
func OpenCreateAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req OpenCreateAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
// Only keep allowed open flags.
|
|
if allowedFlags := req.Flags & allowedOpenFlags; allowedFlags != req.Flags {
|
|
log.Debugf("discarding open flags that are not allowed: old open flags = %d, new open flags = %d", req.Flags, allowedFlags)
|
|
req.Flags = allowedFlags
|
|
}
|
|
|
|
name := string(req.Name)
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
|
|
var (
|
|
childFD *ControlFD
|
|
childStat Statx
|
|
openFD *OpenFD
|
|
hostOpenFD int
|
|
)
|
|
if err := fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
childFD, childStat, openFD, hostOpenFD, err = fd.impl.OpenCreate(req.Mode, req.UID, req.GID, name, uint32(req.Flags))
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if hostOpenFD >= 0 {
|
|
comm.DonateFD(hostOpenFD)
|
|
}
|
|
resp := OpenCreateAtResp{
|
|
NewFD: openFD.id,
|
|
Child: Inode{
|
|
ControlFD: childFD.id,
|
|
Stat: childStat,
|
|
},
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// CloseHandler handles the Close RPC.
|
|
func CloseHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req CloseReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
for _, fd := range req.FDs {
|
|
c.removeFD(fd)
|
|
}
|
|
|
|
// There is no response message for this.
|
|
return 0, nil
|
|
}
|
|
|
|
// FSyncHandler handles the FSync RPC.
|
|
func FSyncHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req FsyncReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
// Return the first error we encounter, but sync everything we can
|
|
// regardless.
|
|
var retErr error
|
|
for _, fdid := range req.FDs {
|
|
if err := c.fsyncFD(fdid); err != nil && retErr == nil {
|
|
retErr = err
|
|
}
|
|
}
|
|
|
|
// There is no response message for this.
|
|
return 0, retErr
|
|
}
|
|
|
|
func (c *Connection) fsyncFD(id FDID) error {
|
|
fd, err := c.lookupOpenFD(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
return fd.controlFD.safelyRead(func() error {
|
|
return fd.impl.Sync()
|
|
})
|
|
}
|
|
|
|
// PWriteHandler handles the PWrite RPC.
|
|
func PWriteHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req PWriteReq
|
|
// Note that it is an optimized Unmarshal operation which avoids any buffer
|
|
// allocation and copying. req.Buf just points to payload. This is safe to do
|
|
// as the handler owns payload and req's lifetime is limited to the handler.
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupOpenFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.writable {
|
|
return 0, unix.EBADF
|
|
}
|
|
var count uint64
|
|
if err := fd.controlFD.safelyWrite(func() error {
|
|
count, err = fd.impl.Write(req.Buf, uint64(req.Offset))
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
resp := PWriteResp{Count: count}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// PReadHandler handles the PRead RPC.
|
|
func PReadHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req PReadReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupOpenFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.readable {
|
|
return 0, unix.EBADF
|
|
}
|
|
|
|
// 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())
|
|
respPayloadLen := respMetaSize + req.Count
|
|
if respPayloadLen > c.maxMessageSize {
|
|
return 0, unix.ENOBUFS
|
|
}
|
|
payloadBuf := comm.PayloadBuf(respPayloadLen)
|
|
var n uint64
|
|
if err := fd.controlFD.safelyRead(func() error {
|
|
n, err = fd.impl.Read(payloadBuf[respMetaSize:], req.Offset)
|
|
return err
|
|
}); 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.Uint64(n)
|
|
resp.NumBytes.MarshalUnsafe(payloadBuf)
|
|
return respMetaSize + uint32(n), nil
|
|
}
|
|
|
|
// MkdirAtHandler handles the MkdirAt RPC.
|
|
func MkdirAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req MkdirAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
name := string(req.Name)
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
var (
|
|
childDir *ControlFD
|
|
childDirStat Statx
|
|
)
|
|
if err := fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
childDir, childDirStat, err = fd.impl.Mkdir(req.Mode, req.UID, req.GID, name)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
resp := MkdirAtResp{
|
|
ChildDir: Inode{
|
|
ControlFD: childDir.id,
|
|
Stat: childDirStat,
|
|
},
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// MknodAtHandler handles the MknodAt RPC.
|
|
func MknodAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req MknodAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
name := string(req.Name)
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
var (
|
|
child *ControlFD
|
|
childStat Statx
|
|
)
|
|
if err := fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
child, childStat, err = fd.impl.Mknod(req.Mode, req.UID, req.GID, name, uint32(req.Minor), uint32(req.Major))
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
resp := MknodAtResp{
|
|
Child: Inode{
|
|
ControlFD: child.id,
|
|
Stat: childStat,
|
|
},
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// SymlinkAtHandler handles the SymlinkAt RPC.
|
|
func SymlinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req SymlinkAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
name := string(req.Name)
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
var (
|
|
symlink *ControlFD
|
|
symlinkStat Statx
|
|
)
|
|
if err := fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
symlink, symlinkStat, err = fd.impl.Symlink(name, string(req.Target), req.UID, req.GID)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
resp := SymlinkAtResp{
|
|
Symlink: Inode{
|
|
ControlFD: symlink.id,
|
|
Stat: symlinkStat,
|
|
},
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// LinkAtHandler handles the LinkAt RPC.
|
|
func LinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req LinkAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
name := string(req.Name)
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
|
|
targetFD, err := c.lookupControlFD(req.Target)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer targetFD.DecRef(nil)
|
|
if targetFD.IsDir() {
|
|
// Can not create hard link to directory.
|
|
return 0, unix.EPERM
|
|
}
|
|
var (
|
|
link *ControlFD
|
|
linkStat Statx
|
|
)
|
|
if err := fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
// This is a lock ordering issue. Need to provide safe read guarantee for
|
|
// targetFD. We know targetFD is not a directory while fd is a directory.
|
|
// So targetFD would either be a descendant of fd or exist elsewhere in the
|
|
// tree. So locking fd first and targetFD later should not lead to cycles.
|
|
targetFD.node.opMu.RLock()
|
|
defer targetFD.node.opMu.RUnlock()
|
|
if targetFD.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
link, linkStat, err = targetFD.impl.Link(fd.impl, name)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
resp := LinkAtResp{
|
|
Link: Inode{
|
|
ControlFD: link.id,
|
|
Stat: linkStat,
|
|
},
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// FStatFSHandler handles the FStatFS RPC.
|
|
func FStatFSHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req FStatFSReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
var resp StatFS
|
|
if err := fd.safelyRead(func() error {
|
|
resp, err = fd.impl.StatFS()
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// FAllocateHandler handles the FAllocate RPC.
|
|
func FAllocateHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req FAllocateReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupOpenFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.writable {
|
|
return 0, unix.EBADF
|
|
}
|
|
|
|
return 0, fd.controlFD.safelyWrite(func() error {
|
|
if fd.controlFD.node.isDeleted() && !c.opts.AllocateOnDeleted {
|
|
return unix.EINVAL
|
|
}
|
|
return fd.impl.Allocate(req.Mode, req.Offset, req.Length)
|
|
})
|
|
}
|
|
|
|
// ReadLinkAtHandler handles the ReadLinkAt RPC.
|
|
func ReadLinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req ReadLinkAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsSymlink() {
|
|
return 0, unix.EINVAL
|
|
}
|
|
|
|
// 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.Uint16
|
|
n uint16
|
|
)
|
|
respMetaSize := uint32(linkLen.SizeBytes())
|
|
if fd.safelyRead(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
n, err = fd.impl.Readlink(func(dataLen uint32) []byte {
|
|
return comm.PayloadBuf(dataLen + respMetaSize)[respMetaSize:]
|
|
})
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
linkLen = primitive.Uint16(n)
|
|
linkLen.MarshalUnsafe(comm.PayloadBuf(respMetaSize))
|
|
return respMetaSize + uint32(n), nil
|
|
}
|
|
|
|
// FlushHandler handles the Flush RPC.
|
|
func FlushHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req FlushReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupOpenFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
|
|
return 0, fd.controlFD.safelyRead(func() error {
|
|
return fd.impl.Flush()
|
|
})
|
|
}
|
|
|
|
// ConnectHandler handles the Connect RPC.
|
|
func ConnectHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req ConnectReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsSocket() {
|
|
return 0, unix.ENOTSOCK
|
|
}
|
|
var sock int
|
|
if err := fd.safelyRead(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
sock, err = fd.impl.Connect(req.SockType)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
comm.DonateFD(sock)
|
|
return 0, nil
|
|
}
|
|
|
|
// ConnectWithCredsHandler handles the ConnectWithCreds RPC.
|
|
func ConnectWithCredsHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req ConnectWithCredsReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsSocket() {
|
|
return 0, unix.ENOTSOCK
|
|
}
|
|
var sock int
|
|
if err := fd.safelyRead(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
sock, err = fd.impl.ConnectWithCreds(req.SockType, req.UID, req.GID)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
comm.DonateFD(sock)
|
|
return 0, nil
|
|
}
|
|
|
|
// BindAtHandler handles the BindAt RPC.
|
|
func BindAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req BindAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
name := string(req.Name)
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
dir, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer dir.DecRef(nil)
|
|
|
|
if !dir.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
|
|
var (
|
|
childFD *ControlFD
|
|
childStat Statx
|
|
boundSocketFD *BoundSocketFD
|
|
hostSocketFD int
|
|
)
|
|
if err := dir.safelyWrite(func() error {
|
|
if dir.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
childFD, childStat, boundSocketFD, hostSocketFD, err = dir.impl.BindAt(name, uint32(req.SockType), req.Mode, req.UID, req.GID)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
comm.DonateFD(hostSocketFD)
|
|
resp := BindAtResp{
|
|
Child: Inode{
|
|
ControlFD: childFD.id,
|
|
Stat: childStat,
|
|
},
|
|
BoundSocketFD: boundSocketFD.id,
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalUnsafe(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// ListenHandler handles the Listen RPC.
|
|
func ListenHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req ListenReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
sock, err := c.lookupBoundSocketFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if err := sock.controlFD.safelyRead(func() error {
|
|
if sock.controlFD.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
return sock.impl.Listen(req.Backlog)
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
// AcceptHandler handles the Accept RPC.
|
|
func AcceptHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req AcceptReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
sock, err := c.lookupBoundSocketFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
var (
|
|
newSock int
|
|
peerAddr string
|
|
)
|
|
if err := sock.controlFD.safelyRead(func() error {
|
|
if sock.controlFD.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
var err error
|
|
newSock, peerAddr, err = sock.impl.Accept()
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
comm.DonateFD(newSock)
|
|
resp := AcceptResp{
|
|
PeerAddr: SizedString(peerAddr),
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalBytes(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// UnlinkAtHandler handles the UnlinkAt RPC.
|
|
func UnlinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req UnlinkAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
name := string(req.Name)
|
|
if err := checkSafeName(name); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
return 0, fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
|
|
fd.node.childrenMu.Lock()
|
|
childNode := fd.node.LookupChildLocked(name)
|
|
fd.node.childrenMu.Unlock()
|
|
if childNode != nil {
|
|
// Before we do the unlink itself, we need to ensure that there
|
|
// are no operations in flight on associated path node.
|
|
//
|
|
// This is another case of a lock ordering issue, but since we always
|
|
// acquire deeper in the hierarchy, we know that we are free of cycles.
|
|
childNode.opMu.Lock()
|
|
defer childNode.opMu.Unlock()
|
|
}
|
|
if err := fd.impl.Unlink(name, uint32(req.Flags)); err != nil {
|
|
return err
|
|
}
|
|
// Since fd.node.opMu is locked for writing, there will not be a concurrent
|
|
// creation of a node at that position if childNode == nil. So only remove
|
|
// node if one existed.
|
|
if childNode != nil {
|
|
fd.node.childrenMu.Lock()
|
|
fd.node.removeChildLocked(name)
|
|
fd.node.childrenMu.Unlock()
|
|
childNode.markDeletedRecursive()
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// RenameAt2Handler handles the RenameAt2 RPC.
|
|
func RenameAt2Handler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req RenameAt2Req
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
return renameAtCommon(c, comm, payloadLen, req.OldDir, req.NewDir, string(req.OldName), string(req.NewName), uint32(req.Flags))
|
|
}
|
|
|
|
// RenameAtHandler handles the RenameAt RPC.
|
|
func RenameAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req RenameAtReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
return renameAtCommon(c, comm, payloadLen, req.OldDir, req.NewDir, string(req.OldName), string(req.NewName), 0)
|
|
}
|
|
|
|
// renameAtCommon is the common implementation for RenameAt and RenameAt2.
|
|
func renameAtCommon(c *Connection, comm Communicator, payloadLen uint32, oldDirFD, newDirFD FDID, oldName, newName string, flags uint32) (uint32, error) {
|
|
if err := checkSafeName(oldName); err != nil {
|
|
return 0, err
|
|
}
|
|
if err := checkSafeName(newName); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
oldDir, err := c.lookupControlFD(oldDirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer oldDir.DecRef(nil)
|
|
newDir, err := c.lookupControlFD(newDirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer newDir.DecRef(nil)
|
|
|
|
if !oldDir.IsDir() || !newDir.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
|
|
// Hold RenameMu for writing during rename, this is important.
|
|
return 0, oldDir.safelyGlobal(func() error {
|
|
if oldDir.node.isDeleted() || newDir.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
|
|
if oldDir.node == newDir.node && oldName == newName {
|
|
// Nothing to do.
|
|
return nil
|
|
}
|
|
|
|
// Attempt the actual rename.
|
|
// RenameAt2 can be replaced by RenameAt with flags = 0.
|
|
if flags == 0 {
|
|
if err := oldDir.impl.RenameAt(oldName, newDir.impl, newName); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := oldDir.impl.RenameAt2(oldName, newDir.impl, newName, flags); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Successful, so update the node tree. Note that since we have global
|
|
// concurrency guarantee here, the node tree can not be modified
|
|
// concurrently in any way.
|
|
|
|
// First see if a file was deleted by being replaced by the rename. If so,
|
|
// detach it from node tree and mark it as deleted.
|
|
newDir.node.childrenMu.Lock()
|
|
replaced := newDir.node.removeChildLocked(newName)
|
|
newDir.node.childrenMu.Unlock()
|
|
oldDir.node.childrenMu.Lock()
|
|
renamed := oldDir.node.removeChildLocked(oldName)
|
|
oldDir.node.childrenMu.Unlock()
|
|
|
|
if replaced != nil {
|
|
if flags&linux.RENAME_EXCHANGE == 0 {
|
|
replaced.opMu.Lock()
|
|
replaced.markDeletedRecursive()
|
|
replaced.opMu.Unlock()
|
|
} else {
|
|
// Move the replaced node to the right position.
|
|
replaced.parent.DecRef(nil)
|
|
replaced.parent = oldDir.node
|
|
replaced.parent.IncRef()
|
|
replaced.name = oldName
|
|
oldDir.node.childrenMu.Lock()
|
|
oldDir.node.insertChildLocked(oldName, replaced)
|
|
oldDir.node.childrenMu.Unlock()
|
|
// Now update all FDs under the subtree rooted at replaced.
|
|
notifyRenameRecursive(replaced)
|
|
}
|
|
}
|
|
// Move the renamed node to the right position.
|
|
if renamed != nil {
|
|
renamed.parent.DecRef(nil)
|
|
renamed.parent = newDir.node
|
|
renamed.parent.IncRef()
|
|
renamed.name = newName
|
|
newDir.node.childrenMu.Lock()
|
|
newDir.node.insertChildLocked(newName, renamed)
|
|
newDir.node.childrenMu.Unlock()
|
|
// Now update all FDs under the subtree rooted at renamed.
|
|
notifyRenameRecursive(renamed)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func notifyRenameRecursive(n *Node) {
|
|
n.forEachFD(func(cfd *ControlFD) {
|
|
cfd.impl.Renamed()
|
|
cfd.forEachOpenFD(func(ofd *OpenFD) {
|
|
ofd.impl.Renamed()
|
|
})
|
|
})
|
|
|
|
n.forEachChild(func(child *Node) {
|
|
notifyRenameRecursive(child)
|
|
})
|
|
}
|
|
|
|
// Getdents64Handler handles the Getdents64 RPC.
|
|
func Getdents64Handler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req Getdents64Req
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupOpenFD(req.DirFD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
if !fd.controlFD.IsDir() {
|
|
return 0, unix.ENOTDIR
|
|
}
|
|
|
|
seek0 := false
|
|
if req.Count < 0 {
|
|
seek0 = true
|
|
req.Count = -req.Count
|
|
}
|
|
|
|
// We will manually marshal the response Getdents64Resp.
|
|
|
|
// numDirents is the number of dirents marshalled into the payload.
|
|
var numDirents primitive.Uint16
|
|
// 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.controlFD.safelyRead(func() error {
|
|
if fd.controlFD.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
return fd.impl.Getdent64(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.
|
|
func FGetXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req FGetXattrReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
|
|
// Manually marshal FGetXattrResp to avoid allocations and copying.
|
|
// FGetXattrResp simply is a wrapper around SizedString.
|
|
var valueLen primitive.Uint16
|
|
respMetaSize := uint32(valueLen.SizeBytes())
|
|
var n uint16
|
|
if err := fd.safelyRead(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
n, err = fd.impl.GetXattr(string(req.Name), uint32(req.BufSize), func(dataLen uint32) []byte {
|
|
return comm.PayloadBuf(dataLen + respMetaSize)[respMetaSize:]
|
|
})
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
payloadBuf := comm.PayloadBuf(respMetaSize)
|
|
valueLen = primitive.Uint16(n)
|
|
valueLen.MarshalBytes(payloadBuf)
|
|
return respMetaSize + uint32(n), nil
|
|
}
|
|
|
|
// FSetXattrHandler handles the FSetXattr RPC.
|
|
func FSetXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req FSetXattrReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
return 0, fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
return fd.impl.SetXattr(string(req.Name), string(req.Value), uint32(req.Flags))
|
|
})
|
|
}
|
|
|
|
// FListXattrHandler handles the FListXattr RPC.
|
|
func FListXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
var req FListXattrReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
|
|
var resp FListXattrResp
|
|
if fd.safelyRead(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
resp.Xattrs, err = fd.impl.ListXattr(req.Size)
|
|
return err
|
|
}); err != nil {
|
|
return 0, err
|
|
}
|
|
respLen := uint32(resp.SizeBytes())
|
|
resp.MarshalBytes(comm.PayloadBuf(respLen))
|
|
return respLen, nil
|
|
}
|
|
|
|
// FRemoveXattrHandler handles the FRemoveXattr RPC.
|
|
func FRemoveXattrHandler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
|
|
if c.opts.Readonly {
|
|
return 0, unix.EROFS
|
|
}
|
|
var req FRemoveXattrReq
|
|
if _, ok := req.CheckedUnmarshal(comm.PayloadBuf(payloadLen)); !ok {
|
|
return 0, unix.EIO
|
|
}
|
|
|
|
fd, err := c.lookupControlFD(req.FD)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer fd.DecRef(nil)
|
|
|
|
return 0, fd.safelyWrite(func() error {
|
|
if fd.node.isDeleted() {
|
|
return unix.EINVAL
|
|
}
|
|
return fd.impl.RemoveXattr(string(req.Name))
|
|
})
|
|
}
|
|
|
|
// checkSafeName validates the name and returns nil or returns an error.
|
|
func checkSafeName(name string) error {
|
|
if name != "" && !strings.Contains(name, "/") && name != "." && name != ".." {
|
|
return nil
|
|
}
|
|
return unix.EINVAL
|
|
}
|
|
|
|
// ExtractErrno extracts a unix.Errno from an error, best effort.
|
|
func ExtractErrno(err error) unix.Errno {
|
|
errno, _ := TryExtractErrno(err)
|
|
return errno
|
|
}
|
|
|
|
// TryExtractErrno extracts a unix.Errno from an error, and reports whether it
|
|
// was successful. If unsuccessful, the returned errno is EIO.
|
|
func TryExtractErrno(err error) (unix.Errno, bool) {
|
|
switch err {
|
|
case os.ErrNotExist:
|
|
return unix.ENOENT, true
|
|
case os.ErrExist:
|
|
return unix.EEXIST, true
|
|
case os.ErrPermission:
|
|
return unix.EACCES, true
|
|
case os.ErrInvalid:
|
|
return unix.EINVAL, true
|
|
}
|
|
|
|
// Attempt to unwrap.
|
|
switch e := err.(type) {
|
|
case *errors.Error:
|
|
return linuxerr.ToUnix(e), true
|
|
case unix.Errno:
|
|
return e, true
|
|
case *os.PathError:
|
|
return TryExtractErrno(e.Err)
|
|
case *os.SyscallError:
|
|
return TryExtractErrno(e.Err)
|
|
case *os.LinkError:
|
|
return TryExtractErrno(e.Err)
|
|
}
|
|
|
|
// Default case.
|
|
log.Warningf("unknown error: %v", err)
|
|
return unix.EIO, false
|
|
}
|