#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include uint64_t max_size(const char *path) { struct statvfs stat; int ret = statvfs(path, &stat); assert(ret == 0); return (uint64_t)stat.f_bsize * stat.f_bavail; } #define qemu_toupper(c) toupper((unsigned char)(c)) static int64_t suffix_mul(char suffix, int64_t unit) { switch (qemu_toupper(suffix)) { case 'B': return 1; case 'K': return unit; case 'M': return unit * unit; case 'G': return unit * unit * unit; case 'T': return unit * unit * unit * unit; case 'P': return unit * unit * unit * unit * unit; case 'E': return unit * unit * unit * unit * unit * unit; } return -1; } /* * Convert string to bytes, allowing either B/b for bytes, K/k for KB, * M/m for MB, G/g for GB or T/t for TB. End pointer will be returned * in *end, if not NULL. Return -ERANGE on overflow, Return -EINVAL on * other error. */ static int do_strtosz(const char *nptr, char **end, const char default_suffix, int64_t unit, uint64_t *result) { int retval; char *endptr; unsigned char c; int mul_required = 0; double val, mul, integral, fraction; errno = 0; val = strtod(nptr, &endptr); if (isnan(val) || endptr == nptr || errno != 0) { retval = -EINVAL; goto out; } fraction = modf(val, &integral); if (fraction != 0) { mul_required = 1; } c = *endptr; mul = suffix_mul(c, unit); if (mul >= 0) { endptr++; } else { mul = suffix_mul(default_suffix, unit); assert(mul >= 0); } if (mul == 1 && mul_required) { retval = -EINVAL; goto out; } /* * Values >= 0xfffffffffffffc00 overflow uint64_t after their trip * through double (53 bits of precision). */ if ((val * mul >= 0xfffffffffffffc00) || val < 0) { retval = -ERANGE; goto out; } *result = val * mul; retval = 0; out: if (end) { *end = endptr; } else if (*endptr) { retval = -EINVAL; } return retval; } int qemu_strtosz(const char *nptr, char **end, uint64_t *result) { return do_strtosz(nptr, end, 'B', 1024, result); } static int64_t cvtnum(const char *s) { int err; uint64_t value; err = qemu_strtosz(s, NULL, &value); if (err < 0) { return err; } if (value > INT64_MAX) { return -ERANGE; } return value; } int balloon_fd; int create_balloon(const char *filename) { int fd = open(filename, O_RDWR); uint64_t sz = max_size(filename); printf("%ld\n", sz); } int main(int argc, char *argv[]) { int ret = 0; int fd = -1; struct statvfs stat; const char *filename = argv[1]; int64_t len = cvtnum(argv[2]), disk_len, off; char *dir; int64_t block = 64 * 1024, try; int64_t flen; char *buf[block]; dir = strdup(filename); dirname(dir); ret = statvfs(dir, &stat); if (ret < 0) { perror("Can not get fs stats"); goto fail; } disk_len = (uint64_t)stat.f_bsize * stat.f_bavail; if (disk_len < len) { perror("Not enough space on disk"); goto fail; } fd = open(filename, O_CREAT | O_RDWR); if (fd < 0) { perror("Can not open file for RW"); goto fail; } sync(); printf("fallocating max space. You can monitor progress by 'watch -d \"df -h %s\"'\n", dir); flen = len; try = (disk_len - 10 * block) / 2 / block * block; while (try >= block) { ret = statvfs(dir, &stat); if (ret < 0) { perror("Can not get fs stats"); goto fail; } disk_len = (uint64_t)stat.f_bsize * stat.f_bavail; try = disk_len / 2 / block * block; if (try < 10 * block) { break; } ret = fallocate(fd, 0, flen, try); if (ret >= 0) { flen += try; } else { break; } } ret = statvfs(dir, &stat); if (ret < 0) { perror("Can not get fs stats"); goto fail; } disk_len = (uint64_t)stat.f_bsize * stat.f_bavail; printf("done (remaining space %ld).\n\n", disk_len); printf("reallocating final blocks:\n"); for (off = 0; off < len; off += block) { while (fallocate(fd, 0, off, block) < 0) { ret = ftruncate(fd, flen - block); if (ret < 0) { error(0, errno, "Failed to punch hole for block %ld", off / block); goto fail; } flen -= block; } lseek(fd, off, SEEK_SET); write(fd, buf, block); printf("done bytes: %ld/%ld \r", off, len); } printf("done. \n\n"); printf("truncating file to final size\n"); ret = ftruncate(fd, len); if (ret < 0) { perror("Failed to truncate to final size"); goto fail; } fail: free(dir); if (fd >= 0) { close(fd); if (ret < 0) { unlink(filename); } } return -ret; }