-
-
Save zevweiss/2cba14070b62c979f2ceaf0ca3b66a13 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* cheesegrater -- reduce a filesystem's free space to shreds. | |
* | |
* Basic strategy: fill the FS with one big file (write until ENOSPC), then hole-punch every | |
* other block in the file. Assuming the filesystem allocates space reasonably contiguously | |
* (and doesn't reshuffle anything during hole-punching), this should produce ~50% free space | |
* in a minimally contiguous form. | |
* | |
* I wrote this a few years ago (circa 2016 I think) basically just to see how different | |
* filesystems would handle it. From what I can recall: | |
* | |
* - ext4 did about as well as could be expected | |
* - btrfs failed (don't remember exactly how) before the hole-punching phase even completed | |
* - xfs ended up in the expected 50%-free state, but then failed all subsequent file-creation | |
* attempts with ENOSPC (because it wants to allocate inodes in 16KB clusters, and there weren't | |
* any contiguous chunks that large remaining) | |
* | |
* (Caveats on the above: (a) I may be misremembering some details, and (b) filesystems are | |
* still in active development and their behavior may have changed in the last few years.) | |
* | |
* Options to tweak the default behavior described above: | |
* - -b BLKSIZE: override the blocksize retrieved via fstatvfs(2) | |
* - -p NBLKS: number of blocks in each hole-punch (default 1) | |
* - -s NBLKS: number of blocks to skip between hole-punches (default 1) | |
* - -z SIZE: restrict file-size to SIZE instead of filling all available space | |
*/ | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <unistd.h> | |
#include <getopt.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/statvfs.h> | |
#include <linux/falloc.h> | |
static unsigned long blksize = 0; | |
static long punch_len = 1, skip_len = 1; | |
static off_t filesize = -1; | |
#define fperror(fmt, ...) do { \ | |
fprintf(stderr, fmt, ##__VA_ARGS__); \ | |
fprintf(stderr, ": %m\n"); \ | |
} while (0) | |
static int strict_strtol(const char* str, int base, long* val) | |
{ | |
char* end; | |
*val = strtol(str, &end, base); | |
return (*str && !*end) ? 0 : -1; | |
} | |
static long parse_number(const char* s) | |
{ | |
long tmp; | |
if (strict_strtol(s, 0, &tmp)) { | |
fprintf(stderr, "'%s' is not a valid number\n", s); | |
exit(1); | |
} | |
return tmp; | |
} | |
static off_t parse_size(const char* s) | |
{ | |
char* end; | |
long shift, n = strtol(s, &end, 0); | |
if (!*s || (end[0] && end[1])) { | |
fprintf(stderr, "'%s' is not a valid size\n", s); | |
exit(1); | |
} | |
switch (toupper(*end)) { | |
case '\0': | |
shift = 0; | |
break; | |
case 'K': | |
shift = 10; | |
break; | |
case 'M': | |
shift = 20; | |
break; | |
case 'G': | |
shift = 30; | |
break; | |
case 'T': | |
shift = 40; | |
break; | |
default: | |
fprintf(stderr, "unrecognized unit suffix '%c'\n", *end); | |
exit(1); | |
} | |
return n << shift; | |
} | |
static char iobuf[1 << 19]; | |
static void cheesegrate(int fd) | |
{ | |
ssize_t status; | |
size_t towrite; | |
unsigned long p; | |
off_t written = 0; | |
memset(iobuf, 'x', sizeof(iobuf)); | |
for (;;) { | |
if (filesize >= 0 && sizeof(iobuf) > (filesize - written)) | |
towrite = filesize - written; | |
else | |
towrite = sizeof(iobuf); | |
if (!towrite) | |
break; | |
status = write(fd, iobuf, towrite); | |
if (status < 0) { | |
if (errno == ENOSPC) | |
break; | |
else | |
perror("write"); | |
} | |
written += status; | |
} | |
if (!punch_len) | |
return; | |
for (p = 0; p < (written / blksize); p += punch_len + skip_len) { | |
if (fallocate(fd, FALLOC_FL_KEEP_SIZE|FALLOC_FL_PUNCH_HOLE, | |
p * blksize, punch_len * blksize)) { | |
perror("fallocate(FALLOC_FL_KEEP_SIZE|FALLOC_FL_PUNCH_HOLE)"); | |
exit(1); | |
} | |
} | |
} | |
static const char* progname; | |
static void usage(FILE* out) | |
{ | |
fprintf(out, "Usage: %s [-b FORCEBLKSIZE | -p PUNCHBLKS | -s SKIPBLKS | -z FILESIZE] FILLFILE\n", progname); | |
} | |
int main(int argc, char** argv) | |
{ | |
int opt; | |
int fd; | |
const char* fillpath; | |
struct statvfs st; | |
progname = argv[0]; | |
while ((opt = getopt(argc, argv, "b:p:s:z:")) != -1) { | |
switch (opt) { | |
case 'b': | |
blksize = parse_size(optarg); | |
if (blksize <= 0) { | |
fprintf(stderr, "Block size must be positive\n"); | |
exit(1); | |
} | |
break; | |
case 'p': | |
punch_len = parse_number(optarg); | |
if (punch_len < 0) { | |
fprintf(stderr, "Punch length must be >= 0\n"); | |
exit(1); | |
} | |
break; | |
case 's': | |
skip_len = parse_number(optarg); | |
if (skip_len < 0) { | |
fprintf(stderr, "Skip length must be >= 0\n"); | |
exit(1); | |
} | |
break; | |
case 'z': | |
filesize = parse_size(optarg); | |
if (filesize < 0) { | |
fprintf(stderr, "File size must be >= 0\n"); | |
exit(1); | |
} | |
break; | |
default: | |
fprintf(stderr, "Error: unrecognized flag '%c'\n", opt); | |
usage(stderr); | |
exit(1); | |
} | |
} | |
argc -= optind; | |
argv += optind; | |
if (argc != 1) { | |
usage(stderr); | |
exit(1); | |
} | |
fillpath = argv[0]; | |
if (unlink(fillpath) && errno != ENOENT) { | |
fperror("unlink(%s)", fillpath); | |
exit(1); | |
} | |
fd = open(fillpath, O_CREAT|O_TRUNC|O_EXCL|O_WRONLY, 0644); | |
if (fd < 0) { | |
fperror("open(%s)", fillpath); | |
exit(1); | |
} | |
if (blksize == 0) { | |
if (fstatvfs(fd, &st)) { | |
fperror("statvfs(%s)", fillpath); | |
exit(1); | |
} | |
blksize = st.f_bsize; | |
} | |
cheesegrate(fd); | |
if (close(fd)) | |
perror("close"); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment