ceph: allow rename operation under different quota realms
authorLuis Henriques <lhenriques@suse.com>
Tue, 7 Apr 2020 10:30:20 +0000 (11:30 +0100)
committerIlya Dryomov <idryomov@gmail.com>
Mon, 1 Jun 2020 11:22:53 +0000 (13:22 +0200)
Returning -EXDEV when trying to 'mv' files/directories from different
quota realms results in copy+unlink operations instead of the faster
CEPH_MDS_OP_RENAME.  This will occur even when there aren't any quotas
set in the destination directory, or if there's enough space left for
the new file(s).

This patch adds a new helper function to be called on rename operations
which will allow these operations if they can be executed.  This patch
mimics userland fuse client commit b8954e5734b3 ("client:
optimize rename operation under different quota root").

Since ceph_quota_is_same_realm() is now called only from this new
helper, make it static.

URL: https://tracker.ceph.com/issues/44791
Signed-off-by: Luis Henriques <lhenriques@suse.com>
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
fs/ceph/dir.c
fs/ceph/quota.c
fs/ceph/super.h

index 93476d4..39f5311 100644 (file)
@@ -1209,11 +1209,12 @@ static int ceph_rename(struct inode *old_dir, struct dentry *old_dentry,
                        op = CEPH_MDS_OP_RENAMESNAP;
                else
                        return -EROFS;
+       } else if (old_dir != new_dir) {
+               err = ceph_quota_check_rename(mdsc, d_inode(old_dentry),
+                                             new_dir);
+               if (err)
+                       return err;
        }
-       /* don't allow cross-quota renames */
-       if ((old_dir != new_dir) &&
-           (!ceph_quota_is_same_realm(old_dir, new_dir)))
-               return -EXDEV;
 
        dout("rename dir %p dentry %p to dir %p dentry %p\n",
             old_dir, old_dentry, new_dir, new_dentry);
index 7377838..198ddde 100644 (file)
@@ -264,7 +264,7 @@ restart:
        return NULL;
 }
 
-bool ceph_quota_is_same_realm(struct inode *old, struct inode *new)
+static bool ceph_quota_is_same_realm(struct inode *old, struct inode *new)
 {
        struct ceph_mds_client *mdsc = ceph_inode_to_client(old)->mdsc;
        struct ceph_snap_realm *old_realm, *new_realm;
@@ -516,3 +516,59 @@ bool ceph_quota_update_statfs(struct ceph_fs_client *fsc, struct kstatfs *buf)
        return is_updated;
 }
 
+/*
+ * ceph_quota_check_rename - check if a rename can be executed
+ * @mdsc:      MDS client instance
+ * @old:       inode to be copied
+ * @new:       destination inode (directory)
+ *
+ * This function verifies if a rename (e.g. moving a file or directory) can be
+ * executed.  It forces an rstat update in the @new target directory (and in the
+ * source @old as well, if it's a directory).  The actual check is done both for
+ * max_files and max_bytes.
+ *
+ * This function returns 0 if it's OK to do the rename, or, if quotas are
+ * exceeded, -EXDEV (if @old is a directory) or -EDQUOT.
+ */
+int ceph_quota_check_rename(struct ceph_mds_client *mdsc,
+                           struct inode *old, struct inode *new)
+{
+       struct ceph_inode_info *ci_old = ceph_inode(old);
+       int ret = 0;
+
+       if (ceph_quota_is_same_realm(old, new))
+               return 0;
+
+       /*
+        * Get the latest rstat for target directory (and for source, if a
+        * directory)
+        */
+       ret = ceph_do_getattr(new, CEPH_STAT_RSTAT, false);
+       if (ret)
+               return ret;
+
+       if (S_ISDIR(old->i_mode)) {
+               ret = ceph_do_getattr(old, CEPH_STAT_RSTAT, false);
+               if (ret)
+                       return ret;
+               ret = check_quota_exceeded(new, QUOTA_CHECK_MAX_BYTES_OP,
+                                          ci_old->i_rbytes);
+               if (!ret)
+                       ret = check_quota_exceeded(new,
+                                                  QUOTA_CHECK_MAX_FILES_OP,
+                                                  ci_old->i_rfiles +
+                                                  ci_old->i_rsubdirs);
+               if (ret)
+                       ret = -EXDEV;
+       } else {
+               ret = check_quota_exceeded(new, QUOTA_CHECK_MAX_BYTES_OP,
+                                          i_size_read(old));
+               if (!ret)
+                       ret = check_quota_exceeded(new,
+                                                  QUOTA_CHECK_MAX_FILES_OP, 1);
+               if (ret)
+                       ret = -EDQUOT;
+       }
+
+       return ret;
+}
index b82f82d..226f19c 100644 (file)
@@ -1210,13 +1210,14 @@ extern void ceph_handle_quota(struct ceph_mds_client *mdsc,
                              struct ceph_mds_session *session,
                              struct ceph_msg *msg);
 extern bool ceph_quota_is_max_files_exceeded(struct inode *inode);
-extern bool ceph_quota_is_same_realm(struct inode *old, struct inode *new);
 extern bool ceph_quota_is_max_bytes_exceeded(struct inode *inode,
                                             loff_t newlen);
 extern bool ceph_quota_is_max_bytes_approaching(struct inode *inode,
                                                loff_t newlen);
 extern bool ceph_quota_update_statfs(struct ceph_fs_client *fsc,
                                     struct kstatfs *buf);
+extern int ceph_quota_check_rename(struct ceph_mds_client *mdsc,
+                                  struct inode *old, struct inode *new);
 extern void ceph_cleanup_quotarealms_inodes(struct ceph_mds_client *mdsc);
 
 #endif /* _FS_CEPH_SUPER_H */