online.c revision 5a760d4906c620ef84886378db5eff28bdbd2e96
1/*
2 * online.c --- Do on-line resizing of the ext3 filesystem
3 *
4 * Copyright (C) 2006 by Theodore Ts'o
5 *
6 * %Begin-Header%
7 * This file may be redistributed under the terms of the GNU Public
8 * License.
9 * %End-Header%
10 */
11
12#include "config.h"
13#include "resize2fs.h"
14#ifdef HAVE_SYS_IOCTL_H
15#include <sys/ioctl.h>
16#endif
17#include <sys/stat.h>
18#include <fcntl.h>
19
20extern char *program_name;
21
22#define MAX_32_NUM ((((unsigned long long) 1) << 32) - 1)
23
24#ifdef __linux__
25static int parse_version_number(const char *s)
26{
27	int	major, minor, rev;
28	char	*endptr;
29	const char *cp = s;
30
31	if (!s)
32		return 0;
33	major = strtol(cp, &endptr, 10);
34	if (cp == endptr || *endptr != '.')
35		return 0;
36	cp = endptr + 1;
37	minor = strtol(cp, &endptr, 10);
38	if (cp == endptr || *endptr != '.')
39		return 0;
40	cp = endptr + 1;
41	rev = strtol(cp, &endptr, 10);
42	if (cp == endptr)
43		return 0;
44	return ((((major * 256) + minor) * 256) + rev);
45}
46
47#define VERSION_CODE(a,b,c) (((a) << 16) + ((b) << 8) + (c))
48
49#endif
50
51errcode_t online_resize_fs(ext2_filsys fs, const char *mtpt,
52			   blk64_t *new_size, int flags EXT2FS_ATTR((unused)))
53{
54#ifdef __linux__
55	struct ext2_new_group_input input;
56	struct ext4_new_group_input input64;
57	struct ext2_super_block *sb = fs->super;
58	unsigned long		new_desc_blocks;
59	ext2_filsys 		new_fs;
60	errcode_t 		retval;
61	double			percent;
62	dgrp_t			i;
63	blk_t			size;
64	int			fd, overhead;
65	int			use_old_ioctl = 1;
66	int			no_meta_bg_resize = 0;
67	int			no_resize_ioctl = 0;
68
69	if (getenv("RESIZE2FS_KERNEL_VERSION")) {
70		char *version_to_emulate = getenv("RESIZE2FS_KERNEL_VERSION");
71		int kvers = parse_version_number(version_to_emulate);
72
73		if (kvers < VERSION_CODE(3, 7, 0))
74			no_meta_bg_resize = 1;
75		if (kvers < VERSION_CODE(3, 3, 0))
76			no_resize_ioctl = 1;
77	}
78
79	printf(_("Filesystem at %s is mounted on %s; "
80		 "on-line resizing required\n"), fs->device_name, mtpt);
81
82	if (*new_size < ext2fs_blocks_count(sb)) {
83		com_err(program_name, 0, _("On-line shrinking not supported"));
84		exit(1);
85	}
86
87	/*
88	 * If the number of descriptor blocks is going to increase,
89	 * the on-line resizing inode must be present.
90	 */
91	new_desc_blocks = ext2fs_div_ceil(
92		ext2fs_div64_ceil(*new_size -
93				  fs->super->s_first_data_block,
94				  EXT2_BLOCKS_PER_GROUP(fs->super)),
95		EXT2_DESC_PER_BLOCK(fs->super));
96	printf("old_desc_blocks = %lu, new_desc_blocks = %lu\n",
97	       fs->desc_blocks, new_desc_blocks);
98
99	/*
100	 * Do error checking to make sure the resize will be successful.
101	 */
102	if ((access("/sys/fs/ext4/features/meta_bg_resize", R_OK) != 0) ||
103	    no_meta_bg_resize) {
104		if (!EXT2_HAS_COMPAT_FEATURE(fs->super,
105					EXT2_FEATURE_COMPAT_RESIZE_INODE) &&
106		    (new_desc_blocks != fs->desc_blocks)) {
107			com_err(program_name, 0,
108				_("Filesystem does not support online resizing"));
109			exit(1);
110		}
111
112		if (EXT2_HAS_COMPAT_FEATURE(fs->super,
113					EXT2_FEATURE_COMPAT_RESIZE_INODE) &&
114		    new_desc_blocks > (fs->desc_blocks +
115				       fs->super->s_reserved_gdt_blocks)) {
116			com_err(program_name, 0,
117				_("Not enough reserved gdt blocks for resizing"));
118			exit(1);
119		}
120
121		if ((ext2fs_blocks_count(sb) > MAX_32_NUM) ||
122		    (*new_size > MAX_32_NUM)) {
123			com_err(program_name, 0,
124				_("Kernel does not support resizing a file system this large"));
125			exit(1);
126		}
127	}
128
129	fd = open(mtpt, O_RDONLY);
130	if (fd < 0) {
131		com_err(program_name, errno,
132			_("while trying to open mountpoint %s"), mtpt);
133		exit(1);
134	}
135
136	if (no_resize_ioctl) {
137		printf(_("Old resize interface requested.\n"));
138	} else if (ioctl(fd, EXT4_IOC_RESIZE_FS, new_size)) {
139		/*
140		 * If kernel does not support EXT4_IOC_RESIZE_FS, use the
141		 * old online resize. Note that the old approach does not
142		 * handle >32 bit file systems
143		 *
144		 * Sigh, if we are running a 32-bit binary on a 64-bit
145		 * kernel (which happens all the time on the MIPS
146		 * architecture in Debian, but can happen on other CPU
147		 * architectures as well) we will get EINVAL returned
148		 * when an ioctl doesn't exist, at least up to Linux
149		 * 3.1.  See compat_sys_ioctl() in fs/compat_ioctl.c
150		 * in the kernel sources.  This is probably a kernel
151		 * bug, but work around it here.
152		 */
153		if ((errno != ENOTTY) && (errno != EINVAL)) {
154			if (errno == EPERM)
155				com_err(program_name, 0,
156				_("Permission denied to resize filesystem"));
157			else
158				com_err(program_name, errno,
159				_("While checking for on-line resizing "
160				  "support"));
161			exit(1);
162		}
163	} else {
164		close(fd);
165		return 0;
166	}
167
168	size = ext2fs_blocks_count(sb);
169
170	if (ioctl(fd, EXT2_IOC_GROUP_EXTEND, &size)) {
171		if (errno == EPERM)
172			com_err(program_name, 0,
173				_("Permission denied to resize filesystem"));
174		else if (errno == ENOTTY)
175			com_err(program_name, 0,
176			_("Kernel does not support online resizing"));
177		else
178			com_err(program_name, errno,
179			_("While checking for on-line resizing support"));
180		exit(1);
181	}
182
183	percent = (ext2fs_r_blocks_count(sb) * 100.0) /
184		ext2fs_blocks_count(sb);
185
186	retval = ext2fs_read_bitmaps(fs);
187	if (retval)
188		return retval;
189
190	retval = ext2fs_dup_handle(fs, &new_fs);
191	if (retval)
192		return retval;
193
194	/* The current method of adding one block group at a time to a
195	 * mounted filesystem means it is impossible to accomodate the
196	 * flex_bg allocation method of placing the metadata together
197	 * in a single block group.  For now we "fix" this issue by
198	 * using the traditional layout for new block groups, where
199	 * each block group is self-contained and contains its own
200	 * bitmap blocks and inode tables.  This means we don't get
201	 * the layout advantages of flex_bg in the new block groups,
202	 * but at least it allows on-line resizing to function.
203	 */
204	new_fs->super->s_feature_incompat &= ~EXT4_FEATURE_INCOMPAT_FLEX_BG;
205	retval = adjust_fs_info(new_fs, fs, 0, *new_size);
206	if (retval)
207		return retval;
208
209	printf(_("Performing an on-line resize of %s to %llu (%dk) blocks.\n"),
210	       fs->device_name, *new_size, fs->blocksize / 1024);
211
212	size = fs->group_desc_count * sb->s_blocks_per_group +
213		sb->s_first_data_block;
214	if (size > *new_size)
215		size = *new_size;
216
217	if (ioctl(fd, EXT2_IOC_GROUP_EXTEND, &size)) {
218		com_err(program_name, errno,
219			_("While trying to extend the last group"));
220		exit(1);
221	}
222
223	for (i = fs->group_desc_count;
224	     i < new_fs->group_desc_count; i++) {
225
226		overhead = (int) (2 + new_fs->inode_blocks_per_group);
227
228		if (ext2fs_bg_has_super(new_fs, new_fs->group_desc_count - 1))
229			overhead += 1 + new_fs->desc_blocks +
230				new_fs->super->s_reserved_gdt_blocks;
231
232		input.group = i;
233		input.block_bitmap = ext2fs_block_bitmap_loc(new_fs, i);
234		input.inode_bitmap = ext2fs_inode_bitmap_loc(new_fs, i);
235		input.inode_table = ext2fs_inode_table_loc(new_fs, i);
236		input.blocks_count = ext2fs_group_blocks_count(new_fs, i);
237		input.reserved_blocks = (blk_t) (percent * input.blocks_count
238						 / 100.0);
239
240#if 0
241		printf("new block bitmap is at 0x%04x\n", input.block_bitmap);
242		printf("new inode bitmap is at 0x%04x\n", input.inode_bitmap);
243		printf("new inode table is at 0x%04x-0x%04x\n",
244		       input.inode_table,
245		       input.inode_table + new_fs->inode_blocks_per_group-1);
246		printf("new group has %u blocks\n", input.blocks_count);
247		printf("new group will reserve %d blocks\n",
248		       input.reserved_blocks);
249		printf("new group has %d free blocks\n",
250		       ext2fs_bg_free_blocks_count(new_fs, i),
251		printf("new group has %d free inodes (%d blocks)\n",
252		       ext2fs_bg_free_inodes_count(new_fs, i),
253		       new_fs->inode_blocks_per_group);
254		printf("Adding group #%d\n", input.group);
255#endif
256
257		if (use_old_ioctl &&
258		    ioctl(fd, EXT2_IOC_GROUP_ADD, &input) == 0)
259			continue;
260		else
261			use_old_ioctl = 0;
262
263		input64.group = input.group;
264		input64.block_bitmap = input.block_bitmap;
265		input64.inode_bitmap = input.inode_bitmap;
266		input64.inode_table = input.inode_table;
267		input64.blocks_count = input.blocks_count;
268		input64.reserved_blocks = input.reserved_blocks;
269		input64.unused = input.unused;
270
271		if (ioctl(fd, EXT4_IOC_GROUP_ADD, &input64) < 0) {
272			com_err(program_name, errno,
273				_("While trying to add group #%d"),
274				input.group);
275			exit(1);
276		}
277	}
278
279	ext2fs_free(new_fs);
280	close(fd);
281
282	return 0;
283#else
284	printf(_("Filesystem at %s is mounted on %s, and on-line resizing is "
285		 "not supported on this system.\n"), fs->device_name, mtpt);
286	exit(1);
287#endif
288}
289