| #!/usr/bin/perl -w | 
 | # SPDX-License-Identifier: GPL-2.0 | 
 | # Copyright (c) 2003-2004 Silicon Graphics, Inc.  All Rights Reserved. | 
 | # | 
 | use strict; | 
 | use IO::File; | 
 | use Getopt::Std; | 
 | # | 
 | # | 
 | # Modify a filesystem's superblock and AGF metadata structures | 
 | # so that only a subset of the allocation groups will be used. | 
 | # Intended use is in testing large virtual devices (eg. loop) | 
 | # with extremely large filesystems, where we want to ensure | 
 | # high allocation groups are used as much as possible (where | 
 | # the block addresses are large). | 
 | # | 
 |  | 
 | my %opt; | 
 | getopts('cf:l:qr:v', \%opt); | 
 |  | 
 | die "Usage: $0 [-f AG] [-l AG] [-r bytes] [-cqv] device\n" unless (@ARGV == 1); | 
 | my $device = shift @ARGV; | 
 | die "$device: no such file\n" unless (-e $device); | 
 |  | 
 | my $clearall = defined($opt{'c'}) ? 1 : 0; | 
 | my $retain = defined($opt{'r'}) ? $opt{'r'} : -1; | 
 | my $quiet = defined($opt{'q'}) ? 1 : 0; | 
 | my $verbose = defined($opt{'v'}) ? 1 : 0; | 
 | my $nagfirst = defined($opt{'f'}) ? $opt{'f'} : 0; | 
 | my $naglast = defined($opt{'l'}) ? $opt{'l'} : 0; | 
 |  | 
 | #  | 
 | # "clearall" means clear everything barring the final AG. | 
 | # "retain" means clearall and retain a specified amount in the | 
 | # second-from-last AG as well (value is the number of bytes). | 
 | # [NB: retain with a value of zero is now the same as clearall]. | 
 | #  | 
 | if ($retain >= 0) { | 
 | 	$clearall = 1; | 
 | } | 
 |  | 
 | sub xfs_db { | 
 | 	my $xfsdb = 'xfs_db -x'; | 
 | 	my %hash; | 
 |  | 
 | 	foreach (@_) { | 
 | 		$xfsdb .= ' -c ' . $_; | 
 | 	} | 
 | 	print $xfsdb, ' ', $device, "\n" if ($verbose); | 
 |  | 
 | 	die unless open(DB, "$xfsdb $device 2>/dev/null |"); | 
 | 	while (<DB>) { | 
 | 		if (/^(\S+) = (.*)$/) { | 
 | 			print if ($verbose); | 
 | 			$hash{$1} = $2; | 
 | 		} | 
 | 	} | 
 | 	return %hash; | 
 | } | 
 |  | 
 |  | 
 | #  | 
 | # Stage 1: Get control information from the superblock | 
 | #  | 
 |  | 
 | my @sbprint = ( '"print fdblocks"', '"print agcount"', '"print blocksize"' ); | 
 | my @agffree = ( '"print freeblks"' ); | 
 |  | 
 | my %sb = xfs_db 'sb', @sbprint; | 
 | print "=== Initially ", $sb{'fdblocks'}, " blocks free across ", | 
 | 	$sb{'agcount'}, " AGs\n" unless $quiet; | 
 | if ($clearall && ($nagfirst || $naglast)) { | 
 | 	print STDERR "  o Clearall/retain specified with first/last AG\n"; | 
 | 	exit(1); | 
 | } | 
 | if ($nagfirst >= $sb{'agcount'}) { | 
 | 	print STDERR "  o First AG number is too large\n"; | 
 | 	exit(1); | 
 | } | 
 | if ($naglast >= $sb{'agcount'}) { | 
 | 	print STDERR "  o Last AG number is too large\n"; | 
 | 	exit(1); | 
 | } | 
 | if ($naglast - $nagfirst < 0) { | 
 | 	print STDERR "  o No AGs to clear\n"; | 
 | 	exit(1); | 
 | } | 
 | if ($clearall) { | 
 | 	$naglast = $sb{'agcount'} - 2; | 
 | 	if ($retain > 0) { | 
 | 		my %check; | 
 |  | 
 | 		$naglast--; | 
 | 		$retain /= $sb{'blocksize'};	# convert to fsblocks | 
 | 		%check = xfs_db "'agf $naglast'", @agffree; | 
 | 		if ($check{'freeblks'} < $retain) { | 
 | 			print STDERR "  o Insufficient space to retain\n"; | 
 | 			exit(1); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 |  | 
 | #  | 
 | # Stage 2: Wipe out all completely masked allocation groups. | 
 | #  | 
 |  | 
 | my @agfprint = ( '"print freeblks"', '"print flcount"' ); | 
 | my @agfcommands = ( '"write freeblks 0"', | 
 | 			'"write longest 0"', '"write flcount 0"', | 
 | 			'"write bnolevel 1"', '"write cntlevel 1"', | 
 | 			'"write flfirst 0"', '"write fllast 0"' ); | 
 | my @bnoprint = ( '"addr bnoroot"', '"print numrecs"' ); | 
 | my @bnocommands = ( '"addr bnoroot"', '"write numrecs 0"', | 
 | 			'"write leftsib -1"', '"write rightsib -1"' ); | 
 | my @cntprint = ( '"addr cntroot"', '"print numrecs"' ); | 
 | my @cntcommands = ( '"addr cntroot"', '"write numrecs 0"', | 
 | 			'"write leftsib -1"', '"write rightsib -1"' ); | 
 |  | 
 | print "=== Wiping ", $naglast - $nagfirst + 1, | 
 | 	" AGs starting from AG #", $nagfirst, "\n" unless $quiet; | 
 |  | 
 | my $ag = $nagfirst; | 
 | while ($ag <= $naglast) { | 
 | 	print "  o AG#", $ag, " AGF fields\n" unless $quiet; | 
 |  | 
 | 	my %agf = xfs_db "'agf $ag'", @agfprint; | 
 | 	xfs_db "'agf $ag'", @agfcommands; | 
 |  | 
 | 	my $blockcnt = $agf{'freeblks'} + $agf{'flcount'}; | 
 | 	$sb{'fdblocks'} -= $blockcnt; | 
 | 	print "     cleared ", $blockcnt, " blocks from AG#", $ag, "\n" | 
 | 		unless $quiet; | 
 |  | 
 | 	my %btree = xfs_db "'agf $ag'", @bnoprint; | 
 | 	xfs_db "'agf $ag'", @bnocommands, "'agf $ag'", @cntcommands; | 
 | 	print "     cleared ", $btree{'numrecs'}, " BNO/CNT btree recs in AG#", | 
 | 		$ag, "\n" unless $quiet; | 
 |  | 
 | 	$ag++; | 
 | } | 
 |  | 
 |  | 
 | #  | 
 | # Stage 3: Wipe out any partially masked allocation group. | 
 | #  | 
 |  | 
 | if ($retain > 0) { | 
 | 	print "  o AG#", $ag, " AGF fields (partial)\n" unless $quiet; | 
 |  | 
 | 	my %ragf = xfs_db "'agf $ag'", '"print freeblks"', | 
 | 		'"addr bnoroot"', '"print recs[1].startblock"'; | 
 | 	my $maskblks = $ragf{'freeblks'} - $retain; | 
 | 	my $newstart = $ragf{'recs[1].startblock'} + $maskblks; | 
 | 	xfs_db "'agf $ag'", | 
 | 		"'write freeblks $retain'", "'write longest $retain'", | 
 | 		"'agf $ag'", '"addr bnoroot"', | 
 | 		"'write recs[1].startblock $newstart'", | 
 | 		"'write recs[1].blockcount $retain'", | 
 | 		"'agf $ag'", '"addr cntroot"', | 
 | 		"'write recs[1].startblock $newstart'", | 
 | 		"'write recs[1].blockcount $retain'"; | 
 |  | 
 | 	$sb{'fdblocks'} -= $maskblks; | 
 | 	print "     cleared ", $maskblks, " blocks from AG#", $ag, "\n" | 
 | 		unless $quiet; | 
 | } | 
 |  | 
 | print "=== Updating final freespace count, ", $sb{'fdblocks'}, " blocks\n" | 
 | 	unless $quiet; | 
 | xfs_db "'sb 0'", "'write fdblocks $sb{'fdblocks'}'" |