ILD

fuse filesystem open fail with stale file handle
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2026-3-4 站点:Inside Linux Development

在用fuse实现的一个聚合文件系统测试中,概率性出现stat或者open文件的时候,返回ESTALE错误。

该文件系统实现共享,多个用户可以访问同一个目录,并且带有不同的权限。


复现条件是:

两个用户同时访问共享文件夹,并且执行mv操作


挂载聚合文件系统:

# modprobe fuse

# mkdir /mnt/merge

# cfs /mnt/merge /mnt/vda/pa0 /mnt/vda/pb0


复现脚本:

/mnt/fs_stress /mnt/merge/user0/myshare/user1/filemgr/test >/dev/null &

/mnt/fs_stress /mnt/merge/user0/myshare/user2/filemgr/test >/dev/null &

mv /mnt/merge/user0/data/abd/* /mnt/merge/user0/data/abc


fs_stress就是遍历文件夹,访问属性,读取文件内容,在后台跑两个。


定位过程

给vfs加日志,一步步定位到是:


fs/stat.c

vfs_fstatat() -> -ESTALE

    vfs_statx() -> -ESTALE


fs/namei.c

filename_lookup() -> -ESTALE

path_lookupat() -> -ESTALE

link_path_walk() -> ESTALE

walk_component() -> ESTALE

lookup_slow()    -> ESTALE

inode->i_op->lookup() -> STALE


fs/fuse/dir.c

上面的lookup函数,是fuse提供的

fuse_lookup()


fs/dcache.c

d_splice_alias()    -> ESTALE

__d_unalias() -> ESTALE


__d_unalias()就是最终的根因了。

/*
 * This helper attempts to cope with remotely renamed directories
 *
 * It assumes that the caller is already holding
 * dentry->d_parent->d_inode->i_mutex, and rename_lock
 *
 * Note: If ever the locking in lock_rename() changes, then please
 * remember to update this too...
 */
static int __d_unalias(struct inode *inode,
                struct dentry *dentry, struct dentry *alias)
{
        struct mutex *m1 = NULL;
        struct rw_semaphore *m2 = NULL;
        int ret = -ESTALE;

        /* If alias and dentry share a parent, then no extra locks required */
        if (alias->d_parent == dentry->d_parent)
                goto out_unalias;

        /* See lock_rename() */
        if (!mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex))
                goto out_err;
        m1 = &dentry->d_sb->s_vfs_rename_mutex;
        if (!inode_trylock_shared(alias->d_parent->d_inode))
                goto out_err;
        m2 = &alias->d_parent->d_inode->i_rwsem;
out_unalias:
        __d_move(alias, dentry, false);
        ret = 0;
out_err:
        if (m2)
                up_read(m2);
        if (m1)
                mutex_unlock(m1);
        return ret;
}


这个函数出错就是返回ESTALE,出错的原因很明朗了,因为只有再执行mv操作的时候,才会复现问题。

所以就是拿取rename锁失败了。

mutex_trylock(&dentry->d_sb->s_vfs_rename_mutex)

这个锁是sb的一个全局锁。


为什么执行__d_unalias()也很明朗了

因为两个用户并发访问同一个共享文件夹,fuse返回的文件夹的inode id相同。


# stat merge//user0/myshare/user2/filemgr/test

Device: 10h/16d Inode: 650241000000051281  Links: 2


# stat merge/user0/myshare/user1/filemgr/test

Device: 10h/16d Inode: 650241000000051281  Links: 2


但是路径不同,导致并发访问的时候,需要和旧的解绑,解绑过程中,由于拿取锁失败,放回了ESTALE。

d_splice_alias()中,如果是文件夹,会尝试和旧的解绑,因为Linux不支持文件夹的硬链,所以文件夹的inode,只能关联到一个dentry。

 if (S_ISDIR(inode->i_mode)) {

                struct dentry *new = __d_find_any_alias(inode);

                if (unlikely(new)) 


解决办法:

共享的文件夹,为每个用户分配不同的inode id。




Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.