From patchwork Tue Mar 10 17:22:11 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Konstantin Khlebnikov X-Patchwork-Id: 5979681 Return-Path: X-Original-To: patchwork-linux-fsdevel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 833F89F2A9 for ; Tue, 10 Mar 2015 17:24:10 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 65B5E20225 for ; Tue, 10 Mar 2015 17:24:09 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 023B62021F for ; Tue, 10 Mar 2015 17:24:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753135AbbCJRXd (ORCPT ); Tue, 10 Mar 2015 13:23:33 -0400 Received: from forward-corp1f.mail.yandex.net ([95.108.130.40]:36394 "EHLO forward-corp1f.mail.yandex.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753701AbbCJRWP (ORCPT ); Tue, 10 Mar 2015 13:22:15 -0400 Received: from smtpcorp1m.mail.yandex.net (smtpcorp1m.mail.yandex.net [77.88.61.150]) by forward-corp1f.mail.yandex.net (Yandex) with ESMTP id 2A7B02420329; Tue, 10 Mar 2015 20:22:12 +0300 (MSK) Received: from smtpcorp1m.mail.yandex.net (localhost [127.0.0.1]) by smtpcorp1m.mail.yandex.net (Yandex) with ESMTP id 0B0A12CA043C; Tue, 10 Mar 2015 20:22:12 +0300 (MSK) Received: from unknown (unknown [2a02:6b8:0:408:a45e:343f:b17c:8d0d]) by smtpcorp1m.mail.yandex.net (nwsmtp/Yandex) with ESMTPSA id OzIuH8XFSh-MBouVJ9U; Tue, 10 Mar 2015 20:22:11 +0300 (using TLSv1.2 with cipher AES128-SHA (128/128 bits)) (Client certificate not present) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yandex-team.ru; s=default; t=1426008132; bh=oOXKUIT0PCvs5pg8GoevW8COxew0JVrs1hmxedA6nE0=; h=Subject:From:To:Cc:Date:Message-ID:In-Reply-To:References: User-Agent:MIME-Version:Content-Type:Content-Transfer-Encoding; b=OL+xAFp/Fumj8AdQWRAEAclxm3xuPDQcq1E4gPXbQLCAenmej//3PikqHUwGRP58E oV4+kKeuuwo6UB7y2CTgvAQd0seMRvJajFLCKQcJX4gLqw6broLa5cRubUdC5LJEu6 FLUuMI3BBz4xeO53sDl4lUNysLJj3FG+vIdaCaJE= Authentication-Results: smtpcorp1m.mail.yandex.net; dkim=pass header.i=@yandex-team.ru Subject: [PATCH RFC v2 5/6] ext4: add shortcut for moving files across projects From: Konstantin Khlebnikov To: linux-fsdevel@vger.kernel.org, Dave Chinner , Jan Kara , linux-ext4@vger.kernel.org, Theodore Ts'o Cc: Dmitry Monakhov , Andy Lutomirski , linux-kernel@vger.kernel.org, Li Xi Date: Tue, 10 Mar 2015 20:22:11 +0300 Message-ID: <20150310172210.23081.28245.stgit@buzz> In-Reply-To: <20150310171133.23081.49616.stgit@buzz> References: <20150310171133.23081.49616.stgit@buzz> User-Agent: StGit/0.17.1-dirty MIME-Version: 1.0 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org X-Spam-Status: No, score=-6.8 required=5.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID,T_RP_MATCHES_RCVD,UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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-fsdevel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html 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 +/* + * 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) {