Merge tag 'ceph-for-5.13-rc1' of git://github.com/ceph/ceph-client
[linux-block.git] / fs / ceph / inode.c
index 8c6811e3682d543ad8043aa9f68572e592c1e918..e1c63adb196ddcb01b39b543e075f2256945e9b1 100644 (file)
@@ -81,9 +81,21 @@ struct inode *ceph_get_snapdir(struct inode *parent)
        struct inode *inode = ceph_get_inode(parent->i_sb, vino);
        struct ceph_inode_info *ci = ceph_inode(inode);
 
-       BUG_ON(!S_ISDIR(parent->i_mode));
        if (IS_ERR(inode))
                return inode;
+
+       if (!S_ISDIR(parent->i_mode)) {
+               pr_warn_once("bad snapdir parent type (mode=0%o)\n",
+                            parent->i_mode);
+               return ERR_PTR(-ENOTDIR);
+       }
+
+       if (!(inode->i_state & I_NEW) && !S_ISDIR(inode->i_mode)) {
+               pr_warn_once("bad snapdir inode type (mode=0%o)\n",
+                            inode->i_mode);
+               return ERR_PTR(-ENOTDIR);
+       }
+
        inode->i_mode = parent->i_mode;
        inode->i_uid = parent->i_uid;
        inode->i_gid = parent->i_gid;
@@ -762,11 +774,32 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,
        bool queue_trunc = false;
        bool new_version = false;
        bool fill_inline = false;
+       umode_t mode = le32_to_cpu(info->mode);
+       dev_t rdev = le32_to_cpu(info->rdev);
 
        dout("%s %p ino %llx.%llx v %llu had %llu\n", __func__,
             inode, ceph_vinop(inode), le64_to_cpu(info->version),
             ci->i_version);
 
+       /* Once I_NEW is cleared, we can't change type or dev numbers */
+       if (inode->i_state & I_NEW) {
+               inode->i_mode = mode;
+       } else {
+               if (inode_wrong_type(inode, mode)) {
+                       pr_warn_once("inode type changed! (ino %llx.%llx is 0%o, mds says 0%o)\n",
+                                    ceph_vinop(inode), inode->i_mode, mode);
+                       return -ESTALE;
+               }
+
+               if ((S_ISCHR(mode) || S_ISBLK(mode)) && inode->i_rdev != rdev) {
+                       pr_warn_once("dev inode rdev changed! (ino %llx.%llx is %u:%u, mds says %u:%u)\n",
+                                    ceph_vinop(inode), MAJOR(inode->i_rdev),
+                                    MINOR(inode->i_rdev), MAJOR(rdev),
+                                    MINOR(rdev));
+                       return -ESTALE;
+               }
+       }
+
        info_caps = le32_to_cpu(info->cap.caps);
 
        /* prealloc new cap struct */
@@ -820,8 +853,6 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,
        issued |= __ceph_caps_dirty(ci);
        new_issued = ~issued & info_caps;
 
-       /* update inode */
-       inode->i_rdev = le32_to_cpu(info->rdev);
        /* directories have fl_stripe_unit set to zero */
        if (le32_to_cpu(info->layout.fl_stripe_unit))
                inode->i_blkbits =
@@ -833,7 +864,7 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,
 
        if ((new_version || (new_issued & CEPH_CAP_AUTH_SHARED)) &&
            (issued & CEPH_CAP_AUTH_EXCL) == 0) {
-               inode->i_mode = le32_to_cpu(info->mode);
+               inode->i_mode = mode;
                inode->i_uid = make_kuid(&init_user_ns, le32_to_cpu(info->uid));
                inode->i_gid = make_kgid(&init_user_ns, le32_to_cpu(info->gid));
                dout("%p mode 0%o uid.gid %d.%d\n", inode, inode->i_mode,
@@ -932,7 +963,7 @@ int ceph_fill_inode(struct inode *inode, struct page *locked_page,
        case S_IFCHR:
        case S_IFSOCK:
                inode->i_blkbits = PAGE_SHIFT;
-               init_special_inode(inode, inode->i_mode, inode->i_rdev);
+               init_special_inode(inode, inode->i_mode, rdev);
                inode->i_op = &ceph_file_iops;
                break;
        case S_IFREG: