Preserve directory inode number after copy up in overlayfs.

Earlier we were using the topmost inode and device number to
compute the directory inode number. But the topmost layer changes
after a copy up which causes a new inode number to be generated
after a copy up.

This confuses some applications like GNU's FTS library. See
fts_safe_changedir() for example which errors out with ENOENT if
it observes that the dirctory inode or device number has changed.

Now we use the bottommost layer's inode and device numbers. Linux
and VFS1 also preserve the directory's inode number after copy up.

PiperOrigin-RevId: 456147019
This commit is contained in:
Ayush Ranjan
2022-06-20 18:36:14 -07:00
committed by gVisor bot
parent c28cb58302
commit c73e953644
2 changed files with 14 additions and 4 deletions
+11 -3
View File
@@ -295,6 +295,13 @@ func (fs *filesystem) lookupLocked(ctx context.Context, parent *dentry, name str
return false
}
// Directories use the lowest layer inode and device numbers to generate a
// filesystem local inode number. This way the inode number does not change
// after copy ups.
child.devMajor = atomicbitops.FromUint32(stat.DevMajor)
child.devMinor = atomicbitops.FromUint32(stat.DevMinor)
child.ino = atomicbitops.FromUint64(stat.Ino)
// Directories are merged with directories from lower layers if they
// are not explicitly opaque.
opaqueVal, err := vfsObj.GetXattrAt(ctx, fs.creds, &vfs.PathOperation{
@@ -316,9 +323,10 @@ func (fs *filesystem) lookupLocked(ctx context.Context, parent *dentry, name str
return nil, topLookupLayer, linuxerr.ENOENT
}
// Device and inode numbers were copied from the topmost layer above;
// override them if necessary. We can use RacyLoad() because child is still
// being initialized.
// Device and inode numbers were copied from the topmost layer above for
// non-directories. They were copied from the bottommost layer for
// directories. Override them if necessary. We can use RacyLoad() because
// child is still being initialized.
if child.isDir() {
child.ino.Store(fs.newDirIno(child.devMajor.RacyLoad(), child.devMinor.RacyLoad(), child.ino.RacyLoad()))
child.devMajor = atomicbitops.FromUint32(linux.UNNAMED_MAJOR)
+3 -1
View File
@@ -117,7 +117,7 @@ type filesystem struct {
renameMu renameRWMutex `state:"nosave"`
// dirInoCache caches overlay-private directory inode numbers by mapped
// topmost device numbers and inode number. dirInoCache is protected by
// bottommost device numbers and inode number. dirInoCache is protected by
// dirInoCacheMu.
dirInoCacheMu dirInoCacheMutex `state:"nosave"`
dirInoCache map[layerDevNoAndIno]uint64
@@ -310,6 +310,8 @@ func (fstype FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt
if rootStat.Mode&linux.S_IFMT == linux.S_IFDIR {
root.devMajor = atomicbitops.FromUint32(linux.UNNAMED_MAJOR)
root.devMinor = atomicbitops.FromUint32(fs.dirDevMinor)
// For root dir, it is okay to use top most level's stat to compute inode
// number because we don't allow copy ups on root dentries.
root.ino.Store(fs.newDirIno(rootStat.DevMajor, rootStat.DevMinor, rootStat.Ino))
} else if !root.upperVD.Ok() {
root.devMajor = atomicbitops.FromUint32(linux.UNNAMED_MAJOR)