diff mbox

[RFC,v2,5/6] ext4: add shortcut for moving files across projects

Message ID 20150310172210.23081.28245.stgit@buzz
State Accepted, archived
Headers show

Commit Message

Konstantin Khlebnikov March 10, 2015, 5:22 p.m. UTC
This patch adds useful optimization for most common case of moving files
across projects: non-directory inode without extra hardlinks (i_nlink == 1)
can be moved into different project without making a copy. We just have to
change project in and reaccount disk usage in one transaction with rename.

As a result simple recursive 'mv' works much faster: it creates new
directories but files are moved without copying.

Flag DQUOT_TRANSFER_NOFAIL tells dquot_transfer_project() to move inode
regardless of quota limits. This is required for moving inode back into
the old project if rename had failed. This error-path little-bit racy
(user could use more that quota allows) but there are not so much errors
which might trigger this path: filesystem corruption or disk failure.
They seem bigger problem than potential quota abuse.

Signed-off-by: Konstantin Khlebnikov <khlebnikov@yandex-team.ru>
---
 fs/ext4/namei.c          |   93 +++++++++++++++++++++++++++++++++++++++++-----
 fs/ext4/super.c          |    2 -
 fs/quota/dquot.c         |   16 +++++---
 include/linux/quotaops.h |   10 ++++-
 4 files changed, 103 insertions(+), 18 deletions(-)


--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index 094f7096a41c..2c738bae7c36 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3029,6 +3029,7 @@  struct ext4_renament {
 	struct inode *inode;
 	bool is_dir;
 	int dir_nlink_delta;
+	bool transfer_project;
 
 	/* entry for "dentry" */
 	struct buffer_head *bh;
@@ -3274,13 +3275,23 @@  static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
 	if (new.inode && !test_opt(new.dir->i_sb, NO_AUTO_DA_ALLOC))
 		ext4_alloc_da_blocks(old.inode);
 
+	credits = (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
+		   EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2);
+
 	if (!ext4_check_project(new.dir, old.inode)) {
-		retval = -EXDEV;
-		goto end_rename;
+		/*
+		 * Shortcut for moving files across projects: inode with one
+		 * hardlink can be tranferred as is without making a copy.
+		 */
+		if (!S_ISDIR(old.inode->i_mode) && old.inode->i_nlink == 1) {
+			credits += 2 * EXT4_QUOTA_TRANS_BLOCKS(old.dir->i_sb);
+			old.transfer_project = true;
+		} else {
+			retval = -EXDEV;
+			goto end_rename;
+		}
 	}
 
-	credits = (2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
-		   EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2);
 	if (!(flags & RENAME_WHITEOUT)) {
 		handle = ext4_journal_start(old.dir, EXT4_HT_DIR, credits);
 		if (IS_ERR(handle)) {
@@ -3297,6 +3308,15 @@  static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
 		}
 	}
 
+	if (old.transfer_project) {
+		retval = dquot_transfer_project(old.inode,
+				EXT4_I(new.dir)->i_project, 0);
+		if (retval) {
+			old.transfer_project = false;
+			goto end_rename;
+		}
+	}
+
 	if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir))
 		ext4_handle_sync(handle);
 
@@ -3355,6 +3375,8 @@  static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
 	 * rename.
 	 */
 	old.inode->i_ctime = ext4_current_time(old.inode);
+	if (old.transfer_project)
+		EXT4_I(old.inode)->i_project = EXT4_I(new.dir)->i_project;
 	ext4_mark_inode_dirty(handle, old.inode);
 
 	if (!whiteout) {
@@ -3395,6 +3417,9 @@  static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry,
 	retval = 0;
 
 end_rename:
+	if (retval && old.transfer_project)
+		dquot_transfer_project(old.inode, EXT4_I(old.inode)->i_project,
+				       DQUOT_TRANSFER_NOFAIL);
 	brelse(old.dir_bh);
 	brelse(old.bh);
 	brelse(new.bh);
@@ -3425,6 +3450,7 @@  static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 	};
 	u8 new_file_type;
 	int retval;
+	int credits;
 
 	dquot_initialize(old.dir);
 	dquot_initialize(new.dir);
@@ -3455,21 +3481,56 @@  static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 	if (!new.bh || le32_to_cpu(new.de->inode) != new.inode->i_ino)
 		goto end_rename;
 
-	if (!ext4_check_project(new.dir, old.inode) ||
-	    !ext4_check_project(old.dir, new.inode)) {
-		retval = -EXDEV;
-		goto end_rename;
+	credits = 2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
+		  2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2;
+
+	if (!ext4_check_project(new.dir, old.inode)) {
+		if (!S_ISDIR(old.inode->i_mode) && old.inode->i_nlink == 1) {
+			credits += 2 * EXT4_QUOTA_TRANS_BLOCKS(old.dir->i_sb);
+			old.transfer_project = true;
+		} else {
+			retval = -EXDEV;
+			goto end_rename;
+		}
+	}
+
+	if (!ext4_check_project(old.dir, new.inode)) {
+		if (!S_ISDIR(new.inode->i_mode) && new.inode->i_nlink == 1) {
+			credits += 2 * EXT4_QUOTA_TRANS_BLOCKS(old.dir->i_sb);
+			new.transfer_project = true;
+		} else {
+			old.transfer_project = false;
+			retval = -EXDEV;
+			goto end_rename;
+		}
 	}
 
-	handle = ext4_journal_start(old.dir, EXT4_HT_DIR,
-		(2 * EXT4_DATA_TRANS_BLOCKS(old.dir->i_sb) +
-		 2 * EXT4_INDEX_EXTRA_TRANS_BLOCKS + 2));
+	handle = ext4_journal_start(old.dir, EXT4_HT_DIR, credits);
 	if (IS_ERR(handle)) {
 		retval = PTR_ERR(handle);
 		handle = NULL;
 		goto end_rename;
 	}
 
+	if (old.transfer_project) {
+		retval = dquot_transfer_project(old.inode,
+				EXT4_I(new.dir)->i_project, 0);
+		if (retval) {
+			old.transfer_project = false;
+			new.transfer_project = false;
+			goto end_rename;
+		}
+	}
+
+	if (new.transfer_project) {
+		retval = dquot_transfer_project(new.inode,
+				EXT4_I(old.dir)->i_project, 0);
+		if (retval) {
+			new.transfer_project = false;
+			goto end_rename;
+		}
+	}
+
 	if (IS_DIRSYNC(old.dir) || IS_DIRSYNC(new.dir))
 		ext4_handle_sync(handle);
 
@@ -3514,6 +3575,10 @@  static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 	 */
 	old.inode->i_ctime = ext4_current_time(old.inode);
 	new.inode->i_ctime = ext4_current_time(new.inode);
+	if (old.transfer_project)
+		EXT4_I(old.inode)->i_project = EXT4_I(new.dir)->i_project;
+	if (new.transfer_project)
+		EXT4_I(new.inode)->i_project = EXT4_I(old.dir)->i_project;
 	ext4_mark_inode_dirty(handle, old.inode);
 	ext4_mark_inode_dirty(handle, new.inode);
 
@@ -3532,6 +3597,12 @@  static int ext4_cross_rename(struct inode *old_dir, struct dentry *old_dentry,
 	retval = 0;
 
 end_rename:
+	if (retval && old.transfer_project)
+		dquot_transfer_project(old.inode, EXT4_I(old.inode)->i_project,
+				       DQUOT_TRANSFER_NOFAIL);
+	if (retval && new.transfer_project)
+		dquot_transfer_project(new.inode, EXT4_I(new.inode)->i_project,
+				       DQUOT_TRANSFER_NOFAIL);
 	brelse(old.dir_bh);
 	brelse(new.dir_bh);
 	brelse(old.bh);
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index c62ed5b554ae..6a6506bce53c 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -1062,7 +1062,7 @@  static int ext4_set_project(struct inode *inode, kprojid_t project)
 	if (IS_ERR(handle))
 		return PTR_ERR(handle);
 
-	ret = dquot_transfer_project(inode, project);
+	ret = dquot_transfer_project(inode, project, 0);
 	if (ret)
 		goto out;
 
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 04c27cdeca05..0b61357554ed 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -1838,7 +1838,8 @@  EXPORT_SYMBOL(dquot_free_inode);
  * We are holding reference on transfer_from & transfer_to, no need to
  * protect them by srcu_read_lock().
  */
-int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
+static int do_dquot_transfer(struct inode *inode,
+		struct dquot **transfer_to, int flags)
 {
 	qsize_t space, cur_space;
 	qsize_t rsv_space = 0;
@@ -1879,10 +1880,10 @@  int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
 		is_valid[cnt] = 1;
 		transfer_from[cnt] = i_dquot(inode)[cnt];
 		ret = check_idq(transfer_to[cnt], 1, &warn_to[cnt]);
-		if (ret)
+		if (ret && !(flags & DQUOT_TRANSFER_NOFAIL))
 			goto over_quota;
 		ret = check_bdq(transfer_to[cnt], space, 0, &warn_to[cnt]);
-		if (ret)
+		if (ret && !(flags & DQUOT_TRANSFER_NOFAIL))
 			goto over_quota;
 	}
 
@@ -1932,6 +1933,11 @@  over_quota:
 	flush_warnings(warn_to);
 	return ret;
 }
+
+int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
+{
+	return do_dquot_transfer(inode, transfer_to, 0);
+}
 EXPORT_SYMBOL(__dquot_transfer);
 
 /* Wrapper for transferring ownership of an inode for uid/gid only
@@ -1960,7 +1966,7 @@  EXPORT_SYMBOL(dquot_transfer);
 /*
  * Helper function for transferring inode into another project.
  */
-int dquot_transfer_project(struct inode *inode, kprojid_t projid)
+int dquot_transfer_project(struct inode *inode, kprojid_t projid, int flags)
 {
 	struct dquot *transfer_to[MAXQUOTAS] = {};
 	struct super_block *sb = inode->i_sb;
@@ -1969,7 +1975,7 @@  int dquot_transfer_project(struct inode *inode, kprojid_t projid)
 	if (!sb_has_quota_active(sb, PRJQUOTA))
 		return 0;
 	transfer_to[PRJQUOTA] = dqget(sb, make_kqid_projid(projid));
-	ret = __dquot_transfer(inode, transfer_to);
+	ret = do_dquot_transfer(inode, transfer_to, flags);
 	dqput_all(transfer_to);
 	return ret;
 }
diff --git a/include/linux/quotaops.h b/include/linux/quotaops.h
index ba54745fe408..810b88c69c5b 100644
--- a/include/linux/quotaops.h
+++ b/include/linux/quotaops.h
@@ -9,10 +9,18 @@ 
 
 #include <linux/fs.h>
 
+/*
+ * Flags for functions __dquot_alloc_space() and __dquot_free_space()
+ */
 #define DQUOT_SPACE_WARN	0x1
 #define DQUOT_SPACE_RESERVE	0x2
 #define DQUOT_SPACE_NOFAIL	0x4
 
+/*
+ * Flags for functions dquot_transfer_*
+ */
+#define DQUOT_TRANSFER_NOFAIL	0x1
+
 static inline struct quota_info *sb_dqopt(struct super_block *sb)
 {
 	return &sb->s_dquot;
@@ -104,7 +112,7 @@  int dquot_set_dqblk(struct super_block *sb, struct kqid id,
 
 int __dquot_transfer(struct inode *inode, struct dquot **transfer_to);
 int dquot_transfer(struct inode *inode, struct iattr *iattr);
-int dquot_transfer_project(struct inode *inode, kprojid_t projid);
+int dquot_transfer_project(struct inode *inode, kprojid_t projid, int flags);
 
 static inline struct mem_dqinfo *sb_dqinfo(struct super_block *sb, int type)
 {