/* * mk_hugefiles.c -- create huge files */ #define _XOPEN_SOURCE 600 /* for inclusion of PATH_MAX in Solaris */ #define _BSD_SOURCE /* for makedev() and major() */ #define _DEFAULT_SOURCE /* since glibc 2.20 _BSD_SOURCE is deprecated */ #include "config.h" #include <stdio.h> #include <stdarg.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <ctype.h> #include <time.h> #ifdef __linux__ #include <sys/utsname.h> #endif #ifdef HAVE_GETOPT_H #include <getopt.h> #else extern char *optarg; extern int optind; #endif #ifdef HAVE_UNISTD_H #include <unistd.h> #endif #ifdef HAVE_STDLIB_H #include <stdlib.h> #endif #ifdef HAVE_ERRNO_H #include <errno.h> #endif #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> #endif #include <sys/types.h> #include <sys/stat.h> #ifdef HAVE_SYS_SYSMACROS_H #include <sys/sysmacros.h> #endif #include <libgen.h> #include <limits.h> #include <blkid/blkid.h> #include "ext2fs/ext2_fs.h" #include "ext2fs/ext2fsP.h" #include "et/com_err.h" #include "uuid/uuid.h" #include "e2p/e2p.h" #include "ext2fs/ext2fs.h" #include "util.h" #include "support/profile.h" #include "support/prof_err.h" #include "support/nls-enable.h" #include "mke2fs.h" static int uid; static int gid; static blk64_t num_blocks; static blk64_t num_slack; static unsigned long num_files; static blk64_t goal; static char *fn_prefix; static int idx_digits; static char *fn_buf; static char *fn_numbuf; int zero_hugefile = 1; #define SYSFS_PATH_LEN 256 typedef char sysfs_path_t[SYSFS_PATH_LEN]; #ifndef HAVE_SNPRINTF /* * We are very careful to avoid needing to worry about buffer * overflows, so we don't really need to use snprintf() except as an * additional safety check. So if snprintf() is not present, it's * safe to fall back to vsprintf(). This provides portability since * vsprintf() is guaranteed by C89, while snprintf() is only * guaranteed by C99 --- which for example, Microsoft Visual Studio * has *still* not bothered to implement. :-/ (Not that I expect * mke2fs to be ported to MS Visual Studio any time soon, but * libext2fs *does* get built on Microsoft platforms, and we might * want to move this into libext2fs some day.) */ static int my_snprintf(char *str, size_t size, const char *format, ...) { va_list ap; int ret; va_start(ap, format); ret = vsprintf(str, format, ap); va_end(ap); return ret; } #define snprintf my_snprintf #endif /* * Fall back to Linux's definitions of makedev and major are needed. * The search_sysfs_block() function is highly unlikely to work on * non-Linux systems anyway. */ #ifndef makedev #define makedev(maj, min) (((maj) << 8) + (min)) #endif static char *search_sysfs_block(dev_t devno, sysfs_path_t ret_path) { struct dirent *de, *p_de; DIR *dir = NULL, *p_dir = NULL; FILE *f; sysfs_path_t path, p_path; unsigned int major, minor; char *ret = ret_path; ret_path[0] = 0; if ((dir = opendir("/sys/block")) == NULL) return NULL; while ((de = readdir(dir)) != NULL) { if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..") || strlen(de->d_name) > sizeof(path)-32) continue; snprintf(path, SYSFS_PATH_LEN, "/sys/block/%s/dev", de->d_name); f = fopen(path, "r"); if (f && (fscanf(f, "%u:%u", &major, &minor) == 2)) { fclose(f); f = NULL; if (makedev(major, minor) == devno) { snprintf(ret_path, SYSFS_PATH_LEN, "/sys/block/%s", de->d_name); goto success; } #ifdef major if (major(devno) != major) continue; #endif } if (f) fclose(f); snprintf(path, SYSFS_PATH_LEN, "/sys/block/%s", de->d_name); if (p_dir) closedir(p_dir); if ((p_dir = opendir(path)) == NULL) continue; while ((p_de = readdir(p_dir)) != NULL) { if (!strcmp(p_de->d_name, ".") || !strcmp(p_de->d_name, "..") || (strlen(p_de->d_name) > SYSFS_PATH_LEN - strlen(path) - 32)) continue; snprintf(p_path, SYSFS_PATH_LEN, "%s/%s/dev", path, p_de->d_name); f = fopen(p_path, "r"); if (f && (fscanf(f, "%u:%u", &major, &minor) == 2) && (((major << 8) + minor) == devno)) { fclose(f); snprintf(ret_path, SYSFS_PATH_LEN, "%s/%s", path, p_de->d_name); goto success; } if (f) fclose(f); } } ret = NULL; success: if (dir) closedir(dir); if (p_dir) closedir(p_dir); return ret; } static blk64_t get_partition_start(const char *device_name) { unsigned long long start; sysfs_path_t path; struct stat st; FILE *f; char *cp; int n; if ((stat(device_name, &st) < 0) || !S_ISBLK(st.st_mode)) return 0; cp = search_sysfs_block(st.st_rdev, path); if (!cp) return 0; if (strlen(path) > SYSFS_PATH_LEN - sizeof("/start")) return 0; strcat(path, "/start"); f = fopen(path, "r"); if (!f) return 0; n = fscanf(f, "%llu", &start); fclose(f); return (n == 1) ? start : 0; } static errcode_t create_directory(ext2_filsys fs, char *dir, ext2_ino_t *ret_ino) { struct ext2_inode inode; ext2_ino_t ino = EXT2_ROOT_INO; ext2_ino_t newdir; errcode_t retval = 0; char *fn, *cp, *next; fn = malloc(strlen(dir) + 1); if (fn == NULL) return ENOMEM; strcpy(fn, dir); cp = fn; while(1) { next = strchr(cp, '/'); if (next) *next++ = 0; if (*cp) { retval = ext2fs_new_inode(fs, ino, LINUX_S_IFDIR, NULL, &newdir); if (retval) goto errout; retval = ext2fs_mkdir(fs, ino, newdir, cp); if (retval) goto errout; ino = newdir; retval = ext2fs_read_inode(fs, ino, &inode); if (retval) goto errout; inode.i_uid = uid & 0xFFFF; ext2fs_set_i_uid_high(inode, (uid >> 16) & 0xffff); inode.i_gid = gid & 0xFFFF; ext2fs_set_i_gid_high(inode, (gid >> 16) & 0xffff); retval = ext2fs_write_inode(fs, ino, &inode); if (retval) goto errout; } if (next == NULL || *next == '\0') break; cp = next; } errout: free(fn); if (retval == 0) *ret_ino = ino; return retval; } static errcode_t mk_hugefile(ext2_filsys fs, blk64_t num, ext2_ino_t dir, unsigned long idx, ext2_ino_t *ino) { errcode_t retval; struct ext2_inode inode; retval = ext2fs_new_inode(fs, 0, LINUX_S_IFREG, NULL, ino); if (retval) return retval; memset(&inode, 0, sizeof(struct ext2_inode)); inode.i_mode = LINUX_S_IFREG | (0666 & ~fs->umask); inode.i_links_count = 1; inode.i_uid = uid & 0xFFFF; ext2fs_set_i_uid_high(inode, (uid >> 16) & 0xffff); inode.i_gid = gid & 0xFFFF; ext2fs_set_i_gid_high(inode, (gid >> 16) & 0xffff); retval = ext2fs_write_new_inode(fs, *ino, &inode); if (retval) return retval; ext2fs_inode_alloc_stats2(fs, *ino, +1, 0); if (ext2fs_has_feature_extents(fs->super)) inode.i_flags |= EXT4_EXTENTS_FL; retval = ext2fs_fallocate(fs, EXT2_FALLOCATE_FORCE_INIT | EXT2_FALLOCATE_ZERO_BLOCKS, *ino, &inode, goal, 0, num); if (retval) return retval; retval = ext2fs_inode_size_set(fs, &inode, num * fs->blocksize); if (retval) return retval; retval = ext2fs_write_inode(fs, *ino, &inode); if (retval) goto errout; if (idx_digits) sprintf(fn_numbuf, "%0*lu", idx_digits, idx); else if (num_files > 1) sprintf(fn_numbuf, "%lu", idx); retry: retval = ext2fs_link(fs, dir, fn_buf, *ino, EXT2_FT_REG_FILE); if (retval == EXT2_ET_DIR_NO_SPACE) { retval = ext2fs_expand_dir(fs, dir); if (retval) goto errout; goto retry; } errout: return retval; } static blk64_t calc_overhead(ext2_filsys fs, blk64_t num) { blk64_t e_blocks, e_blocks2, e_blocks3, e_blocks4; int extents_per_block; int extents = (num + EXT_INIT_MAX_LEN - 1) / EXT_INIT_MAX_LEN; if (extents <= 4) return 0; /* * This calculation is due to the fact that we are inefficient * in how handle extent splits when appending to the end of * the extent tree. Sigh. We should fix this so that we can * actually store 340 extents per 4k block, instead of only 170. */ extents_per_block = ((fs->blocksize - sizeof(struct ext3_extent_header)) / sizeof(struct ext3_extent)); extents_per_block = (extents_per_block/ 2) - 1; e_blocks = (extents + extents_per_block - 1) / extents_per_block; e_blocks2 = (e_blocks + extents_per_block - 1) / extents_per_block; e_blocks3 = (e_blocks2 + extents_per_block - 1) / extents_per_block; e_blocks4 = (e_blocks3 + extents_per_block - 1) / extents_per_block; return e_blocks + e_blocks2 + e_blocks3 + e_blocks4; } /* * Find the place where we should start allocating blocks for the huge * files. Leave <slack> free blocks at the beginning of the file * system for things like metadata blocks. */ static blk64_t get_start_block(ext2_filsys fs, blk64_t slack) { errcode_t retval; blk64_t blk = fs->super->s_first_data_block, next; blk64_t last_blk = ext2fs_blocks_count(fs->super) - 1; while (slack) { retval = ext2fs_find_first_zero_block_bitmap2(fs->block_map, blk, last_blk, &blk); if (retval) break; retval = ext2fs_find_first_set_block_bitmap2(fs->block_map, blk, last_blk, &next); if (retval) next = last_blk; if (next - blk > slack) { blk += slack; break; } slack -= (next - blk); blk = next; } return blk; } static blk64_t round_up_align(blk64_t b, unsigned long align, blk64_t part_offset) { unsigned long m; if (align == 0) return b; part_offset = part_offset % align; m = (b + part_offset) % align; if (m) b += align - m; return b; } errcode_t mk_hugefiles(ext2_filsys fs, const char *device_name) { unsigned long i; ext2_ino_t dir; errcode_t retval; blk64_t fs_blocks, part_offset = 0; unsigned long align; int d, dsize; char *t; if (!get_bool_from_profile(fs_types, "make_hugefiles", 0)) return 0; if (!ext2fs_has_feature_extents(fs->super)) return EXT2_ET_EXTENT_NOT_SUPPORTED; uid = get_int_from_profile(fs_types, "hugefiles_uid", 0); gid = get_int_from_profile(fs_types, "hugefiles_gid", 0); fs->umask = get_int_from_profile(fs_types, "hugefiles_umask", 077); num_files = get_int_from_profile(fs_types, "num_hugefiles", 0); t = get_string_from_profile(fs_types, "hugefiles_slack", "1M"); num_slack = parse_num_blocks2(t, fs->super->s_log_block_size); free(t); t = get_string_from_profile(fs_types, "hugefiles_size", "0"); num_blocks = parse_num_blocks2(t, fs->super->s_log_block_size); free(t); t = get_string_from_profile(fs_types, "hugefiles_align", "0"); align = parse_num_blocks2(t, fs->super->s_log_block_size); free(t); if (get_bool_from_profile(fs_types, "hugefiles_align_disk", 0)) { part_offset = get_partition_start(device_name) / (fs->blocksize / 512); if (part_offset % EXT2FS_CLUSTER_RATIO(fs)) { fprintf(stderr, _("Partition offset of %llu (%uk) blocks " "not compatible with cluster size %u.\n"), part_offset, fs->blocksize, EXT2_CLUSTER_SIZE(fs->super)); exit(1); } } num_blocks = round_up_align(num_blocks, align, 0); zero_hugefile = get_bool_from_profile(fs_types, "zero_hugefiles", zero_hugefile); t = get_string_from_profile(fs_types, "hugefiles_dir", "/"); retval = create_directory(fs, t, &dir); free(t); if (retval) return retval; fn_prefix = get_string_from_profile(fs_types, "hugefiles_name", "hugefile"); idx_digits = get_int_from_profile(fs_types, "hugefiles_digits", 5); d = int_log10(num_files) + 1; if (idx_digits > d) d = idx_digits; dsize = strlen(fn_prefix) + d + 16; fn_buf = malloc(dsize); if (!fn_buf) { free(fn_prefix); return ENOMEM; } strcpy(fn_buf, fn_prefix); fn_numbuf = fn_buf + strlen(fn_prefix); free(fn_prefix); fs_blocks = ext2fs_free_blocks_count(fs->super); if (fs_blocks < num_slack + align) return ENOSPC; fs_blocks -= num_slack + align; if (num_blocks && num_blocks > fs_blocks) return ENOSPC; if (num_blocks == 0 && num_files == 0) num_files = 1; if (num_files == 0 && num_blocks) { num_files = fs_blocks / num_blocks; fs_blocks -= (num_files / 16) + 1; fs_blocks -= calc_overhead(fs, num_blocks) * num_files; num_files = fs_blocks / num_blocks; } if (num_blocks == 0 && num_files > 1) { num_blocks = fs_blocks / num_files; fs_blocks -= (num_files / 16) + 1; fs_blocks -= calc_overhead(fs, num_blocks) * num_files; num_blocks = fs_blocks / num_files; } num_slack += calc_overhead(fs, num_blocks) * num_files; num_slack += (num_files / 16) + 1; /* space for dir entries */ goal = get_start_block(fs, num_slack); goal = round_up_align(goal, align, part_offset); if ((num_blocks ? num_blocks : fs_blocks) > (0x80000000UL / fs->blocksize)) ext2fs_set_feature_large_file(fs->super); if (!quiet) { if (zero_hugefile && verbose) printf("%s", _("Huge files will be zero'ed\n")); printf(_("Creating %lu huge file(s) "), num_files); if (num_blocks) printf(_("with %llu blocks each"), num_blocks); fputs(": ", stdout); } if (num_blocks == 0) num_blocks = ext2fs_blocks_count(fs->super) - goal; for (i=0; i < num_files; i++) { ext2_ino_t ino; retval = mk_hugefile(fs, num_blocks, dir, i, &ino); if (retval) { com_err(program_name, retval, _("while creating huge file %lu"), i); goto errout; } } if (!quiet) fputs(_("done\n"), stdout); errout: free(fn_buf); return retval; }