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