1 /*
2  * mk_hugefiles.c -- create huge files
3  */
4 
5 #define _XOPEN_SOURCE 600 /* for inclusion of PATH_MAX in Solaris */
6 #define _BSD_SOURCE	  /* for makedev() and major() */
7 #define _DEFAULT_SOURCE	  /* since glibc 2.20 _BSD_SOURCE is deprecated */
8 
9 #include "config.h"
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <string.h>
13 #include <strings.h>
14 #include <fcntl.h>
15 #include <ctype.h>
16 #include <time.h>
17 #ifdef __linux__
18 #include <sys/utsname.h>
19 #endif
20 #ifdef HAVE_GETOPT_H
21 #include <getopt.h>
22 #else
23 extern char *optarg;
24 extern int optind;
25 #endif
26 #ifdef HAVE_UNISTD_H
27 #include <unistd.h>
28 #endif
29 #ifdef HAVE_STDLIB_H
30 #include <stdlib.h>
31 #endif
32 #ifdef HAVE_ERRNO_H
33 #include <errno.h>
34 #endif
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <libgen.h>
39 #include <limits.h>
40 #include <blkid/blkid.h>
41 
42 #include "ext2fs/ext2_fs.h"
43 #include "ext2fs/ext2fsP.h"
44 #include "et/com_err.h"
45 #include "uuid/uuid.h"
46 #include "e2p/e2p.h"
47 #include "ext2fs/ext2fs.h"
48 #include "util.h"
49 #include "support/profile.h"
50 #include "support/prof_err.h"
51 #include "support/nls-enable.h"
52 #include "mke2fs.h"
53 
54 static int uid;
55 static int gid;
56 static blk64_t num_blocks;
57 static blk64_t num_slack;
58 static unsigned long num_files;
59 static blk64_t goal;
60 static char *fn_prefix;
61 static int idx_digits;
62 static char *fn_buf;
63 static char *fn_numbuf;
64 int zero_hugefile = 1;
65 
66 #define SYSFS_PATH_LEN 256
67 typedef char sysfs_path_t[SYSFS_PATH_LEN];
68 
69 #ifndef HAVE_SNPRINTF
70 /*
71  * We are very careful to avoid needing to worry about buffer
72  * overflows, so we don't really need to use snprintf() except as an
73  * additional safety check.  So if snprintf() is not present, it's
74  * safe to fall back to vsprintf().  This provides portability since
75  * vsprintf() is guaranteed by C89, while snprintf() is only
76  * guaranteed by C99 --- which for example, Microsoft Visual Studio
77  * has *still* not bothered to implement.  :-/  (Not that I expect
78  * mke2fs to be ported to MS Visual Studio any time soon, but
79  * libext2fs *does* get built on Microsoft platforms, and we might
80  * want to move this into libext2fs some day.)
81  */
my_snprintf(char * str,size_t size,const char * format,...)82 static int my_snprintf(char *str, size_t size, const char *format, ...)
83 {
84 	va_list	ap;
85 	int ret;
86 
87 	va_start(ap, format);
88 	ret = vsprintf(str, format, ap);
89 	va_end(ap);
90 	return ret;
91 }
92 
93 #define snprintf my_snprintf
94 #endif
95 
96 /*
97  * Fall back to Linux's definitions of makedev and major are needed.
98  * The search_sysfs_block() function is highly unlikely to work on
99  * non-Linux systems anyway.
100  */
101 #ifndef makedev
102 #define makedev(maj, min) (((maj) << 8) + (min))
103 #endif
104 
search_sysfs_block(dev_t devno,sysfs_path_t ret_path)105 static char *search_sysfs_block(dev_t devno, sysfs_path_t ret_path)
106 {
107 	struct dirent	*de, *p_de;
108 	DIR		*dir = NULL, *p_dir = NULL;
109 	FILE		*f;
110 	sysfs_path_t	path, p_path;
111 	unsigned int	major, minor;
112 	char		*ret = ret_path;
113 
114 	ret_path[0] = 0;
115 	if ((dir = opendir("/sys/block")) == NULL)
116 		return NULL;
117 	while ((de = readdir(dir)) != NULL) {
118 		if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..") ||
119 		    strlen(de->d_name) > sizeof(path)-32)
120 			continue;
121 		snprintf(path, SYSFS_PATH_LEN,
122 			 "/sys/block/%s/dev", de->d_name);
123 		f = fopen(path, "r");
124 		if (f &&
125 		    (fscanf(f, "%u:%u", &major, &minor) == 2)) {
126 			fclose(f); f = NULL;
127 			if (makedev(major, minor) == devno) {
128 				snprintf(ret_path, SYSFS_PATH_LEN,
129 					 "/sys/block/%s", de->d_name);
130 				goto success;
131 			}
132 #ifdef major
133 			if (major(devno) != major)
134 				continue;
135 #endif
136 		}
137 		if (f)
138 			fclose(f);
139 
140 		snprintf(path, SYSFS_PATH_LEN, "/sys/block/%s", de->d_name);
141 
142 		if (p_dir)
143 			closedir(p_dir);
144 		if ((p_dir = opendir(path)) == NULL)
145 			continue;
146 		while ((p_de = readdir(p_dir)) != NULL) {
147 			if (!strcmp(p_de->d_name, ".") ||
148 			    !strcmp(p_de->d_name, "..") ||
149 			    (strlen(p_de->d_name) >
150 			     SYSFS_PATH_LEN - strlen(path) - 32))
151 				continue;
152 			snprintf(p_path, SYSFS_PATH_LEN, "%s/%s/dev",
153 				 path, p_de->d_name);
154 
155 			f = fopen(p_path, "r");
156 			if (f &&
157 			    (fscanf(f, "%u:%u", &major, &minor) == 2) &&
158 			    (((major << 8) + minor) == devno)) {
159 				fclose(f);
160 				snprintf(ret_path, SYSFS_PATH_LEN, "%s/%s",
161 					 path, p_de->d_name);
162 				goto success;
163 			}
164 			if (f)
165 				fclose(f);
166 		}
167 	}
168 	ret = NULL;
169 success:
170 	if (dir)
171 		closedir(dir);
172 	if (p_dir)
173 		closedir(p_dir);
174 	return ret;
175 }
176 
get_partition_start(const char * device_name)177 static blk64_t get_partition_start(const char *device_name)
178 {
179 	unsigned long long start;
180 	sysfs_path_t	path;
181 	struct stat	st;
182 	FILE		*f;
183 	char		*cp;
184 	int		n;
185 
186 	if ((stat(device_name, &st) < 0) || !S_ISBLK(st.st_mode))
187 		return 0;
188 
189 	cp = search_sysfs_block(st.st_rdev, path);
190 	if (!cp)
191 		return 0;
192 	if (strlen(path) > SYSFS_PATH_LEN - sizeof("/start"))
193 		return 0;
194 	strcat(path, "/start");
195 	f = fopen(path, "r");
196 	if (!f)
197 		return 0;
198 	n = fscanf(f, "%llu", &start);
199 	fclose(f);
200 	return (n == 1) ? start : 0;
201 }
202 
create_directory(ext2_filsys fs,char * dir,ext2_ino_t * ret_ino)203 static errcode_t create_directory(ext2_filsys fs, char *dir,
204 				  ext2_ino_t *ret_ino)
205 
206 {
207 	struct ext2_inode	inode;
208 	ext2_ino_t		ino = EXT2_ROOT_INO;
209 	ext2_ino_t		newdir;
210 	errcode_t		retval = 0;
211 	char			*fn, *cp, *next;
212 
213 	fn = malloc(strlen(dir) + 1);
214 	if (fn == NULL)
215 		return ENOMEM;
216 
217 	strcpy(fn, dir);
218 	cp = fn;
219 	while(1) {
220 		next = strchr(cp, '/');
221 		if (next)
222 			*next++ = 0;
223 		if (*cp) {
224 			retval = ext2fs_new_inode(fs, ino, LINUX_S_IFDIR,
225 						  NULL, &newdir);
226 			if (retval)
227 				goto errout;
228 
229 			retval = ext2fs_mkdir(fs, ino, newdir, cp);
230 			if (retval)
231 				goto errout;
232 
233 			ino = newdir;
234 			retval = ext2fs_read_inode(fs, ino, &inode);
235 			if (retval)
236 				goto errout;
237 
238 			inode.i_uid = uid & 0xFFFF;
239 			ext2fs_set_i_uid_high(inode, (uid >> 16) & 0xffff);
240 			inode.i_gid = gid & 0xFFFF;
241 			ext2fs_set_i_gid_high(inode, (gid >> 16) & 0xffff);
242 			retval = ext2fs_write_inode(fs, ino, &inode);
243 			if (retval)
244 				goto errout;
245 		}
246 		if (next == NULL || *next == '\0')
247 			break;
248 		cp = next;
249 	}
250 errout:
251 	free(fn);
252 	if (retval == 0)
253 		*ret_ino = ino;
254 	return retval;
255 }
256 
mk_hugefile(ext2_filsys fs,blk64_t num,ext2_ino_t dir,unsigned long idx,ext2_ino_t * ino)257 static errcode_t mk_hugefile(ext2_filsys fs, blk64_t num,
258 			     ext2_ino_t dir, unsigned long idx, ext2_ino_t *ino)
259 
260 {
261 	errcode_t		retval;
262 	struct ext2_inode	inode;
263 
264 	retval = ext2fs_new_inode(fs, 0, LINUX_S_IFREG, NULL, ino);
265 	if (retval)
266 		return retval;
267 
268 	memset(&inode, 0, sizeof(struct ext2_inode));
269 	inode.i_mode = LINUX_S_IFREG | (0666 & ~fs->umask);
270 	inode.i_links_count = 1;
271 	inode.i_uid = uid & 0xFFFF;
272 	ext2fs_set_i_uid_high(inode, (uid >> 16) & 0xffff);
273 	inode.i_gid = gid & 0xFFFF;
274 	ext2fs_set_i_gid_high(inode, (gid >> 16) & 0xffff);
275 
276 	retval = ext2fs_write_new_inode(fs, *ino, &inode);
277 	if (retval)
278 		return retval;
279 
280 	ext2fs_inode_alloc_stats2(fs, *ino, +1, 0);
281 
282 	if (ext2fs_has_feature_extents(fs->super))
283 		inode.i_flags |= EXT4_EXTENTS_FL;
284 	retval = ext2fs_fallocate(fs,
285 				  EXT2_FALLOCATE_FORCE_INIT |
286 				  EXT2_FALLOCATE_ZERO_BLOCKS,
287 				  *ino, &inode, goal, 0, num);
288 	if (retval)
289 		return retval;
290 	retval = ext2fs_inode_size_set(fs, &inode, num * fs->blocksize);
291 	if (retval)
292 		return retval;
293 
294 	retval = ext2fs_write_inode(fs, *ino, &inode);
295 	if (retval)
296 		goto errout;
297 
298 	if (idx_digits)
299 		sprintf(fn_numbuf, "%0*lu", idx_digits, idx);
300 	else if (num_files > 1)
301 		sprintf(fn_numbuf, "%lu", idx);
302 
303 retry:
304 	retval = ext2fs_link(fs, dir, fn_buf, *ino, EXT2_FT_REG_FILE);
305 	if (retval == EXT2_ET_DIR_NO_SPACE) {
306 		retval = ext2fs_expand_dir(fs, dir);
307 		if (retval)
308 			goto errout;
309 		goto retry;
310 	}
311 
312 errout:
313 	return retval;
314 }
315 
calc_overhead(ext2_filsys fs,blk64_t num)316 static blk64_t calc_overhead(ext2_filsys fs, blk64_t num)
317 {
318 	blk64_t e_blocks, e_blocks2, e_blocks3, e_blocks4;
319 	int extents_per_block;
320 	int extents = (num + EXT_INIT_MAX_LEN - 1) / EXT_INIT_MAX_LEN;
321 
322 	if (extents <= 4)
323 		return 0;
324 
325 	/*
326 	 * This calculation is due to the fact that we are inefficient
327 	 * in how handle extent splits when appending to the end of
328 	 * the extent tree.  Sigh.  We should fix this so that we can
329 	 * actually store 340 extents per 4k block, instead of only 170.
330 	 */
331 	extents_per_block = ((fs->blocksize -
332 			      sizeof(struct ext3_extent_header)) /
333 			     sizeof(struct ext3_extent));
334 	extents_per_block = (extents_per_block/ 2) - 1;
335 
336 	e_blocks = (extents + extents_per_block - 1) / extents_per_block;
337 	e_blocks2 = (e_blocks + extents_per_block - 1) / extents_per_block;
338 	e_blocks3 = (e_blocks2 + extents_per_block - 1) / extents_per_block;
339 	e_blocks4 = (e_blocks3 + extents_per_block - 1) / extents_per_block;
340 	return e_blocks + e_blocks2 + e_blocks3 + e_blocks4;
341 }
342 
343 /*
344  * Find the place where we should start allocating blocks for the huge
345  * files.  Leave <slack> free blocks at the beginning of the file
346  * system for things like metadata blocks.
347  */
get_start_block(ext2_filsys fs,blk64_t slack)348 static blk64_t get_start_block(ext2_filsys fs, blk64_t slack)
349 {
350 	errcode_t retval;
351 	blk64_t blk = fs->super->s_first_data_block, next;
352 	blk64_t last_blk = ext2fs_blocks_count(fs->super) - 1;
353 
354 	while (slack) {
355 		retval = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
356 						blk, last_blk, &blk);
357 		if (retval)
358 			break;
359 
360 		retval = ext2fs_find_first_set_block_bitmap2(fs->block_map,
361 						blk, last_blk, &next);
362 		if (retval)
363 			next = last_blk;
364 
365 		if (next - blk > slack) {
366 			blk += slack;
367 			break;
368 		}
369 
370 		slack -= (next - blk);
371 		blk = next;
372 	}
373 	return blk;
374 }
375 
round_up_align(blk64_t b,unsigned long align,blk64_t part_offset)376 static blk64_t round_up_align(blk64_t b, unsigned long align,
377 			      blk64_t part_offset)
378 {
379 	unsigned long m;
380 
381 	if (align == 0)
382 		return b;
383 	part_offset = part_offset % align;
384 	m = (b + part_offset) % align;
385 	if (m)
386 		b += align - m;
387 	return b;
388 }
389 
mk_hugefiles(ext2_filsys fs,const char * device_name)390 errcode_t mk_hugefiles(ext2_filsys fs, const char *device_name)
391 {
392 	unsigned long	i;
393 	ext2_ino_t	dir;
394 	errcode_t	retval;
395 	blk64_t		fs_blocks, part_offset = 0;
396 	unsigned long	align;
397 	int		d, dsize;
398 	char		*t;
399 
400 	if (!get_bool_from_profile(fs_types, "make_hugefiles", 0))
401 		return 0;
402 
403 	if (!ext2fs_has_feature_extents(fs->super))
404 		return EXT2_ET_EXTENT_NOT_SUPPORTED;
405 
406 	uid = get_int_from_profile(fs_types, "hugefiles_uid", 0);
407 	gid = get_int_from_profile(fs_types, "hugefiles_gid", 0);
408 	fs->umask = get_int_from_profile(fs_types, "hugefiles_umask", 077);
409 	num_files = get_int_from_profile(fs_types, "num_hugefiles", 0);
410 	t = get_string_from_profile(fs_types, "hugefiles_slack", "1M");
411 	num_slack = parse_num_blocks2(t, fs->super->s_log_block_size);
412 	free(t);
413 	t = get_string_from_profile(fs_types, "hugefiles_size", "0");
414 	num_blocks = parse_num_blocks2(t, fs->super->s_log_block_size);
415 	free(t);
416 	t = get_string_from_profile(fs_types, "hugefiles_align", "0");
417 	align = parse_num_blocks2(t, fs->super->s_log_block_size);
418 	free(t);
419 	if (get_bool_from_profile(fs_types, "hugefiles_align_disk", 0)) {
420 		part_offset = get_partition_start(device_name) /
421 			(fs->blocksize / 512);
422 		if (part_offset % EXT2FS_CLUSTER_RATIO(fs)) {
423 			fprintf(stderr,
424 				_("Partition offset of %llu (%uk) blocks "
425 				  "not compatible with cluster size %u.\n"),
426 				part_offset, fs->blocksize,
427 				EXT2_CLUSTER_SIZE(fs->super));
428 			exit(1);
429 		}
430 	}
431 	num_blocks = round_up_align(num_blocks, align, 0);
432 	zero_hugefile = get_bool_from_profile(fs_types, "zero_hugefiles",
433 					      zero_hugefile);
434 
435 	t = get_string_from_profile(fs_types, "hugefiles_dir", "/");
436 	retval = create_directory(fs, t, &dir);
437 	free(t);
438 	if (retval)
439 		return retval;
440 
441 	fn_prefix = get_string_from_profile(fs_types, "hugefiles_name",
442 					    "hugefile");
443 	idx_digits = get_int_from_profile(fs_types, "hugefiles_digits", 5);
444 	d = int_log10(num_files) + 1;
445 	if (idx_digits > d)
446 		d = idx_digits;
447 	dsize = strlen(fn_prefix) + d + 16;
448 	fn_buf = malloc(dsize);
449 	if (!fn_buf) {
450 		free(fn_prefix);
451 		return ENOMEM;
452 	}
453 	strcpy(fn_buf, fn_prefix);
454 	fn_numbuf = fn_buf + strlen(fn_prefix);
455 	free(fn_prefix);
456 
457 	fs_blocks = ext2fs_free_blocks_count(fs->super);
458 	if (fs_blocks < num_slack + align)
459 		return ENOSPC;
460 	fs_blocks -= num_slack + align;
461 	if (num_blocks && num_blocks > fs_blocks)
462 		return ENOSPC;
463 	if (num_blocks == 0 && num_files == 0)
464 		num_files = 1;
465 
466 	if (num_files == 0 && num_blocks) {
467 		num_files = fs_blocks / num_blocks;
468 		fs_blocks -= (num_files / 16) + 1;
469 		fs_blocks -= calc_overhead(fs, num_blocks) * num_files;
470 		num_files = fs_blocks / num_blocks;
471 	}
472 
473 	if (num_blocks == 0 && num_files > 1) {
474 		num_blocks = fs_blocks / num_files;
475 		fs_blocks -= (num_files / 16) + 1;
476 		fs_blocks -= calc_overhead(fs, num_blocks) * num_files;
477 		num_blocks = fs_blocks / num_files;
478 	}
479 
480 	num_slack += calc_overhead(fs, num_blocks) * num_files;
481 	num_slack += (num_files / 16) + 1; /* space for dir entries */
482 	goal = get_start_block(fs, num_slack);
483 	goal = round_up_align(goal, align, part_offset);
484 
485 	if ((num_blocks ? num_blocks : fs_blocks) >
486 	    (0x80000000UL / fs->blocksize))
487 		ext2fs_set_feature_large_file(fs->super);
488 
489 	if (!quiet) {
490 		if (zero_hugefile && verbose)
491 			printf("%s", _("Huge files will be zero'ed\n"));
492 		printf(_("Creating %lu huge file(s) "), num_files);
493 		if (num_blocks)
494 			printf(_("with %llu blocks each"), num_blocks);
495 		fputs(": ", stdout);
496 	}
497 	if (num_blocks == 0)
498 		num_blocks = ext2fs_blocks_count(fs->super) - goal;
499 	for (i=0; i < num_files; i++) {
500 		ext2_ino_t ino;
501 
502 		retval = mk_hugefile(fs, num_blocks, dir, i, &ino);
503 		if (retval) {
504 			com_err(program_name, retval,
505 				_("while creating huge file %lu"), i);
506 			goto errout;
507 		}
508 	}
509 	if (!quiet)
510 		fputs(_("done\n"), stdout);
511 
512 errout:
513 	free(fn_buf);
514 	return retval;
515 }
516