commit
97c85a828f36bbfffe9d77b977b65a5872b6cad4 upstream.
Current snaphost code does not properly handle moving inode from one
empty snap realm to another empty snap realm. After changing inode's
snap realm, some dirty pages' snap context can be not equal to inode's
i_head_snap. This can trigger BUG() in ceph_put_wrbuffer_cap_refs()
The fix is introduce a global empty snap context for all empty snap
realm. This avoids triggering the BUG() for filesystem with no snapshot.
Fixes: http://tracker.ceph.com/issues/9928
Signed-off-by: Yan, Zheng <zyan@redhat.com>
Reviewed-by: Ilya Dryomov <idryomov@redhat.com>
[bwh: Backported to 3.2:
- Adjust context
- As we don't have ceph_create_snap_context(), open-code it in
ceph_snap_init()]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
+
+static struct ceph_snap_context *empty_snapc;
+
/*
* build the snap context for a given realm.
*/
/*
* build the snap context for a given realm.
*/
+ if (num == 0 && realm->seq == empty_snapc->seq) {
+ ceph_get_snap_context(empty_snapc);
+ snapc = empty_snapc;
+ goto done;
+ }
+
/* alloc new snap context */
err = -ENOMEM;
if (num > (SIZE_MAX - sizeof(*snapc)) / sizeof(u64))
/* alloc new snap context */
err = -ENOMEM;
if (num > (SIZE_MAX - sizeof(*snapc)) / sizeof(u64))
dout("build_snap_context %llx %p: %p seq %lld (%d snaps)\n",
realm->ino, realm, snapc, snapc->seq, snapc->num_snaps);
dout("build_snap_context %llx %p: %p seq %lld (%d snaps)\n",
realm->ino, realm, snapc, snapc->seq, snapc->num_snaps);
if (realm->cached_context)
ceph_put_snap_context(realm->cached_context);
realm->cached_context = snapc;
if (realm->cached_context)
ceph_put_snap_context(realm->cached_context);
realm->cached_context = snapc;
cap_snap. lucky us. */
dout("queue_cap_snap %p already pending\n", inode);
kfree(capsnap);
cap_snap. lucky us. */
dout("queue_cap_snap %p already pending\n", inode);
kfree(capsnap);
+ } else if (ci->i_snap_realm->cached_context == empty_snapc) {
+ dout("queue_cap_snap %p empty snapc\n", inode);
+ kfree(capsnap);
} else if (dirty & (CEPH_CAP_AUTH_EXCL|CEPH_CAP_XATTR_EXCL|
CEPH_CAP_FILE_EXCL|CEPH_CAP_FILE_WR)) {
struct ceph_snap_context *snapc = ci->i_head_snapc;
} else if (dirty & (CEPH_CAP_AUTH_EXCL|CEPH_CAP_XATTR_EXCL|
CEPH_CAP_FILE_EXCL|CEPH_CAP_FILE_WR)) {
struct ceph_snap_context *snapc = ci->i_head_snapc;
+int __init ceph_snap_init(void)
+{
+ empty_snapc = kzalloc(sizeof(struct ceph_snap_context), GFP_NOFS);
+ if (!empty_snapc)
+ return -ENOMEM;
+ atomic_set(&empty_snapc->nref, 1);
+ empty_snapc->seq = 1;
+ return 0;
+}
+void ceph_snap_exit(void)
+{
+ ceph_put_snap_context(empty_snapc);
+}
- ret = register_filesystem(&ceph_fs_type);
+ ret = ceph_snap_init();
if (ret)
goto out_icache;
if (ret)
goto out_icache;
+ ret = register_filesystem(&ceph_fs_type);
+ if (ret)
+ goto out_snap;
+
pr_info("loaded (mds proto %d)\n", CEPH_MDSC_PROTOCOL);
return 0;
pr_info("loaded (mds proto %d)\n", CEPH_MDSC_PROTOCOL);
return 0;
+out_snap:
+ ceph_snap_exit();
out_icache:
destroy_caches();
out:
out_icache:
destroy_caches();
out:
{
dout("exit_ceph\n");
unregister_filesystem(&ceph_fs_type);
{
dout("exit_ceph\n");
unregister_filesystem(&ceph_fs_type);
extern int __ceph_finish_cap_snap(struct ceph_inode_info *ci,
struct ceph_cap_snap *capsnap);
extern void ceph_cleanup_empty_realms(struct ceph_mds_client *mdsc);
extern int __ceph_finish_cap_snap(struct ceph_inode_info *ci,
struct ceph_cap_snap *capsnap);
extern void ceph_cleanup_empty_realms(struct ceph_mds_client *mdsc);
+extern int ceph_snap_init(void);
+extern void ceph_snap_exit(void);
/*
* a cap_snap is "pending" if it is still awaiting an in-progress
/*
* a cap_snap is "pending" if it is still awaiting an in-progress