diff options
-rw-r--r-- | Documentation/filesystems/ext4.txt | 7 | ||||
-rw-r--r-- | fs/ext4/ext4.h | 2 | ||||
-rw-r--r-- | fs/ext4/ioctl.c | 57 | ||||
-rw-r--r-- | fs/ext4/resize.c | 177 |
4 files changed, 243 insertions, 0 deletions
diff --git a/Documentation/filesystems/ext4.txt b/Documentation/filesystems/ext4.txt index 4917cf24a5e0..10ec4639f152 100644 --- a/Documentation/filesystems/ext4.txt +++ b/Documentation/filesystems/ext4.txt | |||
@@ -581,6 +581,13 @@ Table of Ext4 specific ioctls | |||
581 | behaviour may change in the future as it is | 581 | behaviour may change in the future as it is |
582 | not necessary and has been done this way only | 582 | not necessary and has been done this way only |
583 | for sake of simplicity. | 583 | for sake of simplicity. |
584 | |||
585 | EXT4_IOC_RESIZE_FS Resize the filesystem to a new size. The number | ||
586 | of blocks of resized filesystem is passed in via | ||
587 | 64 bit integer argument. The kernel allocates | ||
588 | bitmaps and inode table, the userspace tool thus | ||
589 | just passes the new number of blocks. | ||
590 | |||
584 | .............................................................................. | 591 | .............................................................................. |
585 | 592 | ||
586 | References | 593 | References |
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 05058e2b7f4f..4bc0e82a9054 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h | |||
@@ -583,6 +583,7 @@ enum { | |||
583 | /* note ioctl 11 reserved for filesystem-independent FIEMAP ioctl */ | 583 | /* note ioctl 11 reserved for filesystem-independent FIEMAP ioctl */ |
584 | #define EXT4_IOC_ALLOC_DA_BLKS _IO('f', 12) | 584 | #define EXT4_IOC_ALLOC_DA_BLKS _IO('f', 12) |
585 | #define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent) | 585 | #define EXT4_IOC_MOVE_EXT _IOWR('f', 15, struct move_extent) |
586 | #define EXT4_IOC_RESIZE_FS _IOW('f', 16, __u64) | ||
586 | 587 | ||
587 | #if defined(__KERNEL__) && defined(CONFIG_COMPAT) | 588 | #if defined(__KERNEL__) && defined(CONFIG_COMPAT) |
588 | /* | 589 | /* |
@@ -1929,6 +1930,7 @@ extern int ext4_group_add(struct super_block *sb, | |||
1929 | extern int ext4_group_extend(struct super_block *sb, | 1930 | extern int ext4_group_extend(struct super_block *sb, |
1930 | struct ext4_super_block *es, | 1931 | struct ext4_super_block *es, |
1931 | ext4_fsblk_t n_blocks_count); | 1932 | ext4_fsblk_t n_blocks_count); |
1933 | extern int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count); | ||
1932 | 1934 | ||
1933 | /* super.c */ | 1935 | /* super.c */ |
1934 | extern void *ext4_kvmalloc(size_t size, gfp_t flags); | 1936 | extern void *ext4_kvmalloc(size_t size, gfp_t flags); |
diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index ff1aab7cd6e8..c1a98804a383 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c | |||
@@ -18,6 +18,8 @@ | |||
18 | #include "ext4_jbd2.h" | 18 | #include "ext4_jbd2.h" |
19 | #include "ext4.h" | 19 | #include "ext4.h" |
20 | 20 | ||
21 | #define MAX_32_NUM ((((unsigned long long) 1) << 32) - 1) | ||
22 | |||
21 | long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | 23 | long ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
22 | { | 24 | { |
23 | struct inode *inode = filp->f_dentry->d_inode; | 25 | struct inode *inode = filp->f_dentry->d_inode; |
@@ -329,6 +331,60 @@ mext_out: | |||
329 | return err; | 331 | return err; |
330 | } | 332 | } |
331 | 333 | ||
334 | case EXT4_IOC_RESIZE_FS: { | ||
335 | ext4_fsblk_t n_blocks_count; | ||
336 | struct super_block *sb = inode->i_sb; | ||
337 | int err = 0, err2 = 0; | ||
338 | |||
339 | if (EXT4_HAS_RO_COMPAT_FEATURE(sb, | ||
340 | EXT4_FEATURE_RO_COMPAT_BIGALLOC)) { | ||
341 | ext4_msg(sb, KERN_ERR, | ||
342 | "Online resizing not (yet) supported with bigalloc"); | ||
343 | return -EOPNOTSUPP; | ||
344 | } | ||
345 | |||
346 | if (EXT4_HAS_INCOMPAT_FEATURE(sb, | ||
347 | EXT4_FEATURE_INCOMPAT_META_BG)) { | ||
348 | ext4_msg(sb, KERN_ERR, | ||
349 | "Online resizing not (yet) supported with meta_bg"); | ||
350 | return -EOPNOTSUPP; | ||
351 | } | ||
352 | |||
353 | if (copy_from_user(&n_blocks_count, (__u64 __user *)arg, | ||
354 | sizeof(__u64))) { | ||
355 | return -EFAULT; | ||
356 | } | ||
357 | |||
358 | if (n_blocks_count > MAX_32_NUM && | ||
359 | !EXT4_HAS_INCOMPAT_FEATURE(sb, | ||
360 | EXT4_FEATURE_INCOMPAT_64BIT)) { | ||
361 | ext4_msg(sb, KERN_ERR, | ||
362 | "File system only supports 32-bit block numbers"); | ||
363 | return -EOPNOTSUPP; | ||
364 | } | ||
365 | |||
366 | err = ext4_resize_begin(sb); | ||
367 | if (err) | ||
368 | return err; | ||
369 | |||
370 | err = mnt_want_write(filp->f_path.mnt); | ||
371 | if (err) | ||
372 | goto resizefs_out; | ||
373 | |||
374 | err = ext4_resize_fs(sb, n_blocks_count); | ||
375 | if (EXT4_SB(sb)->s_journal) { | ||
376 | jbd2_journal_lock_updates(EXT4_SB(sb)->s_journal); | ||
377 | err2 = jbd2_journal_flush(EXT4_SB(sb)->s_journal); | ||
378 | jbd2_journal_unlock_updates(EXT4_SB(sb)->s_journal); | ||
379 | } | ||
380 | if (err == 0) | ||
381 | err = err2; | ||
382 | mnt_drop_write(filp->f_path.mnt); | ||
383 | resizefs_out: | ||
384 | ext4_resize_end(sb); | ||
385 | return err; | ||
386 | } | ||
387 | |||
332 | case FITRIM: | 388 | case FITRIM: |
333 | { | 389 | { |
334 | struct request_queue *q = bdev_get_queue(sb->s_bdev); | 390 | struct request_queue *q = bdev_get_queue(sb->s_bdev); |
@@ -427,6 +483,7 @@ long ext4_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | |||
427 | } | 483 | } |
428 | case EXT4_IOC_MOVE_EXT: | 484 | case EXT4_IOC_MOVE_EXT: |
429 | case FITRIM: | 485 | case FITRIM: |
486 | case EXT4_IOC_RESIZE_FS: | ||
430 | break; | 487 | break; |
431 | default: | 488 | default: |
432 | return -ENOIOCTLCMD; | 489 | return -ENOIOCTLCMD; |
diff --git a/fs/ext4/resize.c b/fs/ext4/resize.c index dac23561f3eb..5fe2a013ee65 100644 --- a/fs/ext4/resize.c +++ b/fs/ext4/resize.c | |||
@@ -1430,6 +1430,70 @@ exit: | |||
1430 | return err; | 1430 | return err; |
1431 | } | 1431 | } |
1432 | 1432 | ||
1433 | static int ext4_setup_next_flex_gd(struct super_block *sb, | ||
1434 | struct ext4_new_flex_group_data *flex_gd, | ||
1435 | ext4_fsblk_t n_blocks_count, | ||
1436 | unsigned long flexbg_size) | ||
1437 | { | ||
1438 | struct ext4_super_block *es = EXT4_SB(sb)->s_es; | ||
1439 | struct ext4_new_group_data *group_data = flex_gd->groups; | ||
1440 | ext4_fsblk_t o_blocks_count; | ||
1441 | ext4_group_t n_group; | ||
1442 | ext4_group_t group; | ||
1443 | ext4_group_t last_group; | ||
1444 | ext4_grpblk_t last; | ||
1445 | ext4_grpblk_t blocks_per_group; | ||
1446 | unsigned long i; | ||
1447 | |||
1448 | blocks_per_group = EXT4_BLOCKS_PER_GROUP(sb); | ||
1449 | |||
1450 | o_blocks_count = ext4_blocks_count(es); | ||
1451 | |||
1452 | if (o_blocks_count == n_blocks_count) | ||
1453 | return 0; | ||
1454 | |||
1455 | ext4_get_group_no_and_offset(sb, o_blocks_count, &group, &last); | ||
1456 | BUG_ON(last); | ||
1457 | ext4_get_group_no_and_offset(sb, n_blocks_count - 1, &n_group, &last); | ||
1458 | |||
1459 | last_group = group | (flexbg_size - 1); | ||
1460 | if (last_group > n_group) | ||
1461 | last_group = n_group; | ||
1462 | |||
1463 | flex_gd->count = last_group - group + 1; | ||
1464 | |||
1465 | for (i = 0; i < flex_gd->count; i++) { | ||
1466 | int overhead; | ||
1467 | |||
1468 | group_data[i].group = group + i; | ||
1469 | group_data[i].blocks_count = blocks_per_group; | ||
1470 | overhead = ext4_bg_has_super(sb, group + i) ? | ||
1471 | (1 + ext4_bg_num_gdb(sb, group + i) + | ||
1472 | le16_to_cpu(es->s_reserved_gdt_blocks)) : 0; | ||
1473 | group_data[i].free_blocks_count = blocks_per_group - overhead; | ||
1474 | if (EXT4_HAS_RO_COMPAT_FEATURE(sb, | ||
1475 | EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) | ||
1476 | flex_gd->bg_flags[i] = EXT4_BG_BLOCK_UNINIT | | ||
1477 | EXT4_BG_INODE_UNINIT; | ||
1478 | else | ||
1479 | flex_gd->bg_flags[i] = EXT4_BG_INODE_ZEROED; | ||
1480 | } | ||
1481 | |||
1482 | if (last_group == n_group && | ||
1483 | EXT4_HAS_RO_COMPAT_FEATURE(sb, | ||
1484 | EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) | ||
1485 | /* We need to initialize block bitmap of last group. */ | ||
1486 | flex_gd->bg_flags[i - 1] &= ~EXT4_BG_BLOCK_UNINIT; | ||
1487 | |||
1488 | if ((last_group == n_group) && (last != blocks_per_group - 1)) { | ||
1489 | group_data[i - 1].blocks_count = last + 1; | ||
1490 | group_data[i - 1].free_blocks_count -= blocks_per_group- | ||
1491 | last - 1; | ||
1492 | } | ||
1493 | |||
1494 | return 1; | ||
1495 | } | ||
1496 | |||
1433 | /* Add group descriptor data to an existing or new group descriptor block. | 1497 | /* Add group descriptor data to an existing or new group descriptor block. |
1434 | * Ensure we handle all possible error conditions _before_ we start modifying | 1498 | * Ensure we handle all possible error conditions _before_ we start modifying |
1435 | * the filesystem, because we cannot abort the transaction and not have it | 1499 | * the filesystem, because we cannot abort the transaction and not have it |
@@ -1827,3 +1891,116 @@ int ext4_group_extend(struct super_block *sb, struct ext4_super_block *es, | |||
1827 | exit_put: | 1891 | exit_put: |
1828 | return err; | 1892 | return err; |
1829 | } /* ext4_group_extend */ | 1893 | } /* ext4_group_extend */ |
1894 | |||
1895 | /* | ||
1896 | * ext4_resize_fs() resizes a fs to new size specified by @n_blocks_count | ||
1897 | * | ||
1898 | * @sb: super block of the fs to be resized | ||
1899 | * @n_blocks_count: the number of blocks resides in the resized fs | ||
1900 | */ | ||
1901 | int ext4_resize_fs(struct super_block *sb, ext4_fsblk_t n_blocks_count) | ||
1902 | { | ||
1903 | struct ext4_new_flex_group_data *flex_gd = NULL; | ||
1904 | struct ext4_sb_info *sbi = EXT4_SB(sb); | ||
1905 | struct ext4_super_block *es = sbi->s_es; | ||
1906 | struct buffer_head *bh; | ||
1907 | struct inode *resize_inode; | ||
1908 | ext4_fsblk_t o_blocks_count; | ||
1909 | ext4_group_t o_group; | ||
1910 | ext4_group_t n_group; | ||
1911 | ext4_grpblk_t offset; | ||
1912 | unsigned long n_desc_blocks; | ||
1913 | unsigned long o_desc_blocks; | ||
1914 | unsigned long desc_blocks; | ||
1915 | int err = 0, flexbg_size = 1; | ||
1916 | |||
1917 | o_blocks_count = ext4_blocks_count(es); | ||
1918 | |||
1919 | if (test_opt(sb, DEBUG)) | ||
1920 | printk(KERN_DEBUG "EXT4-fs: resizing filesystem from %llu " | ||
1921 | "upto %llu blocks\n", o_blocks_count, n_blocks_count); | ||
1922 | |||
1923 | if (n_blocks_count < o_blocks_count) { | ||
1924 | /* On-line shrinking not supported */ | ||
1925 | ext4_warning(sb, "can't shrink FS - resize aborted"); | ||
1926 | return -EINVAL; | ||
1927 | } | ||
1928 | |||
1929 | if (n_blocks_count == o_blocks_count) | ||
1930 | /* Nothing need to do */ | ||
1931 | return 0; | ||
1932 | |||
1933 | ext4_get_group_no_and_offset(sb, n_blocks_count - 1, &n_group, &offset); | ||
1934 | ext4_get_group_no_and_offset(sb, o_blocks_count, &o_group, &offset); | ||
1935 | |||
1936 | n_desc_blocks = (n_group + EXT4_DESC_PER_BLOCK(sb)) / | ||
1937 | EXT4_DESC_PER_BLOCK(sb); | ||
1938 | o_desc_blocks = (sbi->s_groups_count + EXT4_DESC_PER_BLOCK(sb) - 1) / | ||
1939 | EXT4_DESC_PER_BLOCK(sb); | ||
1940 | desc_blocks = n_desc_blocks - o_desc_blocks; | ||
1941 | |||
1942 | if (desc_blocks && | ||
1943 | (!EXT4_HAS_COMPAT_FEATURE(sb, EXT4_FEATURE_COMPAT_RESIZE_INODE) || | ||
1944 | le16_to_cpu(es->s_reserved_gdt_blocks) < desc_blocks)) { | ||
1945 | ext4_warning(sb, "No reserved GDT blocks, can't resize"); | ||
1946 | return -EPERM; | ||
1947 | } | ||
1948 | |||
1949 | resize_inode = ext4_iget(sb, EXT4_RESIZE_INO); | ||
1950 | if (IS_ERR(resize_inode)) { | ||
1951 | ext4_warning(sb, "Error opening resize inode"); | ||
1952 | return PTR_ERR(resize_inode); | ||
1953 | } | ||
1954 | |||
1955 | /* See if the device is actually as big as what was requested */ | ||
1956 | bh = sb_bread(sb, n_blocks_count - 1); | ||
1957 | if (!bh) { | ||
1958 | ext4_warning(sb, "can't read last block, resize aborted"); | ||
1959 | return -ENOSPC; | ||
1960 | } | ||
1961 | brelse(bh); | ||
1962 | |||
1963 | if (offset != 0) { | ||
1964 | /* extend the last group */ | ||
1965 | ext4_grpblk_t add; | ||
1966 | add = EXT4_BLOCKS_PER_GROUP(sb) - offset; | ||
1967 | err = ext4_group_extend_no_check(sb, o_blocks_count, add); | ||
1968 | if (err) | ||
1969 | goto out; | ||
1970 | } | ||
1971 | |||
1972 | if (EXT4_HAS_INCOMPAT_FEATURE(sb, EXT4_FEATURE_INCOMPAT_FLEX_BG) && | ||
1973 | es->s_log_groups_per_flex) | ||
1974 | flexbg_size = 1 << es->s_log_groups_per_flex; | ||
1975 | |||
1976 | o_blocks_count = ext4_blocks_count(es); | ||
1977 | if (o_blocks_count == n_blocks_count) | ||
1978 | goto out; | ||
1979 | |||
1980 | flex_gd = alloc_flex_gd(flexbg_size); | ||
1981 | if (flex_gd == NULL) { | ||
1982 | err = -ENOMEM; | ||
1983 | goto out; | ||
1984 | } | ||
1985 | |||
1986 | /* Add flex groups. Note that a regular group is a | ||
1987 | * flex group with 1 group. | ||
1988 | */ | ||
1989 | while (ext4_setup_next_flex_gd(sb, flex_gd, n_blocks_count, | ||
1990 | flexbg_size)) { | ||
1991 | ext4_alloc_group_tables(sb, flex_gd, flexbg_size); | ||
1992 | err = ext4_flex_group_add(sb, resize_inode, flex_gd); | ||
1993 | if (unlikely(err)) | ||
1994 | break; | ||
1995 | } | ||
1996 | |||
1997 | out: | ||
1998 | if (flex_gd) | ||
1999 | free_flex_gd(flex_gd); | ||
2000 | |||
2001 | iput(resize_inode); | ||
2002 | if (test_opt(sb, DEBUG)) | ||
2003 | printk(KERN_DEBUG "EXT4-fs: resized filesystem from %llu " | ||
2004 | "upto %llu blocks\n", o_blocks_count, n_blocks_count); | ||
2005 | return err; | ||
2006 | } | ||