mkextrafs.pl 13.5 KB
Newer Older
1 2
#!/usr/bin/perl -w
#
3
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
23 24 25 26 27 28 29
#
use English;
use Getopt::Std;
use Fcntl;
use IO::Handle;
use Socket;

30 31 32 33
# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
my $DOSTYPE = "$BINDIR/dostype";

34 35 36 37 38 39
#
# This file goes in boss:/z/testbed/distributions on boss so that is can
# be passed over via wget to the CDROM on widearea nodes.
#
sub usage()
{
Mike Hibler's avatar
Mike Hibler committed
40
    print("Usage: mkextrafs.pl [-2fl] [-s slice] [-r disk] <mountpoint>\n");
41 42 43 44 45
    print("       [-r disk]  disk device to use (default: ad0)\n");
    print("       [-s slice] DOS partition to use or 0 for entire disk (default: 4)\n");
    print("       <mount>    where the new filesystem will be mounted\n");
    print("       -f         DANGEROUS! force creation of the filesystem\n");
    print("                   even if partition is in use\n");
Mike Hibler's avatar
Mike Hibler committed
46 47
    print("       -2         hack: break DOS partition into two unequal (70/30)\n");
    print("                   BSD partitions and mount the first\n");
48
    print("       -m         do everything but do not mount filesystem\n");
49 50 51
    print("Usage: mkextrafs.pl -l [-f]\n");
    print("       -l  list available partitions\n");
    print("       -f  also list inactive partitions that have non-zero type\n");
52 53
    exit(-1);
}
54
my $optlist    = "2fls:r:cnm";
55 56 57 58
my $diskopt;
my $checkit    = 0;
my $forceit    = 0;
my $noinit     = 0;
59
my $nomount    = 0;
60
my $showonly   = 0;
Mike Hibler's avatar
Mike Hibler committed
61
my $twoparts   = 0;
62
my $debug      = 0;
Mike Hibler's avatar
Mike Hibler committed
63 64 65 66 67 68 69

#
# Yep, hardwired for now.  Should be options or queried via TMCC.
#
my $disk       = "ad0";
my $slice      = "4";
my $partition  = "e";
70

71
sub mysystem($);
72 73
sub showspace($);
sub os_find_freedisk($$); # XXX from liblocsetup.pm
74

75 76 77 78 79 80 81 82 83 84
#
# Turn off line buffering on output
#
STDOUT->autoflush(1);
STDERR->autoflush(1);

#
# Untaint the environment.
# 
$ENV{'PATH'} = "/tmp:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:".
85
    "/usr/local/bin:/usr/site/bin:/usr/site/sbin:/usr/local/etc/emulab";
86 87
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

88 89 90 91 92
my $FBSDVERS;
if (`uname -r` =~ /^(\d+)\./) {
    $FBSDVERS = $1;
}

Mike Hibler's avatar
Mike Hibler committed
93
#
94
# Determine if we should use the "geom" tools to make everything happen.
Mike Hibler's avatar
Mike Hibler committed
95 96
# Empirically, this seems to only be needed for FreeBSD 8 and above.
#
97
my $usegeom = 0;
98
if ($FBSDVERS > 7) {
99
    $usegeom = $1;
Mike Hibler's avatar
Mike Hibler committed
100 101
}

102 103 104 105 106 107 108 109
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
Mike Hibler's avatar
Mike Hibler committed
110 111 112
if (defined($options{"f"})) {
    $forceit = 1;
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
113 114 115
if (defined($options{"s"})) {
    $slice = $options{"s"};
}
116 117 118 119 120 121 122 123 124
if (defined($options{"c"})) {
    $checkit = 1;
}
if (defined($options{"r"})) {
    $diskopt = $options{"r"};
}
if (defined($options{"n"})) {
    $noinit = $options{"n"};
}
125 126 127
if (defined($options{"m"})) {
    $nomount = $options{"m"};
}
128 129 130
if (defined($options{"l"})) {
    $showonly = 1;
}
Mike Hibler's avatar
Mike Hibler committed
131 132 133
if (defined($options{"2"})) {
    $twoparts = 1;
}
134 135 136 137 138 139

if ($showonly) {
    showspace($forceit);
    exit(0);
}

140 141 142 143 144 145 146 147 148 149
if (@ARGV != 1) {
    usage();
}
my $mountpoint  = $ARGV[0];

if (! -d $mountpoint) {
    die("*** $0:\n".
	"    $mountpoint does not exist!\n");
}

150
#
151
# XXX determine the disk based on the root fs if not provided.
152
#
153 154
if (defined($diskopt)) {
    $disk = $diskopt;
155
    $disk =~ s/^\/dev\///;
156 157 158 159 160 161
}
else {
    my $rootdev = `df | egrep '/\$'`;
    if ($rootdev =~ /^\/dev\/([a-z]+\d+)s[1-4][a-h]/) {
	$disk = $1;
    }
162 163
}

Mike Hibler's avatar
Mike Hibler committed
164 165 166
my $slicedev   = "${disk}s${slice}";
my $fsdevice   = "/dev/${slicedev}${partition}";

167 168 169 170 171
#
# For entire disk, we will use "fdisk -I" to initialize DOS partition 1.
# Note: we will create the BSD 'e' partition later.
#
if ($slice == 0) {
172 173 174 175 176 177 178
    if ($FBSDVERS >= 10) {
	$slicedev = "${disk}p1";
	$fsdevice = "/dev/$slicedev";
    } else {
	$slicedev = "${disk}s1";
	$fsdevice = "/dev/${slicedev}e";
    }
179 180
}

181
#
Mike Hibler's avatar
Mike Hibler committed
182 183
# An existing fstab entry indicates we have already done this
# XXX override with forceit?  Would require unmounting and removing from fstab.
184 185
#
if (!system("egrep -q -s '^${fsdevice}' /etc/fstab")) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
186
    if ($checkit) {
187
	exit(0);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
188
    }
189
    die("*** $0:\n".
Mike Hibler's avatar
Mike Hibler committed
190 191
	"    There is already an entry in /etc/fstab for $fsdevice\n");
}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
192
elsif ($checkit) {
193
    exit(1);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
194
}
Mike Hibler's avatar
Mike Hibler committed
195 196 197 198 199 200 201 202

#
# Likewise, if already mounted somewhere, fail
#
my $mounted = `mount | egrep '^${fsdevice}'`;
if ($mounted =~ /^${fsdevice} on (\S*)/) {
    die("*** $0:\n".
	"    $fsdevice is already mounted on $1\n");
203
}
Mike Hibler's avatar
Mike Hibler committed
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
#
# As of FreeBSD 10, I am tired of fighting the old MBR tools.
# So for whole disks (slice == 0) we are going to use GPT so that we
# can get good (1M) alignment and potentially big-ass partitions with
# a minimum of fuss.
#
# This means that you cannot image those partitions since imagezip
# does not yet (as of 05/2014) understand GPT. But we have no mechanism
# for capturing an image from anything but the system disk anyway.
#
if ($FBSDVERS >= 10 && $slice == 0) {
    my @out = `gpart show $disk 2>/dev/null`;
    if ($? == 0) {
	# first line should tell us how the drive is partitioned
	my $format = "UNKNOWN";
	if ($out[0] =~ /^=>\s*\d+\s+\d+\s+$disk\s+(\S+)\s+/) {
	    $format = $1;
	}
	if ($forceit) {
	    mysystem("gpart destroy -F $disk");
	} else {
	    die("*** $0:\n".
		"    $disk is already partitioned (type $format), ".
		"use -f to override\n");
	}
    }
    mysystem("gpart create -s gpt $disk");
    mysystem("gpart add -i 1 -t freebsd-ufs -a 1m $disk");

    mysystem("newfs -U $fsdevice");
    mysystem("echo \"$fsdevice $mountpoint ufs rw 0 2\" >> /etc/fstab");

    if (!$nomount) {
	mysystem("mount $mountpoint");
	mysystem("mkdir $mountpoint/local");
    }
    exit(0);
}

244 245 246 247
#
# See what the current type is for the partition
#
my $stype = -1;
248
my $soffset = -1;
249
if (!open(FD, "fdisk -s /dev/$disk 2>&1|")) {
250 251
    die("*** $0:\n".
        "    $disk: could not get partition info\n");
252
}
253 254 255 256 257 258 259 260 261 262 263
while (<FD>) {
    #
    # No MBR should mean that the entire disk is unused.
    # If they want to use the whole disk (slice==0), this is okay.
    #
    if (/invalid fdisk partition table found/) {
	if ($slice != 0) {
	    die("*** $0:\n".
		"    $disk: no partition table!\n");
	}
	$stype = 0;
Leigh B Stoller's avatar
Leigh B Stoller committed
264
	next;
265 266 267 268 269 270 271 272
    }
    #
    # Format of fdisk output is:
    #  /dev/da0: 30394 cyl 255 hd 63 sec
    #  Part        Start Size     Type Flags
    #  1:          63    12305790 0xa5 0x80
    #  ...
    #
273
    if (/^\s*([1-4]):\s+(\d+)\s+\d+\s+(0x\S\S)\s+/) {
274
	my $part = $1;
275 276
	my $offset = $2;
	my $type = hex($3);
277

278 279 280 281 282 283 284 285 286 287 288 289 290 291
	#
	# If there is a valid partition on the disk and they are
	# using the entire disk without forcing, stop them!
	#
	if ($slice == 0) {
	    if ($type != 0 && !$forceit) {
		die("*** $0:\n".
		    "    There are valid partitions on the disk;".
		    " need to specify -f to overwrite entire disk!\n");
	    }
	    $stype = 0;
	}
	if ($slice == $part) {
	    $stype = $type;
292 293 294 295
	    # XXX need to remember the offset as well
	    if ($usegeom) {
		$soffset = $offset;
	    }
296 297 298 299 300 301 302
	    last;
	}
    }
}	    
close(FD);

if ($stype == -1) {
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
    #
    # XXX if we are in the GEOM world and we do not find the 4th partition
    # it may be because a previous gpart call noticed the unused partition
    # and deleted it. This happens in elabinelab where we first repurpose
    # the second (Linux) partition.
    #
    # If this happens, we just try re-creating the 4th partition. gpart
    # will "do the right thing" in terms of offset and size.
    #
    if ($usegeom && $slice == 4) {
	$stype = 0;
    } else {
	die("*** $0:\n".
	    "    Could not find partition $slice fdisk entry!\n");
    }
318
}
Mike Hibler's avatar
Mike Hibler committed
319 320 321 322 323 324 325 326 327 328 329 330 331 332

#
# Fail if not forcing and the partition type is non-zero.
#
if (!$forceit) {
    if ($stype != 0) {
	die("*** $0:\n".
	    "    non-zero partition type ($stype) for /dev/${slicedev}, ".
	    "use -f to override\n");
    }
} elsif ($stype && $stype != 165) {
    warn("*** $0: WARNING: changing partition type from $stype to 165\n");
}

Mike Hibler's avatar
Mike Hibler committed
333 334 335
#
# Dark magic to allow us to modify the open boot disk
#
336
if ($usegeom && $slice != 0) {
337
    my $oarg = "";
338 339
    if ($stype != 0) {
	mysystem("gpart delete -i $slice $disk");
340 341 342
	if ($soffset >= 0) {
	    $oarg = "-b $soffset";
	}
343
    }
Mike Hibler's avatar
Mike Hibler committed
344 345 346 347 348
    # XXX it appears that in FBSD 10, even if the type is 0,
    # we still need to force the location.
    elsif ($FBSDVERS >= 10 && $soffset >= 0) {
	$oarg = "-b $soffset";
    }
349
    mysystem("gpart add -i $slice -t freebsd $oarg $disk");
350
    $stype = 165;
Mike Hibler's avatar
Mike Hibler committed
351 352
}

Mike Hibler's avatar
Mike Hibler committed
353
#
354 355 356 357
# If they are whacking the whole disk, just use "fdisk -I' to initialize
# partition 1 of the disk.
#
if ($slice == 0 && !$noinit) {
358
    mysystem("fdisk -I /dev/$disk");
359
}
Mike Hibler's avatar
Mike Hibler committed
360
#
361 362 363
# Otherwise set the partition type to BSD if not already set.
#
elsif ($stype != 165) {
364
    die("*** $0:\n".
365 366 367
	"    No $DOSTYPE program, cannot set type of DOS partition\n")
	if (! -e "$DOSTYPE");
    mysystem("$DOSTYPE -f /dev/$disk $slice 165");
368
}
369 370 371
#
# If not recreating the filesystems, just try to mount it
#
372 373 374 375 376
elsif ($noinit) {
    mysystem("mount $fsdevice $mountpoint");
    mysystem("echo \"$fsdevice $mountpoint ufs rw 0 2\" >> /etc/fstab");
    exit(0);
}
377 378 379 380

#
# Now create the disklabel
#
381
my $tmpfile = "/tmp/disklabel";
Mike Hibler's avatar
Mike Hibler committed
382
mysystem("disklabel -w -r $slicedev auto");
383
mysystem("disklabel -r $slicedev > $tmpfile");
384

385 386 387 388 389
#
# Tweak the disk label to ensure there is an 'e' partition
# In FreeBSD 4.x, we just add one.
# In FreeBSD 5.x, we replace the 'a' partition which is created by default.
#
Mike Hibler's avatar
Mike Hibler committed
390 391 392 393
# In "two part" mode, break into two partitions 'e' (70%) and 'f' (30%)
# and use the first.  This is primarily for elabinelab use, where we need
# a smaller /share that is not on the same FS as /proj and /users.
#
394
open(DL, "+<$tmpfile") or
395
    die("*** $0:\n".
396 397 398
	"    Could not edit temporary label in $tmpfile!\n");
my @dl = <DL>;
if (scalar(grep(/^  ${partition}: /, @dl)) != 0) {
399
    die("*** $0:\n".
Mike Hibler's avatar
Mike Hibler committed
400
        "    Already an \'$partition\' partition?!\n");
401
}
402 403 404 405
seek(DL, 0, 0);
truncate(DL, 0);

my $done = 0;
Mike Hibler's avatar
Mike Hibler committed
406 407 408
my $fsize = 0;
my $foff = 0;
my $bad = 0;
409 410
foreach my $line (@dl) {
    # we assume that if the 'a' paritition exists, it is the correct one
Mike Hibler's avatar
Mike Hibler committed
411 412 413 414
    if (!$done && $line =~ /^\s+a:\s+(\d+)\s+(\d+)(.*)$/) {
	my $size = $1;
	my $off = $2;
	my $rest = $3;
415
	$rest =~ s/unused/4.2BSD/;
Mike Hibler's avatar
Mike Hibler committed
416 417 418 419 420 421 422 423
	if ($twoparts) {
	    my $esize = int($size / 16 * 0.7) * 16;
	    $foff = $off + $esize;
	    $fsize = $size - $esize;
	    $line = "  e: $esize $off $rest";
	} else {
	    $line = "  e: $size $off $rest";
	}
424 425
	$done = 1;
    }
Mike Hibler's avatar
Mike Hibler committed
426 427 428 429 430 431
    # otherwise we use the 'c' partition as our model
    if (!$done && $line =~ /^\s+c:\s+(\d+)\s+(\d+)(.*)$/) {
        print DL $line;
	my $size = $1;
	my $off = $2;
	my $rest = $3;
432
	$rest =~ s/unused/4.2BSD/;
Mike Hibler's avatar
Mike Hibler committed
433 434 435 436 437 438 439 440
	if ($twoparts) {
	    my $esize = int($size / 16 * 0.7) * 16;
	    $foff = $off + $esize;
	    $fsize = $size - $esize;
	    $line = "  e: $esize $off $rest";
	} else {
	    $line = "  e: $size $off $rest";
	}
441 442
	$done = 1;
    }
Mike Hibler's avatar
Mike Hibler committed
443 444 445
    if ($line =~ /^  f:/) {
	$bad = 1;
    }
446 447
    print DL $line;
}
Mike Hibler's avatar
Mike Hibler committed
448
if (!$bad && $twoparts && $fsize > 0) {
449
    print DL "  f: $fsize $foff 4.2BSD 0 0\n";
Mike Hibler's avatar
Mike Hibler committed
450
}
451 452 453 454
close(DL);
mysystem("disklabel -R -r $slicedev $tmpfile");
unlink($tmpfile);

455 456 457 458
# FreeBSD 5 doesn't have MAKEDEV
mysystem("cd /dev; ./MAKEDEV ${slicedev}c")
    if (-e "/dev/MAKEDEV");

Mike Hibler's avatar
Mike Hibler committed
459
mysystem("newfs -U -i 25000 $fsdevice");
460 461
mysystem("echo \"$fsdevice $mountpoint ufs rw 0 2\" >> /etc/fstab");

462 463 464 465
if (!$nomount) {
    mysystem("mount $mountpoint");
    mysystem("mkdir $mountpoint/local");
}
466
exit(0);
467 468 469 470 471 472 473 474 475 476

sub mysystem($)
{
    my ($command) = @_;

    if (0) {
	print "'$command'\n";
    }
    else {
	print "'$command'\n";
477 478
	my $rv = system($command);
	if ($rv) {
479
	    die("*** $0:\n".
480
		"    Failed ($rv): '$command'\n");
481 482 483 484
	}
    }
    return 0
}
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544

sub sizestr($)
{
    my ($bytes) = @_;
    my ($divisor, $units);

    if ($bytes > 1000 * 1000 * 1000 * 1000) {
	$divisor = 1000 * 1000 * 1000 * 1000;
	$units = "TB";
    } elsif ($bytes > 1000 * 1000 * 1000) {
	$divisor = 1000 * 1000 * 1000;
	$units = "GB";
    } else {
	$divisor = 1000 * 1000;
	$units = "MB";
    }
    return sprintf "%6.2f%s", $bytes / $divisor, $units;
}

sub showspace($)
{
    my ($force) = @_;

    # XXX we keep this dynamic since this script can operate standalone
    require liblocsetup;

    my %diskinfo = liblocsetup::os_find_freedisk(0, $force);
    if (!%diskinfo) {
	print "No space found on node\n";
    }
    print "Found space";
    print " ('*' = in use, but unmounted)"
	if ($force);
    print ":\nDisk\tSlice\tBytes\n";
    foreach my $disk (sort keys %diskinfo) {
	my $ff = "";
	my $iu = "";
	if (exists($diskinfo{$disk}{"size"})) {
	    my $size = sizestr($diskinfo{$disk}{"size"});
	    if ($diskinfo{$disk}{"type"} != 0) {
		$ff = "-f";
		$iu = "*";
	    }
	    print "$disk$iu\t \t$size # mkextrafs -r $disk -s 0 $ff\n";
	} else {
	    foreach my $part (keys %{$diskinfo{$disk}}) {
		my $pnum;
		($pnum = $part) =~ s/.*(\d+)$/$1/;
		if (exists($diskinfo{$disk}{$part}{"size"})) {
		    my $size = sizestr($diskinfo{$disk}{$part}{"size"});
		    if ($diskinfo{$disk}{$part}{"type"} != 0) {
			$ff = "-f";
			$iu = "*";
		    }
		    print "$disk$iu\t$pnum\t$size # mkextrafs -r $disk -s $pnum $ff\n";
		}
	    }
	}
    }
}