e2fsprogs/misc/mk_hugefiles.c
Theodore Ts'o f6c50d68cc mke2fs: fix bugs in hugefile creation
For certain sizes mke2fs's hugefile creation would fail with the error:

mke2fs: Could not allocate block in ext2 filesystem while creating huge file 0

This would happen because we had failed to reserve enough space for
the metadata blocks for the hugefile.  There were two problems:

1) The overhead calculation function failed to take into account the
cluster size for bigalloc file systems.

2) In the case where num_blocks is 0 and num_files is 1, the overhead
calculation function was passed a size of 0, which caused the
calculated overhead to be zero.

Google-Bug-Id: 123239032
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
2019-01-24 23:18:11 -05:00

606 lines
15 KiB
C

/*
* 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 300
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;
blk64_t lblk, bend = 0;
__u64 size;
blk64_t left;
blk64_t count = 0;
struct ext2_inode inode;
ext2_extent_handle_t handle;
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);
retval = ext2fs_extent_open2(fs, *ino, &inode, &handle);
if (retval)
return retval;
/*
* We don't use ext2fs_fallocate() here because hugefiles are
* designed to be physically contiguous (if the block group
* descriptors are configured to be in a single block at the
* beginning of the file system, by using the
* packed_meta_blocks layout), with the extent tree blocks
* allocated near the beginning of the file system.
*/
lblk = 0;
left = num ? num : 1;
while (left) {
blk64_t pblk, end;
blk64_t n = left;
retval = ext2fs_find_first_zero_block_bitmap2(fs->block_map,
goal, ext2fs_blocks_count(fs->super) - 1, &end);
if (retval)
goto errout;
goal = end;
retval = ext2fs_find_first_set_block_bitmap2(fs->block_map, goal,
ext2fs_blocks_count(fs->super) - 1, &bend);
if (retval == ENOENT) {
bend = ext2fs_blocks_count(fs->super);
if (num == 0)
left = 0;
}
if (!num || bend - goal < left)
n = bend - goal;
pblk = goal;
if (num)
left -= n;
goal += n;
count += n;
ext2fs_block_alloc_stats_range(fs, pblk, n, +1);
if (zero_hugefile) {
blk64_t ret_blk;
retval = ext2fs_zero_blocks2(fs, pblk, n,
&ret_blk, NULL);
if (retval)
com_err(program_name, retval,
_("while zeroing block %llu "
"for hugefile"), ret_blk);
}
while (n) {
blk64_t l = n;
struct ext2fs_extent newextent;
if (l > EXT_INIT_MAX_LEN)
l = EXT_INIT_MAX_LEN;
newextent.e_len = l;
newextent.e_pblk = pblk;
newextent.e_lblk = lblk;
newextent.e_flags = 0;
retval = ext2fs_extent_insert(handle,
EXT2_EXTENT_INSERT_AFTER, &newextent);
if (retval)
return retval;
pblk += l;
lblk += l;
n -= l;
}
}
retval = ext2fs_read_inode(fs, *ino, &inode);
if (retval)
goto errout;
retval = ext2fs_iblk_add_blocks(fs, &inode,
count / EXT2FS_CLUSTER_RATIO(fs));
if (retval)
goto errout;
size = (__u64) count * fs->blocksize;
retval = ext2fs_inode_size_set(fs, &inode, size);
if (retval)
goto errout;
retval = ext2fs_write_new_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;
}
if (retval)
goto errout;
errout:
if (handle)
ext2fs_extent_free(handle);
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) *
EXT2FS_CLUSTER_RATIO(fs);
}
/*
* 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_blocks : fs_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);
}
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;
}