2192 lines · 63.7 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* *  linux/fs/ext4/resize.c * * Support for resizing an ext4 filesystem while it is mounted. * * Copyright (C) 2001, 2002 Andreas Dilger <adilger@clusterfs.com> * * This could probably be made into a module, because it is not often in use. */  #include <linux/errno.h>#include <linux/slab.h>#include <linux/jiffies.h> #include "ext4_jbd2.h" struct ext4_rcu_ptr {	struct rcu_head rcu;	void *ptr;}; static void ext4_rcu_ptr_callback(struct rcu_head *head){	struct ext4_rcu_ptr *ptr; 	ptr = container_of(head, struct ext4_rcu_ptr, rcu);	kvfree(ptr->ptr);	kfree(ptr);} void ext4_kvfree_array_rcu(void *to_free){	struct ext4_rcu_ptr *ptr = kzalloc_obj(*ptr); 	if (ptr) {		ptr->ptr = to_free;		call_rcu(&ptr->rcu, ext4_rcu_ptr_callback);		return;	}	synchronize_rcu();	kvfree(to_free);} int ext4_resize_begin(struct super_block *sb){	struct ext4_sb_info *sbi = EXT4_SB(sb);	int ret = 0; 	if (!capable(CAP_SYS_RESOURCE))		return -EPERM; 	/*	 * If the reserved GDT blocks is non-zero, the resize_inode feature	 * should always be set.	 */	if (sbi->s_es->s_reserved_gdt_blocks &&	    !ext4_has_feature_resize_inode(sb)) {		ext4_error(sb, "resize_inode disabled but reserved GDT blocks non-zero");		return -EFSCORRUPTED;	} 	/*	 * If we are not using the primary superblock/GDT copy don't resize,         * because the user tools have no way of handling this.  Probably a         * bad time to do it anyways.         */	if (EXT4_B2C(sbi, sbi->s_sbh->b_blocknr) !=	    le32_to_cpu(sbi->s_es->s_first_data_block)) {		ext4_warning(sb, "won't resize using backup superblock at %llu",			(unsigned long long)sbi->s_sbh->b_blocknr);		return -EPERM;	} 	/*	 * We are not allowed to do online-resizing on a filesystem mounted	 * with error, because it can destroy the filesystem easily.	 */	if (sbi->s_mount_state & EXT4_ERROR_FS) {		ext4_warning(sb, "There are errors in the filesystem, "			     "so online resizing is not allowed");		return -EPERM;	} 	if (ext4_has_feature_sparse_super2(sb)) {		ext4_msg(sb, KERN_ERR, "Online resizing not supported with sparse_super2");		return -EOPNOTSUPP;	} 	if (test_and_set_bit_lock(EXT4_FLAGS_RESIZING,				  &sbi->s_ext4_flags))		ret = -EBUSY; 	return ret;} int ext4_resize_end(struct super_block *sb, bool update_backups){	clear_bit_unlock(EXT4_FLAGS_RESIZING, &EXT4_SB(sb)->s_ext4_flags);	smp_mb__after_atomic();	if (update_backups)		return ext4_update_overhead(sb, true);	return 0;} static ext4_grpblk_t ext4_group_overhead_blocks(struct super_block *sb,						ext4_group_t group) {	ext4_grpblk_t overhead;	overhead = ext4_bg_num_gdb(sb, group);	if (ext4_bg_has_super(sb, group))		overhead += 1 +			  le16_to_cpu(EXT4_SB(sb)->s_es->s_reserved_gdt_blocks);	return overhead;} #define outside(b, first, last)	((b) < (first) || (b) >= (last))#define inside(b, first, last)	((b) >= (first) && (b) < (last)) static int verify_group_input(struct super_block *sb,			      struct ext4_new_group_data *input){	struct ext4_sb_info *sbi = EXT4_SB(sb);	struct ext4_super_block *es = sbi->s_es;	ext4_fsblk_t start = ext4_blocks_count(es);	ext4_fsblk_t end = start + input->blocks_count;	ext4_group_t group = input->group;	ext4_fsblk_t itend = input->inode_table + sbi->s_itb_per_group;	unsigned overhead;	ext4_fsblk_t metaend;	struct buffer_head *bh = NULL;	ext4_grpblk_t free_blocks_count, offset;	int err = -EINVAL; 	if (group != sbi->s_groups_count) {		ext4_warning(sb, "Cannot add at group %u (only %u groups)",			     input->group, sbi->s_groups_count);		return -EINVAL;	} 	overhead = ext4_group_overhead_blocks(sb, group);	metaend = start + overhead;	free_blocks_count = input->blocks_count - 2 - overhead -			    sbi->s_itb_per_group;	input->free_clusters_count = EXT4_B2C(sbi, free_blocks_count); 	if (test_opt(sb, DEBUG))		printk(KERN_DEBUG "EXT4-fs: adding %s group %u: %u blocks "		       "(%d free, %u reserved)\n",		       ext4_bg_has_super(sb, input->group) ? "normal" :		       "no-super", input->group, input->blocks_count,		       free_blocks_count, input->reserved_blocks); 	ext4_get_group_no_and_offset(sb, start, NULL, &offset);	if (offset != 0)			ext4_warning(sb, "Last group not full");	else if (input->reserved_blocks > input->blocks_count / 5)		ext4_warning(sb, "Reserved blocks too high (%u)",			     input->reserved_blocks);	else if (free_blocks_count < 0)		ext4_warning(sb, "Bad blocks count %u",			     input->blocks_count);	else if (IS_ERR(bh = ext4_sb_bread(sb, end - 1, 0))) {		err = PTR_ERR(bh);		bh = NULL;		ext4_warning(sb, "Cannot read last block (%llu)",			     end - 1);	} else if (outside(input->block_bitmap, start, end))		ext4_warning(sb, "Block bitmap not in group (block %llu)",			     (unsigned long long)input->block_bitmap);	else if (outside(input->inode_bitmap, start, end))		ext4_warning(sb, "Inode bitmap not in group (block %llu)",			     (unsigned long long)input->inode_bitmap);	else if (outside(input->inode_table, start, end) ||		 outside(itend - 1, start, end))		ext4_warning(sb, "Inode table not in group (blocks %llu-%llu)",			     (unsigned long long)input->inode_table, itend - 1);	else if (input->inode_bitmap == input->block_bitmap)		ext4_warning(sb, "Block bitmap same as inode bitmap (%llu)",			     (unsigned long long)input->block_bitmap);	else if (inside(input->block_bitmap, input->inode_table, itend))		ext4_warning(sb, "Block bitmap (%llu) in inode table "			     "(%llu-%llu)",			     (unsigned long long)input->block_bitmap,			     (unsigned long long)input->inode_table, itend - 1);	else if (inside(input->inode_bitmap, input->inode_table, itend))		ext4_warning(sb, "Inode bitmap (%llu) in inode table "			     "(%llu-%llu)",			     (unsigned long long)input->inode_bitmap,			     (unsigned long long)input->inode_table, itend - 1);	else if (inside(input->block_bitmap, start, metaend))		ext4_warning(sb, "Block bitmap (%llu) in GDT table (%llu-%llu)",			     (unsigned long long)input->block_bitmap,			     start, metaend - 1);	else if (inside(input->inode_bitmap, start, metaend))		ext4_warning(sb, "Inode bitmap (%llu) in GDT table (%llu-%llu)",			     (unsigned long long)input->inode_bitmap,			     start, metaend - 1);	else if (inside(input->inode_table, start, metaend) ||		 inside(itend - 1, start, metaend))		ext4_warning(sb, "Inode table (%llu-%llu) overlaps GDT table "			     "(%llu-%llu)",			     (unsigned long long)input->inode_table,			     itend - 1, start, metaend - 1);	else		err = 0;	brelse(bh); 	return err;} /* * ext4_new_flex_group_data is used by 64bit-resize interface to add a flex * group each time. */struct ext4_new_flex_group_data {	struct ext4_new_group_data *groups;	/* new_group_data for groups						   in the flex group */	__u16 *bg_flags;			/* block group flags of groups						   in @groups */	ext4_group_t resize_bg;			/* number of allocated						   new_group_data */	ext4_group_t count;			/* number of groups in @groups						 */}; /* * Avoiding memory allocation failures due to too many groups added each time. */#define MAX_RESIZE_BG				16384 /* * alloc_flex_gd() allocates an ext4_new_flex_group_data that satisfies the * resizing from @o_group to @n_group, its size is typically @flexbg_size. * * Returns NULL on failure otherwise address of the allocated structure. */static struct ext4_new_flex_group_data *alloc_flex_gd(unsigned int flexbg_size,				ext4_group_t o_group, ext4_group_t n_group){	ext4_group_t last_group;	unsigned int max_resize_bg;	struct ext4_new_flex_group_data *flex_gd; 	flex_gd = kmalloc_obj(*flex_gd, GFP_NOFS);	if (flex_gd == NULL)		goto out3; 	max_resize_bg = umin(flexbg_size, MAX_RESIZE_BG);	flex_gd->resize_bg = max_resize_bg; 	/* Avoid allocating large 'groups' array if not needed */	last_group = o_group | (flex_gd->resize_bg - 1);	if (n_group <= last_group)		flex_gd->resize_bg = 1 << fls(n_group - o_group);	else if (n_group - last_group < flex_gd->resize_bg)		flex_gd->resize_bg = 1 << max(fls(last_group - o_group),					      fls(n_group - last_group)); 	if (WARN_ON_ONCE(flex_gd->resize_bg > max_resize_bg))		flex_gd->resize_bg = max_resize_bg; 	flex_gd->groups = kmalloc_objs(struct ext4_new_group_data,				       flex_gd->resize_bg, GFP_NOFS);	if (flex_gd->groups == NULL)		goto out2; 	flex_gd->bg_flags = kmalloc_array(flex_gd->resize_bg, sizeof(__u16),					  GFP_NOFS);	if (flex_gd->bg_flags == NULL)		goto out1; 	return flex_gd; out1:	kfree(flex_gd->groups);out2:	kfree(flex_gd);out3:	return NULL;} static void free_flex_gd(struct ext4_new_flex_group_data *flex_gd){	kfree(flex_gd->bg_flags);	kfree(flex_gd->groups);	kfree(flex_gd);} /* * ext4_alloc_group_tables() allocates block bitmaps, inode bitmaps * and inode tables for a flex group. * * This function is used by 64bit-resize.  Note that this function allocates * group tables from the 1st group of groups contained by @flexgd, which may * be a partial of a flex group. * * @sb: super block of fs to which the groups belongs * * Returns 0 on a successful allocation of the metadata blocks in the * block group. */static int ext4_alloc_group_tables(struct super_block *sb,				struct ext4_new_flex_group_data *flex_gd,				unsigned int flexbg_size){	struct ext4_new_group_data *group_data = flex_gd->groups;	ext4_fsblk_t start_blk;	ext4_fsblk_t last_blk;	ext4_group_t src_group;	ext4_group_t bb_index = 0;	ext4_group_t ib_index = 0;	ext4_group_t it_index = 0;	ext4_group_t group;	ext4_group_t last_group;	unsigned overhead;	__u16 uninit_mask = (flexbg_size > 1) ? ~EXT4_BG_BLOCK_UNINIT : ~0;	int i; 	BUG_ON(flex_gd->count == 0 || group_data == NULL); 	src_group = group_data[0].group;	last_group  = src_group + flex_gd->count - 1; 	BUG_ON((flexbg_size > 1) && ((src_group & ~(flexbg_size - 1)) !=	       (last_group & ~(flexbg_size - 1))));next_group:	group = group_data[0].group;	if (src_group >= group_data[0].group + flex_gd->count)		return -ENOSPC;	start_blk = ext4_group_first_block_no(sb, src_group);	last_blk = start_blk + group_data[src_group - group].blocks_count; 	overhead = ext4_group_overhead_blocks(sb, src_group); 	start_blk += overhead; 	/* We collect contiguous blocks as much as possible. */	src_group++;	for (; src_group <= last_group; src_group++) {		overhead = ext4_group_overhead_blocks(sb, src_group);		if (overhead == 0)			last_blk += group_data[src_group - group].blocks_count;		else			break;	} 	/* Allocate block bitmaps */	for (; bb_index < flex_gd->count; bb_index++) {		if (start_blk >= last_blk)			goto next_group;		group_data[bb_index].block_bitmap = start_blk++;		group = ext4_get_group_number(sb, start_blk - 1);		group -= group_data[0].group;		group_data[group].mdata_blocks++;		flex_gd->bg_flags[group] &= uninit_mask;	} 	/* Allocate inode bitmaps */	for (; ib_index < flex_gd->count; ib_index++) {		if (start_blk >= last_blk)			goto next_group;		group_data[ib_index].inode_bitmap = start_blk++;		group = ext4_get_group_number(sb, start_blk - 1);		group -= group_data[0].group;		group_data[group].mdata_blocks++;		flex_gd->bg_flags[group] &= uninit_mask;	} 	/* Allocate inode tables */	for (; it_index < flex_gd->count; it_index++) {		unsigned int itb = EXT4_SB(sb)->s_itb_per_group;		ext4_fsblk_t next_group_start; 		if (start_blk + itb > last_blk)			goto next_group;		group_data[it_index].inode_table = start_blk;		group = ext4_get_group_number(sb, start_blk);		next_group_start = ext4_group_first_block_no(sb, group + 1);		group -= group_data[0].group; 		if (start_blk + itb > next_group_start) {			flex_gd->bg_flags[group + 1] &= uninit_mask;			overhead = start_blk + itb - next_group_start;			group_data[group + 1].mdata_blocks += overhead;			itb -= overhead;		} 		group_data[group].mdata_blocks += itb;		flex_gd->bg_flags[group] &= uninit_mask;		start_blk += EXT4_SB(sb)->s_itb_per_group;	} 	/* Update free clusters count to exclude metadata blocks */	for (i = 0; i < flex_gd->count; i++) {		group_data[i].free_clusters_count -=				EXT4_NUM_B2C(EXT4_SB(sb),					     group_data[i].mdata_blocks);	} 	if (test_opt(sb, DEBUG)) {		int i;		group = group_data[0].group; 		printk(KERN_DEBUG "EXT4-fs: adding a flex group with "		       "%u groups, flexbg size is %u:\n", flex_gd->count,		       flexbg_size); 		for (i = 0; i < flex_gd->count; i++) {			ext4_debug(			       "adding %s group %u: %u blocks (%u free, %u mdata blocks)\n",			       ext4_bg_has_super(sb, group + i) ? "normal" :			       "no-super", group + i,			       group_data[i].blocks_count,			       group_data[i].free_clusters_count,			       group_data[i].mdata_blocks);		}	}	return 0;} static struct buffer_head *bclean(handle_t *handle, struct super_block *sb,				  ext4_fsblk_t blk){	struct buffer_head *bh;	int err; 	bh = sb_getblk(sb, blk);	if (unlikely(!bh))		return ERR_PTR(-ENOMEM);	BUFFER_TRACE(bh, "get_write_access");	err = ext4_journal_get_write_access(handle, sb, bh, EXT4_JTR_NONE);	if (err) {		brelse(bh);		bh = ERR_PTR(err);	} else {		memset(bh->b_data, 0, sb->s_blocksize);		set_buffer_uptodate(bh);	} 	return bh;} static int ext4_resize_ensure_credits_batch(handle_t *handle, int credits){	return ext4_journal_ensure_credits_fn(handle, credits,		EXT4_MAX_TRANS_DATA, 0, 0);} /* * set_flexbg_block_bitmap() mark clusters [@first_cluster, @last_cluster] used. * * Helper function for ext4_setup_new_group_blocks() which set . * * @sb: super block * @handle: journal handle * @flex_gd: flex group data */static int set_flexbg_block_bitmap(struct super_block *sb, handle_t *handle,			struct ext4_new_flex_group_data *flex_gd,			ext4_fsblk_t first_cluster, ext4_fsblk_t last_cluster){	struct ext4_sb_info *sbi = EXT4_SB(sb);	ext4_group_t count = last_cluster - first_cluster + 1;	ext4_group_t count2; 	ext4_debug("mark clusters [%llu-%llu] used\n", first_cluster,		   last_cluster);	for (; count > 0; count -= count2, first_cluster += count2) {		ext4_fsblk_t start;		struct buffer_head *bh;		ext4_group_t group;		int err; 		group = ext4_get_group_number(sb, EXT4_C2B(sbi, first_cluster));		start = EXT4_B2C(sbi, ext4_group_first_block_no(sb, group));		group -= flex_gd->groups[0].group; 		count2 = EXT4_CLUSTERS_PER_GROUP(sb) - (first_cluster - start);		if (count2 > count)			count2 = count; 		if (flex_gd->bg_flags[group] & EXT4_BG_BLOCK_UNINIT) {			BUG_ON(flex_gd->count > 1);			continue;		} 		err = ext4_resize_ensure_credits_batch(handle, 1);		if (err < 0)			return err; 		bh = sb_getblk(sb, flex_gd->groups[group].block_bitmap);		if (unlikely(!bh))			return -ENOMEM; 		BUFFER_TRACE(bh, "get_write_access");		err = ext4_journal_get_write_access(handle, sb, bh,						    EXT4_JTR_NONE);		if (err) {			brelse(bh);

Lines 1–500 of 2192.