Implement renameat2.

The flags RENAME_EXCHANGE and RENAME_NOREPLACE are supported.

Fix #7895

PiperOrigin-RevId: 885187932
This commit is contained in:
Jing Chen
2026-03-17 13:43:32 -07:00
committed by gVisor bot
parent 6272d6b8ae
commit 61f761343f
17 changed files with 348 additions and 79 deletions
+7
View File
@@ -52,3 +52,10 @@ func DirentNames(dirfd int) ([]string, error) {
})
return names, err
}
// RenameAt is a convenience wrapper to make the renameat syscall.
//
// RenameAt is equivalent to RenameAt2 with a zero flags argument.
func RenameAt(oldDirFD int, oldName string, newDirFD int, newName string) error {
return RenameAt2(oldDirFD, oldName, newDirFD, newName, 0)
}
+4 -5
View File
@@ -55,9 +55,8 @@ func Utimensat(dirFd int, name string, times [2]unix.Timespec, flags int) error
return nil
}
// RenameAt is a convenience wrapper to make the renameat(2) syscall. It
// additionally handles empty names.
func RenameAt(oldDirFD int, oldName string, newDirFD int, newName string) error {
// RenameAt2 is a convenience wrapper to make the renameat syscall.
func RenameAt2(oldDirFD int, oldName string, newDirFD int, newName string, flags uint32) error {
var oldNamePtr unsafe.Pointer
if oldName != "" {
nameBytes, err := unix.BytePtrFromString(oldName)
@@ -76,12 +75,12 @@ func RenameAt(oldDirFD int, oldName string, newDirFD int, newName string) error
}
if _, _, errno := unix.Syscall6(
unix.SYS_RENAMEAT,
unix.SYS_RENAMEAT2,
uintptr(oldDirFD),
uintptr(oldNamePtr),
uintptr(newDirFD),
uintptr(newNamePtr),
0,
uintptr(flags),
0); errno != 0 {
return syserr.FromHost(errno).ToError()
+2 -1
View File
@@ -300,7 +300,7 @@ MID | Message | Request | Response
20 | Flush | FlushReq | | Flush may be called before Close on an Open FD. It cleans up the file state. Its behavior is implementation specific. The server must provide a read concurrency guarantee on the file node during this operation.
21 | Connect | ConnectReq | Donates: \[sockFD\] | Connect is analogous to calling socket(2) and then connect(2) on that socket FD. The socket FD is created using socket(AF\_UNIX, ConnectReq.sockType, 0). ConnectReq.fd must be a Control FD on a socket file that is connect(2)-ed to. On success, the socket FD is donated. The server must provide a read concurrency guarantee on the file node during this operation.
22 | UnlinkAt | UnlinkAtReq | | UnlinkAt is analogous to unlinkat(2). Fields in UnlinkAtReq are similar to unlinkat(2) arguments. UnlinkAtReq.dirFD must be a Control FD for the directory inside which the child named UnlinkAtReq.name is to be unlinked. The server must provide a write concurrency guarantee on the directory and to-be-deleted file node during this operation.
23 | RenameAt | RenameAtReq | | RenameAt is analogous to renameat(2). Fields in RenameAtReq are similar to renameat(2) arguments. RenameAtReq.oldDir and RenameAtReq.newDir must be Control FDs on the old directory and new directory respectively. The file named RenameAtReq.oldName inside old directory is renamed into new directory with the name RenameAtReq.newName. The server must provide global concurrency guarantee during this operation.
23 | RenameAt | RenameAtReq | | RenameAt is equivalent to RenameAt2 with a 0 flag argument.
24 | Getdents64 | Getdents64Req | Getdents64Resp | Getdents64 is analogous to getdents64(2). Fields in Getdents64Req are similar to getdents64(2) arguments. Getdents64Req.dirFD should be a Open FD on a directory. It returns an array of directory entries (Dirent). Each dirent also contains the dev\_t for the entry inode. This operation advances the open directory FD’s offset. The server must provide a read concurrency guarantee on the file node during this operation.
25 | FGetXattr | FGetXattrReq | FGetXattrResp | FGetXattr is analogous to fgetxattr(2). Fields in FGetXattrReq are similar to fgetxattr(2) arguments. It must be invoked on a Control FD. The server must provide a read concurrency guarantee on the file node during this operation.
26 | FSetXattr | FSetXattrReq | | FSetXattr is analogous to fsetxattr(2). Fields in FSetXattrReq are similar to fsetxattr(2) arguments. It must be invoked on a Control FD. The server must provide a write concurrency guarantee on the file node during this operation.
@@ -309,6 +309,7 @@ MID | Message | Request | Response
29 | BindAt | BindAtReq | BindAtResp<br>Donates: \[sockFD\] | BindAt is analogous to calling socket(2) and then bind(2) on that socket FD with a path. The path which is binded to is the host path of the directory represented by the control FD BindAtReq.DirFD + ‘/’ + BindAtReq.Name. The socket FD is created using socket(AF\_UNIX, BindAtReq.sockType, 0). It additionally allows the client to set the UID and GID for the newly created socket. On success, the socket FD is donated to the client. The client may use this donated socket FD to poll for notifications. The client may listen(2) and accept(2) from the FD if syscall filters permit. There are other RPCs to perform those operations. On success a Bound Socket FD is also returned along with an Inode for the newly created socket file. The server must provide a write concurrency guarantee on the directory node during this operation.
30 | Listen | ListenReq | | Listen is analogous to calling listen(2) on the host socket FD represented by the Bound Socket FD ListenReq.fd with backlog ListenReq.backlog. The server must provide a read concurrency guarantee on the socket node during this operation.
31 | Accept | AcceptReq | AcceptResp<br>Donates: \[connFD\] | Accept is analogous to calling accept(2) on the host socket FD represented by the Bound Socket FD AcceptReq.fd. On success, Accept donates the connection FD which was accepted and also returns the peer address as a string in AcceptResp.peerAddr. The server may choose to protect the peer address by returning an empty string. Accept must not block. The server must provide a read concurrency guarantee on the socket node during this operation.
33 | RenameAt2 | RenameAt2Req | | RenameAt2 is analogous to renameat2. Fields in RenameAtReq are similar to renameat2 arguments. RenameAtReq.oldDir and RenameAtReq.newDir must be Control FDs on the old directory and new directory respectively. The file named RenameAt2Req.oldName inside old directory is renamed into new directory with the name RenameAtReq.newName. The server must provide global concurrency guarantee during this operation.
### Chunking
+23 -2
View File
@@ -513,9 +513,30 @@ func (f *ClientFD) UnlinkAt(ctx context.Context, name string, flags uint32) erro
return err
}
// RenameAt makes the RenameAt RPC which renames oldName inside directory f to
// RenameAt makes the RenameAt or RenameAt2 RPC which renames oldName inside directory f to
// newDirFD directory with name newName.
func (f *ClientFD) RenameAt(ctx context.Context, oldName string, newDirFD FDID, newName string) error {
func (f *ClientFD) RenameAt(ctx context.Context, oldName string, newDirFD FDID, newName string, flags uint32) error {
if flags != 0 {
if !f.client.IsSupported(RenameAt2) {
// Returns EINVAL for backward compatibility.
return unix.EINVAL
}
req := RenameAt2Req{
RenameAtReq: RenameAtReq{
OldDir: f.fd,
OldName: SizedString(oldName),
NewDir: newDirFD,
NewName: SizedString(newName),
},
Flags: primitive.Uint32(flags),
}
var resp RenameAtResp
ctx.UninterruptibleSleepStart()
err := f.client.SndRcvMessage(RenameAt2, uint32(req.SizeBytes()), req.MarshalBytes, resp.CheckedUnmarshal, nil, req.String, resp.String)
ctx.UninterruptibleSleepFinish()
return err
}
req := RenameAtReq{
OldDir: f.fd,
OldName: SizedString(oldName),
+8
View File
@@ -516,6 +516,14 @@ type ControlFDImpl interface {
// On the server, RenameAt has a global concurrency guarantee.
RenameAt(oldName string, newDir ControlFDImpl, newName string) error
// RenameAt2 renames a given file to a new name in a potentially new directory.
//
// oldName must be a name relative to this file, which must be a directory.
// newName is a name relative to newDir.
//
// On the server, RenameAt2 has a global concurrency guarantee.
RenameAt2(oldName string, newDir ControlFDImpl, newName string, flags uint32) error
// Renamed is called to notify the FD implementation that the file has been
// renamed. FD implementation may update its state accordingly.
//
+48 -14
View File
@@ -81,6 +81,7 @@ var handlers = [...]RPCHandler{
Listen: ListenHandler,
Accept: AcceptHandler,
ConnectWithCreds: ConnectWithCredsHandler,
RenameAt2: RenameAt2Handler,
}
// ErrorHandler handles Error message.
@@ -1266,6 +1267,18 @@ func UnlinkAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint3
})
}
// RenameAt2Handler handles the RenameAt2 RPC.
func RenameAt2Handler(c *Connection, comm Communicator, payloadLen uint32) (uint32, error) {
if c.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.readonly {
@@ -1275,22 +1288,24 @@ func RenameAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint3
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)
}
oldName := string(req.OldName)
// 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
}
newName := string(req.NewName)
if err := checkSafeName(newName); err != nil {
return 0, err
}
oldDir, err := c.lookupControlFD(req.OldDir)
oldDir, err := c.lookupControlFD(oldDirFD)
if err != nil {
return 0, err
}
defer oldDir.DecRef(nil)
newDir, err := c.lookupControlFD(req.NewDir)
newDir, err := c.lookupControlFD(newDirFD)
if err != nil {
return 0, err
}
@@ -1312,8 +1327,15 @@ func RenameAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint3
}
// Attempt the actual rename.
if err := oldDir.impl.RenameAt(oldName, newDir.impl, newName); err != nil {
return err
// 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
@@ -1325,16 +1347,29 @@ func RenameAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint3
newDir.node.childrenMu.Lock()
replaced := newDir.node.removeChildLocked(newName)
newDir.node.childrenMu.Unlock()
if replaced != nil {
replaced.opMu.Lock()
replaced.markDeletedRecursive()
replaced.opMu.Unlock()
}
// Now move the renamed node to the right position.
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
@@ -1343,7 +1378,6 @@ func RenameAtHandler(c *Connection, comm Communicator, payloadLen uint32) (uint3
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)
}
+40 -1
View File
@@ -176,6 +176,9 @@ const (
// ConnectWithCreds is analogous to connect(2) but it asks the server
// to connect with the provided effective uid/gid.
ConnectWithCreds MID = 32
// RenameAt2 is loosely analogous to renameat2(2).
RenameAt2 MID = 33
)
const (
@@ -1539,7 +1542,43 @@ func (r *RenameAtReq) CheckedUnmarshal(src []byte) ([]byte, bool) {
return srcRemain, true
}
// RenameAtResp is an empty response to RenameAtReq.
// RenameAt2Req is used to make RenameAt2 requests. Note that the request takes in
// the to-be-renamed file's FD instead of oldDir and oldName like renameat(2).
type RenameAt2Req struct {
RenameAtReq
Flags primitive.Uint32
}
// String implements fmt.Stringer.String.
func (r *RenameAt2Req) String() string {
return fmt.Sprintf("RenameAt2Req{OldDir: %d, NewDir: %d, OldName: %s, NewName: %s, Flags: %#x}", r.OldDir, r.NewDir, r.OldName, r.NewName, r.Flags)
}
// SizeBytes implements marshal.Marshallable.SizeBytes.
func (r *RenameAt2Req) SizeBytes() int {
return r.RenameAtReq.SizeBytes() + r.Flags.SizeBytes()
}
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
func (r *RenameAt2Req) MarshalBytes(dst []byte) []byte {
dst = r.RenameAtReq.MarshalBytes(dst)
return r.Flags.MarshalBytes(dst)
}
// CheckedUnmarshal implements marshal.CheckedMarshallable.CheckedUnmarshal.
func (r *RenameAt2Req) CheckedUnmarshal(src []byte) ([]byte, bool) {
if r.SizeBytes() > len(src) {
return src, false
}
srcRemain, ok := r.RenameAtReq.CheckedUnmarshal(src)
if !ok {
return src, false
}
srcRemain = r.Flags.UnmarshalUnsafe(srcRemain)
return srcRemain, true
}
// RenameAtResp is an empty response to RenameAtReq and RenameAt2Req.
type RenameAtResp struct{ EmptyMessage }
// String implements fmt.Stringer.String.
+1 -1
View File
@@ -571,7 +571,7 @@ func testRename(ctx context.Context, t *testing.T, tester Tester, root lisafs.Cl
defer closeFD(ctx, t, tempDir)
// Move tempFile into tempDir.
if err := root.RenameAt(ctx, name, tempDir.ID(), "movedFile"); err != nil {
if err := root.RenameAt(ctx, name, tempDir.ID(), "movedFile", 0); err != nil {
t.Fatalf("rename failed: %v", err)
}
+1 -1
View File
@@ -76,7 +76,7 @@ func main() {
unix.SYS_READLINKAT: seccomp.MatchAll{},
unix.SYS_READV: seccomp.MatchAll{},
unix.SYS_RECVMSG: seccomp.MatchAll{},
unix.SYS_RENAMEAT: seccomp.MatchAll{},
unix.SYS_RENAMEAT2: seccomp.MatchAll{},
unix.SYS_RESTART_SYSCALL: seccomp.MatchAll{},
unix.SYS_RT_SIGACTION: seccomp.MatchAll{},
unix.SYS_RT_SIGPROCMASK: seccomp.MatchAll{},
+104 -49
View File
@@ -1383,7 +1383,15 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return err
}
if opts.Flags&^linux.RENAME_NOREPLACE != 0 {
// Support flags:
// - RENAME_NOREPLACE
// - RENAME_EXCHANGE
if opts.Flags&^(linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE) != 0 {
return linuxerr.EINVAL
}
// RENAME_NOREPLACE can't be employed together with RENAME_EXCHANGE.
if opts.Flags&(linux.RENAME_EXCHANGE|linux.RENAME_NOREPLACE) == linux.RENAME_EXCHANGE|linux.RENAME_NOREPLACE {
return linuxerr.EINVAL
}
if fs.opts.interop == InteropModeShared && opts.Flags&linux.RENAME_NOREPLACE != 0 {
@@ -1488,6 +1496,11 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
return linuxerr.ENOTDIR
}
}
} else { // replaced == nil
if opts.Flags&linux.RENAME_EXCHANGE != 0 {
// RENAME_EXCHANGE requires that the target file exist.
return linuxerr.ENOENT
}
}
if oldParent == newParent && oldName == newName {
@@ -1501,11 +1514,11 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
// Update the remote filesystem.
if !renamed.inode.isSynthetic() {
if err := oldParent.inode.rename(ctx, oldName, newParent, newName); err != nil {
if err := oldParent.inode.rename(ctx, oldName, newParent, newName, opts.Flags); err != nil {
vfsObj.AbortRenameDentry(&renamed.vfsd, replacedVFSD)
return err
}
} else if replaced != nil && !replaced.inode.isSynthetic() {
} else if replaced != nil && !replaced.inode.isSynthetic() && opts.Flags&linux.RENAME_EXCHANGE == 0 {
// We are replacing an existing real file with a synthetic one, so we
// need to unlink the former.
flags := uint32(0)
@@ -1526,57 +1539,99 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
defer oldParent.childrenMu.Unlock()
}
toDecRef = vfsObj.CommitRenameReplaceDentry(ctx, &renamed.vfsd, replacedVFSD)
if replaced != nil {
replaced.setDeleted()
// If an extra reference is held on replaced as described by the
// comment for dentry.refs, drop that reference now. We can't race with
// fs.unlinkAt() or invalidation since fs.renameMu has been locked for
// writing since before we obtained replaced.
if replaced.inode.isSynthetic() {
newParent.syntheticChildren--
replaced.decRefNoCaching()
} else if replaced.inode.endpoint != nil {
replaced.decRefNoCaching()
if opts.Flags&linux.RENAME_EXCHANGE != 0 {
if renamed != nil {
vfsObj.CommitRenameExchangeDentry(&renamed.vfsd, replacedVFSD)
}
ds = appendDentry(ds, replaced)
// Remove the replaced entry from its parent's cache.
delete(newParent.children, newName)
}
oldParent.cacheNegativeLookupLocked(oldName) // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
if renamed.inode.isSynthetic() {
oldParent.syntheticChildren--
newParent.syntheticChildren++
}
// We have d.opMu for writing, so no need to check for existence of a
// child with the given name. We could not have raced.
newParent.cacheNewChildLocked(renamed, newName)
oldParent.decRefNoCaching()
if oldParent != newParent {
ds = appendDentry(ds, newParent)
ds = appendDentry(ds, oldParent)
}
// Update metadata.
if renamed.inode.cachedMetadataAuthoritative() {
renamed.touchCtime()
}
if oldParent.inode.cachedMetadataAuthoritative() {
oldParent.clearDirentsLocked()
oldParent.touchCMtime()
if renamed.isDir() {
oldParent.decLinks()
if oldParent != newParent {
switch {
case replaced.inode.isSynthetic() && !renamed.inode.isSynthetic():
newParent.syntheticChildren--
oldParent.syntheticChildren++ // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
case !replaced.inode.isSynthetic() && renamed.inode.isSynthetic():
newParent.syntheticChildren++
oldParent.syntheticChildren-- // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
}
renamed.parent.Store(newParent)
replaced.parent.Store(oldParent)
}
}
if newParent.inode.cachedMetadataAuthoritative() {
newParent.clearDirentsLocked()
newParent.touchCMtime()
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
// Increase the link count if we did not replace another directory.
newParent.incLinks()
renamed.name = newName
replaced.name = oldName
newParent.children[newName] = renamed
oldParent.children[oldName] = replaced // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
// Update metadata.
if renamed.inode.cachedMetadataAuthoritative() {
renamed.touchCtime()
}
if replaced.inode.cachedMetadataAuthoritative() {
replaced.touchCtime()
}
if oldParent.inode.cachedMetadataAuthoritative() {
oldParent.clearDirentsLocked()
oldParent.touchCMtime()
}
if oldParent != newParent && newParent.inode.cachedMetadataAuthoritative() {
newParent.clearDirentsLocked()
newParent.touchCMtime()
}
// Sends notifications for both the renamed and replaced dentries.
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
vfs.InotifyRename(ctx, &replaced.inode.watches, &newParent.inode.watches, &oldParent.inode.watches, newName, oldName, replaced.isDir())
} else {
toDecRef = vfsObj.CommitRenameReplaceDentry(ctx, &renamed.vfsd, replacedVFSD)
if replaced != nil {
replaced.setDeleted()
// If an extra reference is held on replaced as described by the
// comment for dentry.refs, drop that reference now. We can't race with
// fs.unlinkAt() or invalidation since fs.renameMu has been locked for
// writing since before we obtained replaced.
if replaced.inode.isSynthetic() {
newParent.syntheticChildren--
replaced.decRefNoCaching()
} else if replaced.inode.endpoint != nil {
replaced.decRefNoCaching()
}
ds = appendDentry(ds, replaced)
// Remove the replaced entry from its parent's cache.
delete(newParent.children, newName)
}
oldParent.cacheNegativeLookupLocked(oldName) // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
if renamed.inode.isSynthetic() {
oldParent.syntheticChildren--
newParent.syntheticChildren++
}
// We have d.opMu for writing, so no need to check for existence of a
// child with the given name. We could not have raced.
newParent.cacheNewChildLocked(renamed, newName)
oldParent.decRefNoCaching()
if oldParent != newParent {
ds = appendDentry(ds, newParent)
ds = appendDentry(ds, oldParent)
}
// Update metadata.
if renamed.inode.cachedMetadataAuthoritative() {
renamed.touchCtime()
}
if oldParent.inode.cachedMetadataAuthoritative() {
oldParent.clearDirentsLocked()
oldParent.touchCMtime()
if renamed.isDir() {
oldParent.decLinks()
}
}
if newParent.inode.cachedMetadataAuthoritative() {
newParent.clearDirentsLocked()
newParent.touchCMtime()
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
// Increase the link count if we did not replace another directory.
newParent.incLinks()
}
}
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
}
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
return nil
}
+3 -3
View File
@@ -499,12 +499,12 @@ func (i *inode) unlink(ctx context.Context, name string, flags uint32) error {
}
// Precondition: !d.isSynthetic().
func (i *inode) rename(ctx context.Context, oldName string, newParent *dentry, newName string) error {
func (i *inode) rename(ctx context.Context, oldName string, newParent *dentry, newName string, flags uint32) error {
switch it := i.impl.(type) {
case *lisafsInode:
return it.controlFD.RenameAt(ctx, oldName, newParent.inode.impl.(*lisafsInode).controlFD.ID(), newName)
return it.controlFD.RenameAt(ctx, oldName, newParent.inode.impl.(*lisafsInode).controlFD.ID(), newName, flags)
case *directfsInode:
return fsutil.RenameAt(it.controlFD, oldName, newParent.inode.impl.(*directfsInode).controlFD, newName)
return fsutil.RenameAt2(it.controlFD, oldName, newParent.inode.impl.(*directfsInode).controlFD, newName, flags)
default:
panic("unknown inode implementation")
}
+2 -1
View File
@@ -442,11 +442,12 @@ func hostFilesystemFilters() seccomp.SyscallRules {
seccomp.AnyValue{},
seccomp.AnyValue{},
},
unix.SYS_RENAMEAT: seccomp.PerArg{
unix.SYS_RENAMEAT2: seccomp.PerArg{
seccomp.NonNegativeFD{},
seccomp.AnyValue{},
seccomp.NonNegativeFD{},
seccomp.AnyValue{},
seccomp.AnyValue{},
},
archFstatAtSysNo(): seccomp.PerArg{
seccomp.NonNegativeFD{},
+1 -1
View File
@@ -254,7 +254,7 @@ var lisafsFilters = seccomp.MakeSyscallRules(map[uintptr]seccomp.SyscallRule{
unix.SYS_MKDIRAT: seccomp.MatchAll{},
unix.SYS_MKNODAT: seccomp.MatchAll{},
unix.SYS_READLINKAT: seccomp.MatchAll{},
unix.SYS_RENAMEAT: seccomp.MatchAll{},
unix.SYS_RENAMEAT2: seccomp.MatchAll{},
unix.SYS_SYMLINKAT: seccomp.MatchAll{},
unix.SYS_FTRUNCATE: seccomp.MatchAll{},
unix.SYS_UNLINKAT: seccomp.MatchAll{},
+6
View File
@@ -193,6 +193,7 @@ func (s *LisafsServer) SupportedMessages() []lisafs.MID {
lisafs.Listen,
lisafs.Accept,
lisafs.ConnectWithCreds,
lisafs.RenameAt2,
}
}
@@ -996,6 +997,11 @@ func (fd *controlFDLisa) RenameAt(oldName string, newDir lisafs.ControlFDImpl, n
return fsutil.RenameAt(fd.hostFD, oldName, newDir.(*controlFDLisa).hostFD, newName)
}
// RenameAt2 implements lisafs.ControlFDImpl.RenameAt2.
func (fd *controlFDLisa) RenameAt2(oldName string, newDir lisafs.ControlFDImpl, newName string, flags uint32) error {
return fsutil.RenameAt2(fd.hostFD, oldName, newDir.(*controlFDLisa).hostFD, newName, flags)
}
// Renamed implements lisafs.ControlFDImpl.Renamed.
func (fd *controlFDLisa) Renamed() {
// controlFDLisa does not have any state to update on rename.
+1
View File
@@ -2315,6 +2315,7 @@ cc_binary(
"//test/util:cleanup",
"//test/util:file_descriptor",
"//test/util:fs_util",
"//test/util:posix_error",
"//test/util:save_util",
"//test/util:temp_path",
"//test/util:test_main",
+93
View File
@@ -12,22 +12,26 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/strings/string_view.h"
#include "test/util/capability_util.h"
#include "test/util/cleanup.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
#include "test/util/posix_error.h"
#include "test/util/save_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
using ::testing::AnyOf;
using ::testing::Matches;
namespace gvisor {
namespace testing {
@@ -499,6 +503,95 @@ TEST(Renameat2Test, NoReplaceDot) {
SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL, EEXIST)));
}
TEST(Renameat2Test, ExchangeDirectories) {
absl::string_view c1 = "xyz";
absl::string_view c2 = "abc";
const auto d1 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
const auto d2 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
auto f1 = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateFileWith(d1.path(), c1, TempPath::kDefaultFileMode));
auto f2 = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateFileWith(d2.path(), c2, TempPath::kDefaultFileMode));
// renameat2 returns EINVAL if the flag is unsupported.
auto rename_result = renameat2(AT_FDCWD, d1.path().c_str(), AT_FDCWD,
d2.path().c_str(), RENAME_EXCHANGE);
EXPECT_THAT(rename_result,
AnyOf(SyscallFailsWithErrno(EINVAL), SyscallSucceeds()));
if (Matches(SyscallSucceeds())(rename_result)) {
EXPECT_THAT(Exists(JoinPath(d1.path(), Basename(f2.path()))),
IsPosixErrorOkAndHolds(true));
EXPECT_THAT(Exists(JoinPath(d1.path(), Basename(f1.path()))),
IsPosixErrorOkAndHolds(false));
EXPECT_THAT(Exists(JoinPath(d2.path(), Basename(f1.path()))),
IsPosixErrorOkAndHolds(true));
EXPECT_THAT(Exists(JoinPath(d2.path(), Basename(f2.path()))),
IsPosixErrorOkAndHolds(false));
} else {
EXPECT_THAT(Exists(JoinPath(d1.path(), Basename(f2.path()))),
IsPosixErrorOkAndHolds(false));
EXPECT_THAT(Exists(JoinPath(d1.path(), Basename(f1.path()))),
IsPosixErrorOkAndHolds(true));
EXPECT_THAT(Exists(JoinPath(d2.path(), Basename(f1.path()))),
IsPosixErrorOkAndHolds(false));
EXPECT_THAT(Exists(JoinPath(d2.path(), Basename(f2.path()))),
IsPosixErrorOkAndHolds(true));
}
}
TEST(Renameat2Test, PathDoesNotExist) {
const auto d1 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
// d2 doesn't exist.
const auto d2 = NewTempAbsPathInDir(GetAbsoluteTestTmpdir());
EXPECT_THAT(renameat2(AT_FDCWD, d1.path().c_str(), AT_FDCWD, d2.c_str(),
RENAME_EXCHANGE),
SyscallFailsWithErrno(AnyOf(ENOSYS, EINVAL, ENOENT)));
// renameat2 returns EINVAL if the flag is unsupported.
EXPECT_THAT(renameat2(AT_FDCWD, d2.c_str(), AT_FDCWD, d1.path().c_str(),
RENAME_EXCHANGE),
SyscallFailsWithErrno(AnyOf(EINVAL, ENOENT)));
}
TEST(Renameat2Test, InvalidFlags) {
const auto d1 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
const auto d2 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
// RENAME_NOREPLACE can't be used with RENAME_EXCHANGE.
EXPECT_THAT(renameat2(AT_FDCWD, d1.path().c_str(), AT_FDCWD,
d2.path().c_str(), RENAME_EXCHANGE | RENAME_NOREPLACE),
SyscallFailsWithErrno(EINVAL));
}
TEST(Renameat2Test, ExchangeFiles) {
absl::string_view c1 = "xyz";
absl::string_view c2 = "abc";
const auto p1 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
const auto p2 =
ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(GetAbsoluteTestTmpdir()));
const auto f1 = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateFileWith(p1.path(), c1, TempPath::kDefaultFileMode));
const auto f2 = ASSERT_NO_ERRNO_AND_VALUE(
TempPath::CreateFileWith(p2.path(), c2, TempPath::kDefaultFileMode));
auto rename_result = renameat2(AT_FDCWD, f1.path().c_str(), AT_FDCWD,
f2.path().c_str(), RENAME_EXCHANGE);
// renameat2 returns EINVAL if the flag is unsupported.
EXPECT_THAT(rename_result,
AnyOf(SyscallFailsWithErrno(EINVAL), SyscallSucceeds()));
if (Matches(SyscallSucceeds())(rename_result)) {
EXPECT_THAT(GetContents(f2.path()), IsPosixErrorOkAndHolds(c1));
EXPECT_THAT(GetContents(f1.path()), IsPosixErrorOkAndHolds(c2));
} else {
EXPECT_THAT(GetContents(f1.path()), IsPosixErrorOkAndHolds(c1));
EXPECT_THAT(GetContents(f2.path()), IsPosixErrorOkAndHolds(c2));
}
}
} // namespace
} // namespace testing
+4
View File
@@ -59,6 +59,10 @@ constexpr int kOLargeFile = 00400000;
#define RENAME_NOREPLACE (1 << 0)
#endif // RENAME_NOREPLACE
#ifndef RENAME_EXCHANGE
#define RENAME_EXCHANGE (1 << 1)
#endif // RENAME_EXCHANGE
int renameat2(int olddirfd, const char* oldpath, int newdirfd,
const char* newpath, unsigned int flags);
#endif // __Fuchsia__