2028 lines · 53.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * linux/fs/ext4/ioctl.c * * Copyright (C) 1993, 1994, 1995 * Remy Card (card@masi.ibp.fr) * Laboratoire MASI - Institut Blaise Pascal * Universite Pierre et Marie Curie (Paris VI) */ #include <linux/fs.h>#include <linux/capability.h>#include <linux/time.h>#include <linux/compat.h>#include <linux/mount.h>#include <linux/file.h>#include <linux/quotaops.h>#include <linux/random.h>#include <linux/uaccess.h>#include <linux/delay.h>#include <linux/iversion.h>#include <linux/fileattr.h>#include <linux/uuid.h>#include "ext4_jbd2.h"#include "ext4.h"#include <linux/fsmap.h>#include "fsmap.h"#include <trace/events/ext4.h>#include <linux/fserror.h> typedef void ext4_update_sb_callback(struct ext4_sb_info *sbi,				     struct ext4_super_block *es,				     const void *arg); /* * Superblock modification callback function for changing file system * label */static void ext4_sb_setlabel(struct ext4_sb_info *sbi,			     struct ext4_super_block *es, const void *arg){	/* Sanity check, this should never happen */	BUILD_BUG_ON(sizeof(es->s_volume_name) < EXT4_LABEL_MAX); 	memcpy(es->s_volume_name, (char *)arg, EXT4_LABEL_MAX);} /* * Superblock modification callback function for changing file system * UUID. */static void ext4_sb_setuuid(struct ext4_sb_info *sbi,			    struct ext4_super_block *es, const void *arg){	memcpy(es->s_uuid, (__u8 *)arg, UUID_SIZE);} staticint ext4_update_primary_sb(struct super_block *sb, handle_t *handle,			   ext4_update_sb_callback func,			   const void *arg){	int err = 0;	struct ext4_sb_info *sbi = EXT4_SB(sb);	struct buffer_head *bh = sbi->s_sbh;	struct ext4_super_block *es = sbi->s_es; 	trace_ext4_update_sb(sb, bh->b_blocknr, 1); 	BUFFER_TRACE(bh, "get_write_access");	err = ext4_journal_get_write_access(handle, sb,					    bh,					    EXT4_JTR_NONE);	if (err)		goto out_err; 	lock_buffer(bh);	func(sbi, es, arg);	ext4_superblock_csum_set(sb);	unlock_buffer(bh); 	if (buffer_write_io_error(bh) || !buffer_uptodate(bh)) {		ext4_msg(sbi->s_sb, KERN_ERR, "previous I/O error to "			 "superblock detected");		clear_buffer_write_io_error(bh);		set_buffer_uptodate(bh);	} 	err = ext4_handle_dirty_metadata(handle, NULL, bh);	if (err)		goto out_err;	err = sync_dirty_buffer(bh);out_err:	ext4_std_error(sb, err);	return err;} /* * Update one backup superblock in the group 'grp' using the callback * function 'func' and argument 'arg'. If the handle is NULL the * modification is not journalled. * * Returns: 0 when no modification was done (no superblock in the group) *	    1 when the modification was successful *	   <0 on error */static int ext4_update_backup_sb(struct super_block *sb,				 handle_t *handle, ext4_group_t grp,				 ext4_update_sb_callback func, const void *arg){	int err = 0;	ext4_fsblk_t sb_block;	struct buffer_head *bh;	unsigned long offset = 0;	struct ext4_super_block *es; 	if (!ext4_bg_has_super(sb, grp))		return 0; 	/*	 * For the group 0 there is always 1k padding, so we have	 * either adjust offset, or sb_block depending on blocksize	 */	if (grp == 0) {		sb_block = 1 * EXT4_MIN_BLOCK_SIZE;		offset = do_div(sb_block, sb->s_blocksize);	} else {		sb_block = ext4_group_first_block_no(sb, grp);		offset = 0;	} 	trace_ext4_update_sb(sb, sb_block, handle ? 1 : 0); 	bh = ext4_sb_bread(sb, sb_block, 0);	if (IS_ERR(bh))		return PTR_ERR(bh); 	if (handle) {		BUFFER_TRACE(bh, "get_write_access");		err = ext4_journal_get_write_access(handle, sb,						    bh,						    EXT4_JTR_NONE);		if (err)			goto out_bh;	} 	es = (struct ext4_super_block *) (bh->b_data + offset);	lock_buffer(bh);	if (ext4_has_feature_metadata_csum(sb) &&	    es->s_checksum != ext4_superblock_csum(es)) {		ext4_msg(sb, KERN_ERR, "Invalid checksum for backup "		"superblock %llu", sb_block);		unlock_buffer(bh);		goto out_bh;	}	func(EXT4_SB(sb), es, arg);	if (ext4_has_feature_metadata_csum(sb))		es->s_checksum = ext4_superblock_csum(es);	set_buffer_uptodate(bh);	unlock_buffer(bh); 	if (handle) {		err = ext4_handle_dirty_metadata(handle, NULL, bh);		if (err)			goto out_bh;	} else {		BUFFER_TRACE(bh, "marking dirty");		mark_buffer_dirty(bh);	}	err = sync_dirty_buffer(bh); out_bh:	brelse(bh);	ext4_std_error(sb, err);	return (err) ? err : 1;} /* * Update primary and backup superblocks using the provided function * func and argument arg. * * Only the primary superblock and at most two backup superblock * modifications are journalled; the rest is modified without journal. * This is safe because e2fsck will re-write them if there is a problem, * and we're very unlikely to ever need more than two backups. */staticint ext4_update_superblocks_fn(struct super_block *sb,			       ext4_update_sb_callback func,			       const void *arg){	handle_t *handle;	ext4_group_t ngroups;	unsigned int three = 1;	unsigned int five = 5;	unsigned int seven = 7;	int err = 0, ret, i;	ext4_group_t grp, primary_grp;	struct ext4_sb_info *sbi = EXT4_SB(sb); 	/*	 * We can't update superblocks while the online resize is running	 */	if (test_and_set_bit_lock(EXT4_FLAGS_RESIZING,				  &sbi->s_ext4_flags)) {		ext4_msg(sb, KERN_ERR, "Can't modify superblock while"			 "performing online resize");		return -EBUSY;	} 	/*	 * We're only going to update primary superblock and two	 * backup superblocks in this transaction.	 */	handle = ext4_journal_start_sb(sb, EXT4_HT_MISC, 3);	if (IS_ERR(handle)) {		err = PTR_ERR(handle);		goto out;	} 	/* Update primary superblock */	err = ext4_update_primary_sb(sb, handle, func, arg);	if (err) {		ext4_msg(sb, KERN_ERR, "Failed to update primary "			 "superblock");		goto out_journal;	} 	primary_grp = ext4_get_group_number(sb, sbi->s_sbh->b_blocknr);	ngroups = ext4_get_groups_count(sb); 	/*	 * Update backup superblocks. We have to start from group 0	 * because it might not be where the primary superblock is	 * if the fs is mounted with -o sb=<backup_sb_block>	 */	i = 0;	grp = 0;	while (grp < ngroups) {		/* Skip primary superblock */		if (grp == primary_grp)			goto next_grp; 		ret = ext4_update_backup_sb(sb, handle, grp, func, arg);		if (ret < 0) {			/* Ignore bad checksum; try to update next sb */			if (ret == -EFSBADCRC)				goto next_grp;			err = ret;			goto out_journal;		} 		i += ret;		if (handle && i > 1) {			/*			 * We're only journalling primary superblock and			 * two backup superblocks; the rest is not			 * journalled.			 */			err = ext4_journal_stop(handle);			if (err)				goto out;			handle = NULL;		}next_grp:		grp = ext4_list_backups(sb, &three, &five, &seven);	} out_journal:	if (handle) {		ret = ext4_journal_stop(handle);		if (ret && !err)			err = ret;	}out:	clear_bit_unlock(EXT4_FLAGS_RESIZING, &sbi->s_ext4_flags);	smp_mb__after_atomic();	return err ? err : 0;} /* * Swap memory between @a and @b for @len bytes. * * @a:          pointer to first memory area * @b:          pointer to second memory area * @len:        number of bytes to swap * */static void memswap(void *a, void *b, size_t len){	unsigned char *ap, *bp; 	ap = (unsigned char *)a;	bp = (unsigned char *)b;	while (len-- > 0) {		swap(*ap, *bp);		ap++;		bp++;	}} /* * Swap i_data and associated attributes between @inode1 and @inode2. * This function is used for the primary swap between inode1 and inode2 * and also to revert this primary swap in case of errors. * * Therefore you have to make sure, that calling this method twice * will revert all changes. * * @inode1:     pointer to first inode * @inode2:     pointer to second inode */static void swap_inode_data(struct inode *inode1, struct inode *inode2){	loff_t isize;	struct ext4_inode_info *ei1;	struct ext4_inode_info *ei2;	unsigned long tmp;	struct timespec64 ts1, ts2; 	ei1 = EXT4_I(inode1);	ei2 = EXT4_I(inode2); 	swap(inode1->i_version, inode2->i_version); 	ts1 = inode_get_atime(inode1);	ts2 = inode_get_atime(inode2);	inode_set_atime_to_ts(inode1, ts2);	inode_set_atime_to_ts(inode2, ts1); 	ts1 = inode_get_mtime(inode1);	ts2 = inode_get_mtime(inode2);	inode_set_mtime_to_ts(inode1, ts2);	inode_set_mtime_to_ts(inode2, ts1); 	memswap(ei1->i_data, ei2->i_data, sizeof(ei1->i_data));	tmp = ei1->i_flags & EXT4_FL_SHOULD_SWAP;	ei1->i_flags = (ei2->i_flags & EXT4_FL_SHOULD_SWAP) |		(ei1->i_flags & ~EXT4_FL_SHOULD_SWAP);	ei2->i_flags = tmp | (ei2->i_flags & ~EXT4_FL_SHOULD_SWAP);	swap(ei1->i_disksize, ei2->i_disksize);	ext4_es_remove_extent(inode1, 0, EXT_MAX_BLOCKS);	ext4_es_remove_extent(inode2, 0, EXT_MAX_BLOCKS); 	isize = i_size_read(inode1);	i_size_write(inode1, i_size_read(inode2));	i_size_write(inode2, isize);} void ext4_reset_inode_seed(struct inode *inode){	struct ext4_inode_info *ei = EXT4_I(inode);	struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);	__le32 inum = cpu_to_le32(inode->i_ino);	__le32 gen = cpu_to_le32(inode->i_generation);	__u32 csum; 	if (!ext4_has_feature_metadata_csum(inode->i_sb))		return; 	csum = ext4_chksum(sbi->s_csum_seed, (__u8 *)&inum, sizeof(inum));	ei->i_csum_seed = ext4_chksum(csum, (__u8 *)&gen, sizeof(gen));} /* * Swap the information from the given @inode and the inode * EXT4_BOOT_LOADER_INO. It will basically swap i_data and all other * important fields of the inodes. * * @sb:         the super block of the filesystem * @idmap:	idmap of the mount the inode was found from * @inode:      the inode to swap with EXT4_BOOT_LOADER_INO * */static long swap_inode_boot_loader(struct super_block *sb,				struct mnt_idmap *idmap,				struct inode *inode){	handle_t *handle;	int err;	struct inode *inode_bl;	struct ext4_inode_info *ei_bl;	qsize_t size, size_bl, diff;	blkcnt_t blocks;	unsigned short bytes; 	inode_bl = ext4_iget(sb, EXT4_BOOT_LOADER_INO,			EXT4_IGET_SPECIAL | EXT4_IGET_BAD);	if (IS_ERR(inode_bl))		return PTR_ERR(inode_bl);	ei_bl = EXT4_I(inode_bl); 	/* Protect orig inodes against a truncate and make sure,	 * that only 1 swap_inode_boot_loader is running. */	lock_two_nondirectories(inode, inode_bl); 	if (inode->i_nlink != 1 || !S_ISREG(inode->i_mode) ||	    IS_SWAPFILE(inode) || IS_ENCRYPTED(inode) ||	    (EXT4_I(inode)->i_flags & EXT4_JOURNAL_DATA_FL) ||	    ext4_has_inline_data(inode)) {		err = -EINVAL;		goto journal_err_out;	} 	if (IS_RDONLY(inode) || IS_APPEND(inode) || IS_IMMUTABLE(inode) ||	    !inode_owner_or_capable(idmap, inode) ||	    !capable(CAP_SYS_ADMIN)) {		err = -EPERM;		goto journal_err_out;	} 	filemap_invalidate_lock(inode->i_mapping);	err = filemap_write_and_wait(inode->i_mapping);	if (err)		goto err_out; 	err = filemap_write_and_wait(inode_bl->i_mapping);	if (err)		goto err_out; 	/* Wait for all existing dio workers */	inode_dio_wait(inode);	inode_dio_wait(inode_bl); 	truncate_inode_pages(&inode->i_data, 0);	truncate_inode_pages(&inode_bl->i_data, 0); 	handle = ext4_journal_start(inode_bl, EXT4_HT_MOVE_EXTENTS, 2);	if (IS_ERR(handle)) {		err = -EINVAL;		goto err_out;	}	ext4_fc_mark_ineligible(sb, EXT4_FC_REASON_SWAP_BOOT, handle); 	/* Protect extent tree against block allocations via delalloc */	ext4_double_down_write_data_sem(inode, inode_bl); 	if (is_bad_inode(inode_bl) || !S_ISREG(inode_bl->i_mode)) {		/* this inode has never been used as a BOOT_LOADER */		set_nlink(inode_bl, 1);		i_uid_write(inode_bl, 0);		i_gid_write(inode_bl, 0);		inode_bl->i_flags = 0;		ei_bl->i_flags = 0;		inode_set_iversion(inode_bl, 1);		i_size_write(inode_bl, 0);		EXT4_I(inode_bl)->i_disksize = inode_bl->i_size;		inode_bl->i_mode = S_IFREG;		if (ext4_has_feature_extents(sb)) {			ext4_set_inode_flag(inode_bl, EXT4_INODE_EXTENTS);			ext4_ext_tree_init(handle, inode_bl);		} else			memset(ei_bl->i_data, 0, sizeof(ei_bl->i_data));	} 	err = dquot_initialize(inode);	if (err)		goto err_out1; 	size = (qsize_t)(inode->i_blocks) * (1 << 9) + inode->i_bytes;	size_bl = (qsize_t)(inode_bl->i_blocks) * (1 << 9) + inode_bl->i_bytes;	diff = size - size_bl;	swap_inode_data(inode, inode_bl); 	inode_set_ctime_current(inode);	inode_set_ctime_current(inode_bl);	inode_inc_iversion(inode); 	inode->i_generation = get_random_u32();	inode_bl->i_generation = get_random_u32();	ext4_reset_inode_seed(inode);	ext4_reset_inode_seed(inode_bl); 	ext4_discard_preallocations(inode); 	err = ext4_mark_inode_dirty(handle, inode);	if (err < 0) {		/* No need to update quota information. */		ext4_warning(inode->i_sb,			"couldn't mark inode #%llu dirty (err %d)",			inode->i_ino, err);		/* Revert all changes: */		swap_inode_data(inode, inode_bl);		ext4_mark_inode_dirty(handle, inode);		goto err_out1;	} 	blocks = inode_bl->i_blocks;	bytes = inode_bl->i_bytes;	inode_bl->i_blocks = inode->i_blocks;	inode_bl->i_bytes = inode->i_bytes;	err = ext4_mark_inode_dirty(handle, inode_bl);	if (err < 0) {		/* No need to update quota information. */		ext4_warning(inode_bl->i_sb,			"couldn't mark inode #%llu dirty (err %d)",			inode_bl->i_ino, err);		goto revert;	} 

Lines 1–500 of 2028.