-
Notifications
You must be signed in to change notification settings - Fork 71
/
fill2fs
executable file
·342 lines (307 loc) · 10 KB
/
fill2fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2001 Silicon Graphics, Inc. All Rights Reserved.
#
# fill2fs:
# Fill a filesystem, or write a specified number of bits.
# Script runs deterministically given a seed for the random number
# generator. Will generate the same set of directories and files,
# with the only source of variation the precise point at which the
# filesystem fills up. When used with XFS filesystems, and when
# the filesize is small, XFS pre-allocation may cause the filesystem
# to "fill up" before the actual disk space is gone. Using this
# script repeatedly, with a delay between invocations, to account
# for extent flushing, will allow more of the filesystem to be filled.
#
# All generated files are checksummed and this sum is stored in the
# filename (files are named: <sequence number>.<checksum>.data.
# Normally script is invoked using the --list option, which causes all
# data files to be written to standard output (--list=-) or a file
# (--list=filename). This list can be fed into fill2fs_check, which
# recomputes the checksums and flags any errors.
#
# The --stddev=0 forces all files to be precisely --filesize bytes in
# length, some other value (--stddev=val) produces filesizes with a
# normal distribution with standard deviation val. If not specified
# most file sizes are set to lie within 30% of the requested file size.
#
# fill2fs is not guaranteed to write the requested number of bytes, or
# fill the filesystem completely. However, it and fill2 are both very
# careful about establishing when write operations fail. When the
# --verbose option is used the number of bytes "actually written"
# is guaranteed to be the number of bytes on disk.
#
# $Id$
#
use Getopt::Long;
use File::Basename;
#
# fsinfo: get filesystem info put it into the global namespace, initialises:
# $dev, $type, $blocks, $used, $avail, $cap, $mnt, $mnt_options
# $fsblocks, $fsblocksize, $agblocks, $agcount, $imax_pct, $logblocks
# $logstart, $internal
#
# if $verbose is set then output fs info to STDERR
#
sub fsinfo {
my($file) = $_[0];
# filesystem space and mount point
$cmd = "df -P -T --block-size=512";
chomp($_ = `$cmd $file | tail -1`);
$n = ($dev, $type, $blocks, $used, $avail, $cap, $mnt) = split(/ +/);
die("df failed") if ($n != 7);
if ($fscheck && $type ne "xfs") {
die("Error: $progname: filesystem is not xfs")
}
# how was this filesystem mounted
$_ = `grep $dev /etc/mtab`;
@mtab = split(/ +/);
$mnt_options = $mtab[3];
# if we're running as root run xfs_db on the filesystem
if ($> == 0) {
# xfs_db: read only, use the default superblock, print everything
die("Error: $progname: can't read device: \"$dev\"\n") if (! -r $dev);
$_=`xfs_db -r -c sb -c p $dev`;
# multiline matching ^$ refers to individual lines...
($fsblocks) = /^dblocks = (\d+)$/m;
($fsblocksize) = /^blocksize = (\d+)$/m;
($agblocks) = /^agblocks = (\d+)$/m;
($agcount) = /^agcount = (\d+)$/m;
($imax_pct) = /^imax_pct = (\d+)$/m;
($logblocks) = /^logblocks = (\d+)$/m;
($logstart) = /^logstart = (\d+)$/m;
$internal = $logstart > 0 ? " (internal)" : "";
$verbose && print STDERR <<"EOF"
Filesystem information:
type=$type; device=$dev
mount point=$mnt; mount options=$mnt_options
percent full=$cap; size (512 byte blocks)=$blocks; used=$used; avail=$avail
total filesystem size (fs blocks)=$fsblocks; fs block size=$fsblocksize; imax_pct=$imax_pct
agcount=$agcount; agblocks=$agblocks; logblocks=$logblocks; logstart=$logstart$internal
EOF
}
}
# returns numbers with a normal distribution
sub normal {
my($mean) = $_[0];
my($stddev) = $_[1];
$x = -6.0;
for ($i = 0; $i < 12; $i++) {
$x += rand;
}
$x = $mean + $stddev * $x;
return $x;
}
#
# determine script location and find fill2
#
chomp($cwd = `pwd`);
chomp($_ = `type -P fill2 | head -1`);
if (-x $_) {
# look in the path
$fill2 = fill2;
}
else {
# in the same directory - get absolute path
chomp($dirname = dirname $0);
if ($dirname =~ m!^/.*!) {
$fill2 = $dirname . "/fill2";
}
else {
# relative
$fill2 = $cwd . "/" . $dirname . "/fill2";
}
if (! -x $fill2) {
die("Error: $progname: can't find fill2, tried \"$fill2\"\n");
}
}
#
# process/check args
#
$progname=$0;
GetOptions("bytes=f" => \$bytes,
"dir=s" => \$root,
"filesize=i" => \$filesize,
"force!" => \$force,
"help!" => \$help,
"list=s" => \$list,
"fscheck!" => \$fscheck,
"percent=f" => \$percentage,
"seed=i" => \$seed,
"stddev=i" => \$stddev,
"sync=i" => \$sync_bytes,
"verbose!" => \$verbose);
# check/remove output directory, get filesystem info
if (defined $help
|| (! defined $root && @ARGV != 1)
|| (defined $root && @ARGV == 1))
{
# newline at end of die message suppresses line number
print STDERR <<"EOF";
Usage: $progname [options] root_dir
Options:
--bytes=num total number of bytes to write
--dir=name where to write files
--filesize=num set all files to num bytes in size
--force overwrite any existing files
--help print this help message
--list=filename store created files to filename (- for stdout)
--percent=num percentage of filesystem to fill
--seed=num seed for random number generator
--stddev set file size standard deviation
--sync=num sync every num bytes written
--verbose verbose output
EOF
exit(1) unless defined $help;
# otherwise...
exit(0);
}
#
# lots of boring argument checking
#
# root directory and filesystem info
$root = $ARGV[0] if (@ARGV == 1);
if (-e $root) {
if (! $force) {
die("Error: $progname: \"$root\" already exists\n");
}
else {
$verbose && print STDERR "Removing \"$root\"... ";
system("rm -rf $root");
$verbose && print STDERR "done\n";
}
}
chomp($root_dir = dirname $root);
fsinfo $root_dir;
# $list can be "-" for stdout, perl open ">-" opens stdout
open LIST, ">$list" if (defined $list);
# how many bytes should we write
if (defined $bytes && defined $percentage) {
die("Error: $progname: can't specify --bytes and --percent\n");
}
# check percentage
if (defined $percentage && ($percentage < 0 || $percentage > 100)) {
die("Error: $progname: invalid percentage\n");
}
if (! defined $bytes && ! defined $percentage) {
$bytes = $avail * 512;
$verbose && print STDERR <<"EOF";
Neither --bytes nor --percent specified: filling filesystem ($bytes bytes)
EOF
}
elsif (! defined $bytes) {
$bytes = int($blocks * 512 * $percentage / 100.0);
}
if (($bytes > $blocks * 512) || (! $force && $bytes > $avail * 512))
{
die("Error: $progname: not enough free disk space, disk is $cap full\n");
}
#
# To get fix sized files set stddev to 0. The default is to make most files
# within 30% of the requested filesize (or 4k if filesize is not specified).
# Set the standard deviation to be half of the required percentage: ~95% of
# samples lie within 2 standard deviations of the mean.
#
$filesize = 4096 if (! defined $filesize);
die("Error: $progname: --filesize must be >= 1 byte") if ($filesize < 1);
$stddev = 0.5 * 0.3 * $filesize if (! defined $stddev);
$seed = time ^ $$ if (! defined $seed);
srand $seed;
umask 0000;
mkdir $root, 0777;
chdir $root;
$total = 0;
$files = 0;
$dirs = 0;
$d = 0;
$sync_cnt = 1;
#
# fill filesystem
#
$verbose && print STDERR "Writing $bytes bytes (seed is $seed)... ";
while ($total < $bytes) {
$r = rand(3.0);
if (($d == 0 && $r < 0.5) || ($d > 0 && $r >= 0.0 && $r < 2.4)) {
# create a new data file
$n = sprintf("%04d", $names[$d]++);
if ($stddev == 0) {
$size = $filesize;
}
else {
$size = int(normal($filesize, $stddev));
}
$left = $bytes - $total;
$size = 0 if ($size < 0);
$size = $left if ($size > $left);
# fill2 will fail if the filesystem is full - not an error!
$cmd = "$fill2 -d nbytes=$size,linelength=72,seed=$n -b 4k $n";
$cmd .= " > /dev/null 2>&1" if (! $verbose);
if (system($cmd) != 0) {
if ($verbose) {
warn("can't create a file - assuming filesystem is full\n");
}
if (-e $n && unlink($n) != 1) {
warn("couldn't delete \"$n\"");
}
last;
}
$_ = `sum -r $n`;
($sum) = split(/ +/);
$name = "$n.$sum.data";
$cmd = "mv $n $name"; # perl rename failed earlier than using mv
$cmd .= " > /dev/null 2>&1" if (! $verbose);
if (system($cmd) != 0) {
if ($verbose) {
warn("can't rename a file - assuming filesystem is full\n");
}
if (-e $name && unlink($name) != 1) {
warn("couldn't delete \"$name\"");
}
last;
}
if (defined $list) {
chomp($_ = `pwd`);
printf LIST ("%s/%s\n", $_, $name);
}
$total += $size;
$files++;
if (defined $sync_bytes && int($total / $sync_bytes) > $sync_cnt) {
$sync_cnt++;
system("sync");
}
}
# note that if d==0 create directories more frequently than files
elsif (($d == 0 && $r >= 0.5) || ($d > 0 && $r >= 2.4 && $r < 2.7)) {
# create a new directory and descend
$name = sprintf("%04d.d", $names[$d]++);
if (! mkdir($name, 0777)) {
warn("can't make a directory - assuming filesystem is full\n");
last;
}
chdir($name) or die();
$d++;
$dirs++;
}
elsif ($r >= 2.7 && $r < 3.0) {
# pop up to the parent directory same probability as descend
die("Error: $progname: panic: shouldn't be here!") if ($d == 0);
chdir("..") or die();
$d--;
}
}
# make sure we return to the original working directory
chdir($cwd) or die();
$verbose && print STDERR "done\n";
$verbose && print STDERR "$total bytes (in $files files and $dirs directories) were actually written\n";
close LIST;
exit(0) if ($total = $bytes);
exit(1) if ($total == 0);
exit(2) if ($total > 0 && $total < $bytes);
# - to sum all generated data:
# find /home/fill/ -name \*data | xargs ls -al | awk '{total = total + $5; } END { printf("total = %d bytes\n", total); }'
# - to find any files not of the required size
# find . -name \*data -a \! -size 4096c
# - count new files
# find ./fill -name \*.data | wc
# - count new directories
# find ./fill -name \*.d | wc