Skip to content

Commit 6cf32f7

Browse files
ayushr2gvisor-bot
authored andcommitted
Support RENAME_EXCHANGE in tmpfs and overlayfs
renameat2(2) support was added in 2c1c4a5 ("Implement renameat2."), but only for the gofer filesystem; tmpfs and overlayfs still return EINVAL for RENAME_EXCHANGE. Implement RENAME_EXCHANGE in tmpfs by swapping the two dentries between their parent directories. When the exchanged files differ in type, the parents' link counts (for "..") and the exchanged directory's reference on its parent are transferred. Implement RENAME_EXCHANGE in overlayfs by copying up the destination file (and its descendants, if it is a directory) in addition to the source, and then exchanging both files on the upper layer. Unlike a regular rename, no whiteout is created at the source location, since both locations remain occupied. Exchanged directories are marked opaque at their new locations to prevent merging with lower layer directories there. RENAME_EXCHANGE permits the two files to differ in type and does not require a directory being exchanged to be empty, but exchanging a file with an ancestor directory must fail with EINVAL; see Linux's fs/namei.c:__start_renaming(). The gofer filesystem previously required both files to have the same type; lift that restriction, and update parent link counts when files of different types are exchanged across directories. The RENAME_EXCHANGE syscall tests no longer accept EINVAL (except on FUSE, which does not support the flag), and new tests cover mixed-type exchanges and exchanges with ancestor directories. Fixes #7895 FUTURE_COPYBARA_INTEGRATE_REVIEW=#14221 from ayushr2:rename 26ca2a3 PiperOrigin-RevId: 970791991
1 parent 917062d commit 6cf32f7

4 files changed

Lines changed: 346 additions & 116 deletions

File tree

pkg/sentry/fsimpl/gofer/filesystem.go

Lines changed: 97 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,6 +1440,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
14401440
// users.
14411441
return linuxerr.EINVAL
14421442
}
1443+
exchange := opts.Flags&linux.RENAME_EXCHANGE != 0
14431444

14441445
newName := rp.Component()
14451446
if newName == "." || newName == ".." {
@@ -1500,7 +1501,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
15001501
}
15011502
}
15021503
} else {
1503-
if opts.MustBeDir || rp.MustBeDir() {
1504+
if !exchange && (opts.MustBeDir || rp.MustBeDir()) {
15041505
return linuxerr.ENOTDIR
15051506
}
15061507
}
@@ -1528,7 +1529,26 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
15281529
return err
15291530
}
15301531
replacedVFSD = &replaced.vfsd
1531-
if replaced.isDir() {
1532+
if exchange {
1533+
// The exchanged files may differ in type, and a directory being
1534+
// exchanged may be non-empty; but exchanging a file with an
1535+
// ancestor directory would disconnect the latter from the tree.
1536+
if genericIsAncestorDentry(fs, replaced, renamed) {
1537+
return linuxerr.EINVAL
1538+
}
1539+
if rp.MustBeDir() && !replaced.isDir() {
1540+
return linuxerr.ENOTDIR
1541+
}
1542+
if opts.MustBeDir && !renamed.isDir() {
1543+
return linuxerr.ENOTDIR
1544+
}
1545+
if oldParent != newParent && replaced.isDir() {
1546+
// Writability is needed to change replaced's "..".
1547+
if err := replaced.checkPermissions(creds, vfs.MayWrite); err != nil {
1548+
return err
1549+
}
1550+
}
1551+
} else if replaced.isDir() {
15321552
if !renamed.isDir() {
15331553
return linuxerr.EISDIR
15341554
}
@@ -1541,7 +1561,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
15411561
}
15421562
}
15431563
} else { // replaced == nil
1544-
if opts.Flags&linux.RENAME_EXCHANGE != 0 {
1564+
if exchange {
15451565
// RENAME_EXCHANGE requires that the target file exist.
15461566
return linuxerr.ENOENT
15471567
}
@@ -1562,7 +1582,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
15621582
vfsObj.AbortRenameDentry(&renamed.vfsd, replacedVFSD)
15631583
return err
15641584
}
1565-
} else if replaced != nil && !replaced.inode.isSynthetic() && opts.Flags&linux.RENAME_EXCHANGE == 0 {
1585+
} else if replaced != nil && !replaced.inode.isSynthetic() && !exchange {
15661586
// We are replacing an existing real file with a synthetic one, so we
15671587
// need to unlink the former.
15681588
flags := uint32(0)
@@ -1583,11 +1603,8 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
15831603
defer oldParent.childrenMu.Unlock()
15841604
}
15851605

1586-
if opts.Flags&linux.RENAME_EXCHANGE != 0 {
1587-
if renamed != nil {
1588-
vfsObj.CommitRenameExchangeDentry(&renamed.vfsd, replacedVFSD)
1589-
}
1590-
1606+
if exchange {
1607+
vfsObj.CommitRenameExchangeDentry(&renamed.vfsd, replacedVFSD)
15911608
if oldParent != newParent {
15921609
switch {
15931610
case replaced.inode.isSynthetic() && !renamed.inode.isSynthetic():
@@ -1616,66 +1633,85 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
16161633
oldParent.clearDirentsLocked()
16171634
oldParent.touchCMtime()
16181635
}
1619-
if oldParent != newParent && newParent.inode.cachedMetadataAuthoritative() {
1620-
newParent.clearDirentsLocked()
1621-
newParent.touchCMtime()
1636+
if oldParent != newParent {
1637+
if newParent.inode.cachedMetadataAuthoritative() {
1638+
newParent.clearDirentsLocked()
1639+
newParent.touchCMtime()
1640+
}
1641+
// If exactly one of the exchanged files is a directory, its ".."
1642+
// entry moves from one parent directory to the other.
1643+
if renamed.isDir() && !replaced.isDir() {
1644+
if oldParent.inode.cachedMetadataAuthoritative() {
1645+
oldParent.decLinks()
1646+
}
1647+
if newParent.inode.cachedMetadataAuthoritative() {
1648+
newParent.incLinks()
1649+
}
1650+
} else if !renamed.isDir() && replaced.isDir() {
1651+
if newParent.inode.cachedMetadataAuthoritative() {
1652+
newParent.decLinks()
1653+
}
1654+
if oldParent.inode.cachedMetadataAuthoritative() {
1655+
oldParent.incLinks()
1656+
}
1657+
}
16221658
}
16231659
// Sends notifications for both the renamed and replaced dentries.
16241660
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
16251661
vfs.InotifyRename(ctx, &replaced.inode.watches, &newParent.inode.watches, &oldParent.inode.watches, newName, oldName, replaced.isDir())
1626-
} else {
1627-
toDecRef = vfsObj.CommitRenameReplaceDentry(ctx, &renamed.vfsd, replacedVFSD)
1628-
if replaced != nil {
1629-
replaced.setDeleted()
1630-
// If an extra reference is held on replaced as described by the
1631-
// comment for dentry.refs, drop that reference now. We can't race with
1632-
// fs.unlinkAt() or invalidation since fs.renameMu has been locked for
1633-
// writing since before we obtained replaced.
1634-
if replaced.inode.isSynthetic() {
1635-
newParent.syntheticChildren--
1636-
replaced.decRefNoCaching()
1637-
} else if replaced.inode.endpoint != nil {
1638-
replaced.decRefNoCaching()
1639-
}
1640-
ds = appendDentry(ds, replaced)
1641-
// Remove the replaced entry from its parent's cache.
1642-
delete(newParent.children, newName)
1643-
}
1644-
oldParent.cacheNegativeLookupLocked(oldName) // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
1645-
if renamed.inode.isSynthetic() {
1646-
oldParent.syntheticChildren--
1647-
newParent.syntheticChildren++
1648-
}
1649-
// We have d.opMu for writing, so no need to check for existence of a
1650-
// child with the given name. We could not have raced.
1651-
newParent.cacheNewChildLocked(renamed, newName)
1652-
oldParent.decRefNoCaching()
1653-
if oldParent != newParent {
1654-
ds = appendDentry(ds, newParent)
1655-
ds = appendDentry(ds, oldParent)
1656-
}
1662+
return nil
1663+
}
1664+
toDecRef = vfsObj.CommitRenameReplaceDentry(ctx, &renamed.vfsd, replacedVFSD)
1665+
if replaced != nil {
1666+
replaced.setDeleted()
1667+
// If an extra reference is held on replaced as described by the
1668+
// comment for dentry.refs, drop that reference now. We can't race with
1669+
// fs.unlinkAt() or invalidation since fs.renameMu has been locked for
1670+
// writing since before we obtained replaced.
1671+
if replaced.inode.isSynthetic() {
1672+
newParent.syntheticChildren--
1673+
replaced.decRefNoCaching()
1674+
} else if replaced.inode.endpoint != nil {
1675+
replaced.decRefNoCaching()
1676+
}
1677+
ds = appendDentry(ds, replaced)
1678+
// Remove the replaced entry from its parent's cache.
1679+
delete(newParent.children, newName)
1680+
}
1681+
oldParent.cacheNegativeLookupLocked(oldName) // +checklocksforce: oldParent.childrenMu is held if oldParent != newParent.
1682+
if renamed.inode.isSynthetic() {
1683+
oldParent.syntheticChildren--
1684+
newParent.syntheticChildren++
1685+
}
1686+
// We have d.opMu for writing, so no need to check for existence of a
1687+
// child with the given name. We could not have raced.
1688+
newParent.cacheNewChildLocked(renamed, newName)
1689+
oldParent.decRefNoCaching()
1690+
if oldParent != newParent {
1691+
ds = appendDentry(ds, newParent)
1692+
ds = appendDentry(ds, oldParent)
1693+
}
16571694

1658-
// Update metadata.
1659-
if renamed.inode.cachedMetadataAuthoritative() {
1660-
renamed.touchCtime()
1661-
}
1662-
if oldParent.inode.cachedMetadataAuthoritative() {
1663-
oldParent.clearDirentsLocked()
1664-
oldParent.touchCMtime()
1665-
if renamed.isDir() {
1666-
oldParent.decLinks()
1667-
}
1695+
// Update metadata.
1696+
if renamed.inode.cachedMetadataAuthoritative() {
1697+
renamed.touchCtime()
1698+
}
1699+
if oldParent.inode.cachedMetadataAuthoritative() {
1700+
oldParent.clearDirentsLocked()
1701+
oldParent.touchCMtime()
1702+
if renamed.isDir() {
1703+
oldParent.decLinks()
16681704
}
1669-
if newParent.inode.cachedMetadataAuthoritative() {
1670-
newParent.clearDirentsLocked()
1671-
newParent.touchCMtime()
1672-
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
1673-
// Increase the link count if we did not replace another directory.
1674-
newParent.incLinks()
1675-
}
1705+
}
1706+
if newParent.inode.cachedMetadataAuthoritative() {
1707+
newParent.clearDirentsLocked()
1708+
newParent.touchCMtime()
1709+
if renamed.isDir() && (replaced == nil || !replaced.isDir()) {
1710+
// Increase the link count if we did not replace another directory.
1711+
newParent.incLinks()
16761712
}
1677-
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
16781713
}
1714+
vfs.InotifyRename(ctx, &renamed.inode.watches, &oldParent.inode.watches, &newParent.inode.watches, oldName, newName, renamed.isDir())
16791715
return nil
16801716
}
16811717

pkg/sentry/fsimpl/overlay/filesystem.go

Lines changed: 86 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,9 +1090,13 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
10901090
return err
10911091
}
10921092

1093-
if opts.Flags&^linux.RENAME_NOREPLACE != 0 {
1093+
if opts.Flags&^(linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE) != 0 {
10941094
return linuxerr.EINVAL
10951095
}
1096+
if opts.Flags&(linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE) == linux.RENAME_NOREPLACE|linux.RENAME_EXCHANGE {
1097+
return linuxerr.EINVAL
1098+
}
1099+
exchange := opts.Flags&linux.RENAME_EXCHANGE != 0
10961100

10971101
newName := rp.Component()
10981102
if newName == "." || newName == ".." {
@@ -1142,7 +1146,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
11421146
}
11431147
}
11441148
} else {
1145-
if opts.MustBeDir || rp.MustBeDir() {
1149+
if !exchange && (opts.MustBeDir || rp.MustBeDir()) {
11461150
return linuxerr.ENOTDIR
11471151
}
11481152
}
@@ -1175,7 +1179,26 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
11751179
return err
11761180
}
11771181
replacedVFSD = &replaced.vfsd
1178-
if replaced.isDir() {
1182+
if exchange {
1183+
// The exchanged files may differ in type, and a directory being
1184+
// exchanged may be non-empty; but exchanging a file with an
1185+
// ancestor directory would disconnect the latter from the tree.
1186+
if genericIsAncestorDentry(fs, replaced, renamed) {
1187+
return linuxerr.EINVAL
1188+
}
1189+
if rp.MustBeDir() && !replaced.isDir() {
1190+
return linuxerr.ENOTDIR
1191+
}
1192+
if opts.MustBeDir && !renamed.isDir() {
1193+
return linuxerr.ENOTDIR
1194+
}
1195+
if oldParent != newParent && replaced.isDir() {
1196+
// Writability is needed to change replaced's "..".
1197+
if err := replaced.checkPermissions(creds, vfs.MayWrite); err != nil {
1198+
return err
1199+
}
1200+
}
1201+
} else if replaced.isDir() {
11791202
if !renamed.isDir() {
11801203
return linuxerr.EISDIR
11811204
}
@@ -1193,6 +1216,9 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
11931216
return linuxerr.ENOTDIR
11941217
}
11951218
}
1219+
} else if exchange {
1220+
// RENAME_EXCHANGE requires that the target file exist.
1221+
return linuxerr.ENOENT
11961222
}
11971223

11981224
if oldParent == newParent && oldName == newName {
@@ -1216,10 +1242,24 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
12161242
if err := newParent.copyUpLocked(ctx); err != nil {
12171243
return err
12181244
}
1219-
// If replaced exists, it doesn't need to be copied-up, but we do need to
1220-
// serialize with copy-up. Holding renameMu for writing should be
1221-
// sufficient, but out of an abundance of caution...
1222-
if replaced != nil {
1245+
if exchange {
1246+
// replaced is also renamed on the upper layer, so it (and all of its
1247+
// descendants if it's a directory) must be copied-up too.
1248+
if err := replaced.copyUpLocked(ctx); err != nil {
1249+
return err
1250+
}
1251+
if replaced.isDir() {
1252+
replaced.dirMu.NestedLock(dirLockReplaced)
1253+
err := replaced.copyUpDescendantsLocked(ctx, &ds)
1254+
replaced.dirMu.NestedUnlock(dirLockReplaced)
1255+
if err != nil {
1256+
return err
1257+
}
1258+
}
1259+
} else if replaced != nil {
1260+
// replaced doesn't need to be copied-up, but we do need to serialize
1261+
// with copy-up. Holding renameMu for writing should be sufficient, but
1262+
// out of an abundance of caution...
12231263
replaced.copyMu.RLock()
12241264
defer replaced.copyMu.RUnlock()
12251265
}
@@ -1255,7 +1295,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
12551295
}
12561296
}
12571297
}
1258-
if renamed.isDir() {
1298+
if !exchange && renamed.isDir() {
12591299
if replacedLayer == lookupLayerUpper {
12601300
// Remove whiteouts from the directory being replaced.
12611301
needRecreateWhiteouts = true
@@ -1303,6 +1343,44 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
13031343
return err
13041344
}
13051345

1346+
if exchange {
1347+
// Below this point, renamed is at newpop and replaced is at oldpop.
1348+
// Commit the exchange, update the overlay filesystem tree, and abandon
1349+
// attempts to recover from errors.
1350+
vfsObj.CommitRenameExchangeDentry(&renamed.vfsd, &replaced.vfsd)
1351+
genericSetParentAndName(fs, renamed, newParent, newName)
1352+
genericSetParentAndName(fs, replaced, oldParent, oldName)
1353+
// References held by renamed and replaced on their parents are
1354+
// exchanged as well; the counts on each parent are unchanged.
1355+
oldParent.children[oldName] = replaced
1356+
newParent.children[newName] = renamed
1357+
oldParent.dirents = nil
1358+
newParent.dirents = nil
1359+
1360+
// An exchanged directory's contents can no longer be merged with
1361+
// lower layer directories at its new location.
1362+
if renamed.isDir() {
1363+
if err := vfsObj.SetXattrAt(ctx, fs.creds, &newpop, &vfs.SetXattrOptions{
1364+
Name: fs.xattrOpaque,
1365+
Value: "y",
1366+
}); err != nil {
1367+
panic(fmt.Sprintf("unrecoverable overlayfs inconsistency: failed to make exchanged directory opaque: %v", err))
1368+
}
1369+
}
1370+
if replaced.isDir() {
1371+
if err := vfsObj.SetXattrAt(ctx, fs.creds, &oldpop, &vfs.SetXattrOptions{
1372+
Name: fs.xattrOpaque,
1373+
Value: "y",
1374+
}); err != nil {
1375+
panic(fmt.Sprintf("unrecoverable overlayfs inconsistency: failed to make exchanged directory opaque: %v", err))
1376+
}
1377+
}
1378+
1379+
vfs.InotifyRename(ctx, &renamed.watches, &oldParent.watches, &newParent.watches, oldName, newName, renamed.isDir())
1380+
vfs.InotifyRename(ctx, &replaced.watches, &newParent.watches, &oldParent.watches, newName, oldName, replaced.isDir())
1381+
return nil
1382+
}
1383+
13061384
// Below this point, the renamed dentry is now at newpop, and anything we
13071385
// replaced is gone forever. Commit the rename, update the overlay
13081386
// filesystem tree, and abandon attempts to recover from errors.

0 commit comments

Comments
 (0)