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