liblocsetup.pm 37.7 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. Stoller committed
2
#
3
# Copyright (c) 2000-2016 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/>.
# 
# }}}
Leigh B. Stoller's avatar
Leigh B. Stoller committed
23
24
#

25
26
27
28
29
30
31
#
# FreeBSD specific routines and constants for the client bootime setup stuff.
#
package liblocsetup;
use Exporter;
@ISA = "Exporter";
@EXPORT =
Leigh B Stoller's avatar
Leigh B Stoller committed
32
33
    qw ( $CP $EGREP $NFSMOUNT $UMOUNT $TMPASSWD $SFSSD $SFSCD $RPMCMD
	 $HOSTSFILE $LOOPBACKMOUNT $CHMOD
34
	 os_account_cleanup os_ifconfig_line os_etchosts_line
35
	 os_setup os_groupadd os_useradd os_userdel os_usermod os_mkdir
36
	 os_ifconfig_veth os_viface_name os_modpasswd
37
38
	 os_routing_enable_forward os_routing_enable_gated
	 os_routing_add_manual os_routing_del_manual os_homedirdel
39
	 os_groupdel os_getnfsmounts os_islocaldir os_mountextrafs
40
	 os_fwconfig_line os_fwrouteconfig_line os_config_gre os_nfsmount
41
	 os_find_freedisk os_get_ctrlnet_ip
42
	 os_getarpinfo os_createarpentry os_removearpentry
43
	 os_getstaticarp os_setstaticarp os_ismounted os_unmount os_mount
44
45
       );

46
47
sub VERSION()	{ return 1.0; }

48
49
50
# Must come after package declaration!
use English;

51
52
53
54
# Load up the paths. Its conditionalized to be compatabile with older images.
# Note this file has probably already been loaded by the caller.
BEGIN
{
55

56
57
58
59
60
61
62
63
64
65
66
    if (-e "/etc/emulab/paths.pm") {
	require "/etc/emulab/paths.pm";
	import emulabpaths;
    }
    else {
	my $ETCDIR  = "/etc/testbed";
	my $BINDIR  = "/etc/testbed";
	my $VARDIR  = "/etc/testbed";
	my $BOOTDIR = "/etc/testbed";
    }
}
67
68
# Convenience.
sub REMOTE()	{ return libsetup::REMOTE(); }
69
sub REMOTEDED()	{ return libsetup::REMOTEDED(); }
70
sub MFS()	{ return libsetup::MFS(); }
71
sub JAILED()	{ return libsetup::JAILED(); }
Mike Hibler's avatar
Mike Hibler committed
72
sub INXENVM()   { return libsetup::INXENVM(); }
73
74
75

#
# Various programs and things specific to FreeBSD and that we want to export.
Ryan Jackson's avatar
Ryan Jackson committed
76
#
77
$CP		= "/bin/cp";
Mike Hibler's avatar
Mike Hibler committed
78
$DF		= "/bin/df";
79
$EGREP		= "/usr/bin/egrep -s -q";
80
81
$NFSMOUNT	= "/sbin/mount -o -b ";
$LOOPBACKMOUNT	= "/sbin/mount -t null ";
82
$MOUNT		= "/sbin/mount";
83
$UMOUNT		= "/sbin/umount";
84
$TMPASSWD	= "$ETCDIR/master.passwd";
Austin Clements's avatar
Austin Clements committed
85
86
$SFSSD		= "/usr/local/sbin/sfssd";
$SFSCD		= "/usr/local/sbin/sfscd";
Mike Hibler's avatar
Mike Hibler committed
87
$RPMCMD		= "/usr/local/bin/rpm";
88
$HOSTSFILE	= "/etc/hosts";
89
$WGET		= "/usr/local/bin/wget";
Leigh B Stoller's avatar
Leigh B Stoller committed
90
$CHMOD		= "/bin/chmod";
91
$ARP		= "/usr/sbin/arp";
92
93
94
95

#
# These are not exported
#
96
my $TMGROUP	= "$ETCDIR/group";
97
98
99
100
my $USERADD     = "/usr/sbin/pw useradd";
my $USERDEL     = "/usr/sbin/pw userdel";
my $USERMOD     = "/usr/sbin/pw usermod";
my $GROUPADD	= "/usr/sbin/pw groupadd";
101
my $GROUPDEL	= "/usr/sbin/pw groupdel";
102
103
my $CHPASS	= "/usr/bin/chpass -p";
my $MKDB	= "/usr/sbin/pwd_mkdb -p";
104
105
my $IFCONFIGBIN = "/sbin/ifconfig";
my $IFCONFIG    = "$IFCONFIGBIN %s inet %s netmask %s %s %s";
106
my $IFALIAS     = "$IFCONFIGBIN %s alias %s netmask %s";
107
my $IFC_1000MBS = "media 1000baseTX";
108
109
my $IFC_100MBS  = "media 100baseTX";
my $IFC_10MBS   = "media 10baseT/UTP";
110
my $IFC_AUTO	= "";
111
my $IFC_FDUPLEX = "mediaopt full-duplex";
112
my $IFC_HDUPLEX = "mediaopt half-duplex";
113
my $IFC_ADUPLEX = "";
114
my $MKDIR	= "/bin/mkdir";
115
116
my $GATED	= "/usr/local/sbin/gated";
my $ROUTE	= "/sbin/route";
117
118
my $SHELLS	= "/etc/shells";
my $DEFSHELL	= "/bin/tcsh";
119
120
my $ISCSI	= "/sbin/iscontrol";
my $ISCSICNF	= "/etc/iscsi.conf";
121
my $SMARTCTL	= "/usr/local/sbin/smartctl";
122
123

#
124
# OS dependent part of account cleanup. On a remote node, this will
Ryan Jackson's avatar
Ryan Jackson committed
125
126
# only be called from inside a JAIL, or from the prepare script.
#
127
sub os_account_cleanup($)
128
{
129
130
131
132
133
134
    # XXX this stuff should be lifted up into rc.accounts, sigh
    my ($updatemasterpasswdfiles) = @_;
    if (!defined($updatemasterpasswdfiles)) {
	$updatemasterpasswdfiles = 0;
    }

135
136
137
    printf STDOUT "Resetting passwd and group files\n";
    if (system("$CP -f $TMGROUP /etc/group") != 0) {
	print STDERR "Could not copy default group file into place: $!\n";
138
	return -1;
139
    }
Ryan Jackson's avatar
Ryan Jackson committed
140

141
142
    if (system("$CP -f $TMPASSWD /etc/master.passwd_testbed") != 0) {
	print STDERR "Could not copy default passwd file into place: $!\n";
143
	return -1;
144
    }
Ryan Jackson's avatar
Ryan Jackson committed
145

146
147
    if (system("$MKDB /etc/master.passwd_testbed") != 0) {
	print STDERR "Failure running $MKDB on default password file: $!\n";
148
	return -1;
149
150
151
152
153
154
155
156
    }
    return 0;
}

#
# Generate and return an ifconfig line that is approriate for putting
# into a shell script (invoked at bootup).
#
157
sub os_ifconfig_line($$$$$$$$;$$%)
158
{
159
    my ($iface, $inet, $mask, $speed, $duplex, $aliases, $iface_type, $lan,
160
	$settings, $rtabid, $cookie) = @_;
161
162
    my $media    = "";
    my $mediaopt = "";
163
    my ($uplines, $downlines);
164
165
166
167

    #
    # Need to check units on the speed. Just in case.
    #
Mike Hibler's avatar
Mike Hibler committed
168
169
170
171
172
173
174
175
176
177
178
179
    if (!INXENVM()) {
	if ($speed =~ /(\d*)([A-Za-z]*)/) {
	    if ($2 eq "Mbps") {
		$speed = $1;
	    }
	    elsif ($2 eq "Kbps") {
		$speed = $1 / 1000;
	    }
	    else {
		warn("*** Bad speed units $2 in ifconfig, default to 100Mbps\n");
		$speed = 100;
	    }
Mike Hibler's avatar
Mike Hibler committed
180
181
	    if ($speed == 10000) {
		# 10G is 10G, take it or leave it
182
		$media = $IFC_AUTO;
Mike Hibler's avatar
Mike Hibler committed
183
184
	    }
	    elsif ($speed == 1000) {
Mike Hibler's avatar
Mike Hibler committed
185
186
187
188
189
190
191
192
		$media = $IFC_1000MBS;
	    }
	    elsif ($speed == 100) {
		$media = $IFC_100MBS;
	    }
	    elsif ($speed == 10) {
		$media = $IFC_10MBS;
	    }
193
194
195
	    elsif ($speed == 0) {
		$media = $IFC_AUTO;
	    }
Mike Hibler's avatar
Mike Hibler committed
196
	    else {
197
198
199
		warn("*** Bad Speed $speed in ifconfig, default to autoconfig\n");
		$speed = 0;
		$media = $IFC_AUTO;
Mike Hibler's avatar
Mike Hibler committed
200
	    }
201
	}
Mike Hibler's avatar
Mike Hibler committed
202

203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
	if ($media eq $IFC_AUTO) {
	    $mediaopt = $IFC_ADUPLEX;
	} else {
	    if ($duplex eq "full") {
		if ($speed == 10000) {
		    # 10G is always full duplex, no need to set
		    $mediaopt = $IFC_ADUPLEX;
		} else {
		    $mediaopt = $IFC_FDUPLEX;
		}
	    }
	    elsif ($duplex eq "half") {
		$mediaopt = $IFC_HDUPLEX;
	    }
	    else {
		warn("*** Bad duplex $duplex in ifconfig, default to full\n");
		$duplex = "full";
Mike Hibler's avatar
Mike Hibler committed
220
221
		$mediaopt = $IFC_FDUPLEX;
	    }
222
223
224
	}
    }

225
    $uplines = "";
Ryan Jackson's avatar
Ryan Jackson committed
226

227
228
    if ($inet eq "") {
	$uplines .= "$IFCONFIGBIN $iface up $media $mediaopt";
229
    }
230
231
232
233
234
235
236
237
    else {
	#
	# Must set route table id before assigning IP address so that interface
	# route winds up in the correct table.
	#
	if (defined($rtabid)) {
	    $uplines .= "$IFCONFIGBIN $iface rtabid $rtabid\n    ";
	}
238

239
	# Config the interface.
240
241
	$uplines  .= sprintf($IFCONFIG, $iface, $inet, $mask,
			     $media, $mediaopt);
242
243
244
	# An interface underlying virtual interfaces does not go down.
	$downlines = "$IFCONFIGBIN $iface down"
	    if (defined($lan) && $lan ne "");
245
    }
246
247
248
249
    return ($uplines, $downlines);
}

#
250
# Specialized function for configing virtual ethernet devices:
251
#
252
253
254
255
256
#	'veth'	locally hacked veth devices
#	'vlan'	802.1q tagged vlan devices
#	'alias'	IP aliases on physical interfaces
#
sub os_ifconfig_veth($$$$$;$$$$$)
257
{
258
259
    my ($iface, $inet, $mask, $id, $vmac,
	$rtabid, $encap, $vtag, $itype, $cookie) = @_;
260
261
262
263
264
265
266
267
268
269
270
    my ($uplines, $downlines);

    #
    # Do not try this on the MFS!
    #
    return ""
	if (MFS());

    require Socket;
    import Socket;

271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
    if ($itype !~ /^(alias|vlan|veth)$/) {
	warn("Unknown virtual interface type $itype\n");
	return "";
    }

    #
    # IP aliases
    #
    if ($itype eq "alias") {
	# Must do this first to avoid lo0 routes.
	if (!exists($cookie->{"alias"})) {
	    $uplines =
	      "sysctl net.link.ether.inet.useloopback=0 >/dev/null 2>&1\n    ";
	    $cookie->{"alias"} = 1;
	}
Ryan Jackson's avatar
Ryan Jackson committed
286

287
288
289
290
	$uplines   .= sprintf($IFALIAS, $iface, $inet, $mask);
	$downlines .= "$IFCONFIGBIN $iface -alias $inet";

	return ($uplines, $downlines);
291
    }
292

293
294
295
296
297
298
299
300
301
302
303
304
305
    #
    # VLANs
    #
    if ($itype eq "vlan") {
	if (!defined($vtag)) {
	    warn("No vtag in veth config\n");
	    return "";
	}

	$uplines = "$IFCONFIGBIN vlan${id} create link " .
		   "vlan $vtag vlandev $iface\n    ";

	$encap = 1;
306
    }
307
308
309
310

    #
    # VETHs
    #
311
    else {
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
	if (!defined($vtag)) {
	    # Need to derive a vlan tag. Just use the middle two octets.
	    my $vtag = (unpack("I", inet_aton($inet)) >> 8) & 0xffff;
	}

	if ($vmac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
	    $vmac = "$1:$2:$3:$4:$5:$6";
	}
	else {
	    warn("Bad vmac in veth config: $vmac\n");
	    return "";
	}

	$uplines = "$IFCONFIGBIN veth${id} create\n    " .
		   "$IFCONFIGBIN veth${id} vethaddr $vmac/$vtag" .
		       (defined($iface) ? " vethdev $iface\n    " : "\n    ");
328
329
    }

330
331
332
333
    #
    # Must set route table id before assigning IP address so that interface
    # route winds up in the correct table.
    #
334
    if (defined($rtabid)) {
335
	$uplines .= "$IFCONFIGBIN ${itype}${id} rtabid $rtabid\n    ";
336
    }
337

338
    $uplines .= "$IFCONFIGBIN ${itype}${id} inet $inet netmask $mask";
339
340
341
342
343
344
345
346

    #
    # link1 on the veth device implies no encapsulation
    #
    if (!$encap) {
	$uplines .= " link1";
    }

347
348
    $downlines = "$IFCONFIGBIN ${itype}${id} destroy";

349
    return ($uplines, $downlines);
350
351
}

352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
#
# Compute the name of a virtual interface device based on the
# information in ifconfig hash (as returned by getifconfig).
#
sub os_viface_name($)
{
    my ($ifconfig) = @_;
    my $piface = $ifconfig->{"IFACE"};

    #
    # Physical interfaces use their own name
    #
    if (!$ifconfig->{"ISVIRT"}) {
	return $piface;
    }

    #
    # Otherwise we have a virtual interface: alias, veth, vlan.
    #
    # alias: There is no alias device, just use the phys device
    # veth:  veth<ID>
    # vlan:  vlan<ID>
    #
    my $itype = $ifconfig->{"ITYPE"};
    if ($itype eq "alias") {
	return $piface;
    } elsif ($itype =~ /^(vlan|veth)$/) {
	return $itype . $ifconfig->{"ID"};
    }

    warn("FreeBSD does not support virtual interface type '$itype'\n");
    return undef;
}

386
387
388
389
#
# Generate and return an string that is approriate for putting
# into /etc/hosts.
#
390
sub os_etchosts_line($$$)
391
{
392
    my ($name, $ip, $aliases) = @_;
Ryan Jackson's avatar
Ryan Jackson committed
393

394
    return sprintf("%s\t%s %s", $ip, $name, $aliases);
395
396
397
398
}

#
# Add a new group
Ryan Jackson's avatar
Ryan Jackson committed
399
#
400
401
402
403
404
405
406
sub os_groupadd($$)
{
    my($group, $gid) = @_;

    return system("$GROUPADD $group -g $gid");
}

407
408
#
# Delete an old group
Ryan Jackson's avatar
Ryan Jackson committed
409
#
410
411
412
413
414
415
416
sub os_groupdel($)
{
    my($group) = @_;

    return system("$GROUPDEL $group");
}

417
418
#
# Remove a user account.
Ryan Jackson's avatar
Ryan Jackson committed
419
#
420
421
422
423
424
425
426
427
428
sub os_userdel($)
{
    my($login) = @_;

    return system("$USERDEL $login");
}

#
# Modify user group membership.
Ryan Jackson's avatar
Ryan Jackson committed
429
#
430
sub os_usermod($$$$$$)
431
{
432
    my($login, $gid, $glist, $pswd, $root, $shell) = @_;
433
434
435
436
437
438
439

    if ($root) {
	$glist = join(',', split(/,/, $glist), "wheel");
    }
    if ($glist ne "") {
	$glist = "-G $glist";
    }
440
441
    # Map the shell into a full path.
    $shell = MapShell($shell);
442

443
    if (system("$CHPASS '$pswd' $login") != 0) {
444
445
446
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
447
    return system("$USERMOD $login -s $shell -g $gid $glist");
448
449
450
451
}

#
# Add a user.
Ryan Jackson's avatar
Ryan Jackson committed
452
#
453
sub os_useradd($$$$$$$$$)
454
{
455
    my($login, $uid, $gid, $pswd, $glist, $homedir, $gcos, $root, $shell) = @_;
456
    my $args = "";
457
458
459
460
461

    if ($root) {
	$glist = join(',', split(/,/, $glist), "wheel");
    }
    if ($glist ne "") {
462
463
464
465
466
	$args .= "-G $glist ";
    }
    # If remote, let it decide where to put the homedir.
    if (!REMOTE()) {
	$args .= "-d $homedir ";
467
468
469
470

	# Locally, if directory exists and is populated, skip -m
	# cause FreeBSD copies files in anyway!
	$args .= "-m "
471
	    if (! -d $homedir || ! -e "$homedir/.profile");
472
    }
473
474
475
476
477
478
479
    else {
	# populate on remote nodes. At some point will tar files over.
	$args .= "-m ";
    }

    # Map the shell into a full path.
    $shell = MapShell($shell);
480

481
    if (system("$USERADD $login -u $uid -g $gid $args ".
482
	       "-s $shell -c \"$gcos\"") != 0) {
483
484
485
	warn "*** WARNING: $USERADD $login error.\n";
	return -1;
    }
486
    chown($uid, $gid, $homedir);
487

488
    if (system("$CHPASS '$pswd' $login") != 0) {
489
490
491
492
493
494
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
    return 0;
}

495
496
#
# Modify user password
Ryan Jackson's avatar
Ryan Jackson committed
497
#
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
sub os_modpasswd($$)
{
    my($login, $pswd) = @_;

    if (system("$CHPASS '$pswd' $login") != 0) {
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
    if ($login eq "root" &&
	system("$CHPASS '$pswd' toor") != 0) {
	warn "*** WARNING: $CHPASS $login error.\n";
	return -1;
    }
    return 0;
}

514
515
516
517
518
519
520
521
#
# Remove a homedir. Might someday archive and ship back.
#
sub os_homedirdel($$)
{
    return 0;
}

522
523
524
525
526
527
528
529
530
531
532
533
534
#
# Create a directory including all intermediate directories.
#
sub os_mkdir($$)
{
    my ($dir, $mode) = @_;

    if (system("$MKDIR -p -m $mode $dir")) {
	return 0;
    }
    return 1;
}

535
#
Ryan Jackson's avatar
Ryan Jackson committed
536
537
# OS Dependent configuration.
#
538
539
sub os_setup()
{
540
    # This should never happen!
541
    if ((REMOTE() && !REMOTEDED()) || MFS()) {
542
543
	print "Ignoring os setup on remote/MFS node!\n";
	return 0;
544
    }
545
546
}

547
548
549
550
551
552
#
# OS dependent, routing-related commands
#
sub os_routing_enable_forward()
{
    my $cmd;
553
    my $fname = libsetup::ISDELAYNODEPATH();
554

555
    if (REMOTE() && !REMOTEDED()) {
556
557
	$cmd = "echo 'IP forwarding not turned on!'";
    }
558
559
560
    elsif (JAILED()) {
	$cmd = "# IP forwarding is enabled outside the jail";
    } else {
Ryan Jackson's avatar
Ryan Jackson committed
561
	# No Fast Forwarding when operating with linkdelays.
562
	$cmd  = "sysctl net.inet.ip.forwarding=1\n" .
563
	        "    if [ ! -e $fname ]; then\n" .
564
	        "        sysctl net.inet.ip.fastforwarding=1\n" .
565
		"    fi\n";
566
    }
567
568
569
    return $cmd;
}

570
sub os_routing_enable_gated($)
571
{
572
    my ($conffile) = @_;
573
574
    my $cmd;

575
    if (REMOTE() && !REMOTEDED()) {
576
577
578
	$cmd = "echo 'GATED IS NOT ALLOWED!'";
    }
    else {
579
	$cmd = "$GATED -f $conffile";
580
    }
581
582
583
    return $cmd;
}

584
sub os_routing_add_manual($$$$$;$)
585
{
586
    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
587
    my $cmd;
588
    my $rtabopt = (defined($rtabid) ? "-rtabid $rtabid" : "");
589
590

    if ($routetype eq "host") {
591
	$cmd = "$ROUTE add $rtabopt -host $destip $gate";
592
    } elsif ($routetype eq "net") {
593
	$cmd = "$ROUTE add $rtabopt -net $destip $gate $destmask";
594
595
    } elsif ($routetype eq "default") {
	$cmd = "$ROUTE add $rtabopt default $gate";
596
597
    } else {
	warn "*** WARNING: bad routing entry type: $routetype\n";
598
	$cmd = "false";
599
600
601
602
603
    }

    return $cmd;
}

604
sub os_routing_del_manual($$$$$;$)
605
{
606
    my ($routetype, $destip, $destmask, $gate, $cost, $rtabid) = @_;
607
    my $cmd;
608
    my $rtabopt = (defined($rtabid) ? "-rtabid $rtabid" : "");
609
610

    if ($routetype eq "host") {
611
	$cmd = "$ROUTE delete $rtabopt -host $destip";
612
    } elsif ($routetype eq "net") {
613
	$cmd = "$ROUTE delete $rtabopt -net $destip $gate $destmask";
614
615
    } elsif ($routetype eq "default") {
	$cmd = "$ROUTE delete $rtabopt default";
616
617
    } else {
	warn "*** WARNING: bad routing entry type: $routetype\n";
618
	$cmd = "false";
619
620
621
622
623
    }

    return $cmd;
}

624
625
626
627
628
629
630
631
632
633
# Map a shell name to a full path using /etc/shells
sub MapShell($)
{
   my ($shell) = @_;

   if ($shell eq "") {
       return $DEFSHELL;
   }

   my $fullpath = `grep '/${shell}\$' $SHELLS`;
Ryan Jackson's avatar
Ryan Jackson committed
634

635
636
637
638
639
640
641
642
643
644
645
646
647
648
   if ($?) {
       return $DEFSHELL;
   }

   # Sanity Check
   if ($fullpath =~ /^([-\w\/]*)$/) {
       $fullpath = $1;
   }
   else {
       $fullpath = $DEFSHELL;
   }
   return $fullpath;
}

Mike Hibler's avatar
Mike Hibler committed
649
650
651
652
# Return non-zero if given directory is on a "local" filesystem
sub os_islocaldir($)
{
    my ($dir) = @_;
Ryan Jackson's avatar
Ryan Jackson committed
653
    my $rv = 0;
Mike Hibler's avatar
Mike Hibler committed
654

655
656
657
658
659
660
661
662
663
664
665
666
667
668
    #
    # XXX 'df -l' doesn't do the right thing on older FreeBSD
    # so we compare what df returns to the mount table.  If the current
    # hack doesn't work, we will now err on the safe side and say the
    # homedir is remote, since our caller only gets destructive if the
    # homedir is local.
    #
    my $dfoutput = `$DF -l $dir | grep -v -i filesystem 2>/dev/null`;
    my $mdev = (split('\s+', $dfoutput))[0];
    if ($mdev) {
	my $mountout = `$MOUNT | grep $mdev`;
	if ($mountout && $mountout ne "" && $mountout !~ /\(nfs/) {
	    $rv = 1;
	}
Mike Hibler's avatar
Mike Hibler committed
669
670
671
672
    }
    return $rv;
}

673
#
Ryan Jackson's avatar
Ryan Jackson committed
674
675
# Find out what NFS mounts exist already!
#
676
677
678
679
680
681
sub os_getnfsmounts($)
{
    my ($rptr) = @_;
    my %mounted = ();

    #
Ryan Jackson's avatar
Ryan Jackson committed
682
    # Grab the output of the mount command and parse.
683
    #
684
    if (! open(MOUNT, "$MOUNT|")) {
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
	print "os_getnfsmounts: Cannot run mount command\n";
	return -1;
    }
    while (<MOUNT>) {
	if ($_ =~ /^([-\w\.\/:\(\)]+) on ([-\w\.\/]+) \((.*)\)$/) {
	    # Search for nfs string in the option list.
	    foreach my $opt (split(',', $3)) {
		if ($opt eq "nfs") {
		    $mounted{$1} = $2;
		}
	    }
	}
    }
    close(MOUNT);
    %$rptr = %mounted;
    return 0;
}

703
704
705
706
707
sub os_fwconfig_line($@)
{
    my ($fwinfo, @fwrules) = @_;
    my ($upline, $downline);

708
709
710
711
712
    if ($fwinfo->{TYPE} ne "ipfw" && $fwinfo->{TYPE} ne "ipfw2-vlan") {
	warn "*** WARNING: unsupported firewall type '", $fwinfo->{TYPE}, "'\n";
	return ("false", "false");
    }

713
714
715
    # XXX debugging
    my $logaccept = defined($fwinfo->{LOGACCEPT}) ? $fwinfo->{LOGACCEPT} : 0;
    my $logreject = defined($fwinfo->{LOGREJECT}) ? $fwinfo->{LOGREJECT} : 0;
716
    my $dotcpdump = defined($fwinfo->{LOGTCPDUMP}) ? $fwinfo->{LOGTCPDUMP} : 0;
717

Mike Hibler's avatar
Mike Hibler committed
718
719
720
721
722
723
724
725
726
727
728
729
730
731
    #
    # Convert MAC info to a useable form and filter out the firewall itself
    #
    my $href = $fwinfo->{MACS};
    while (my ($node,$mac) = each(%$href)) {
	if ($mac eq $fwinfo->{OUT_IF}) {
	    delete($$href{$node});
	} elsif ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
	    $$href{$node} = "$1:$2:$3:$4:$5:$6";
	} else {
	    warn "*** WARNING: Bad MAC returned for $node in fwinfo: $mac\n";
	    return ("false", "false");
	}
    }
732
733
734
735
736
737
738
739
740
741
742
    $href = $fwinfo->{SRVMACS};
    while (my ($node,$mac) = each(%$href)) {
	if ($mac eq $fwinfo->{OUT_IF}) {
	    delete($$href{$node});
	} elsif ($mac =~ /^(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})$/) {
	    $$href{$node} = "$1:$2:$3:$4:$5:$6";
	} else {
	    warn "*** WARNING: Bad MAC returned for $node in fwinfo: $mac\n";
	    return ("false", "false");
	}
    }
Mike Hibler's avatar
Mike Hibler committed
743

744
745
746
747
748
749
750
751
752
753
    #
    # VLAN enforced layer2 firewall with FreeBSD/IPFW2
    #
    if ($fwinfo->{TYPE} eq "ipfw2-vlan") {
	if (!defined($fwinfo->{IN_VLAN})) {
	    warn "*** WARNING: no VLAN for ipfw2-vlan firewall, NOT SETUP!\n";
	    return ("false", "false");
	}

	my $vlandev = "vlan0";
754
	my $vip;
755
756
757
758
759
760
761
762
763
764
	my $vlanno  = $fwinfo->{IN_VLAN};
	my $pdev    = `$BINDIR/findif $fwinfo->{IN_IF}`;
	chomp($pdev);

	$upline  = "ifconfig $vlandev create link vlan $vlanno vlandev $pdev\n";
	$upline .= "    if [ -z \"`sysctl net.link.ether.bridge 2>/dev/null`\" ]; then\n";
	$upline .= "        kldload bridge.ko >/dev/null 2>&1\n";
	$upline .= "    fi\n";
	$upline .= "    sysctl net.link.ether.bridge_vlan=0\n";
	$upline .= "    sysctl net.link.ether.bridge_ipfw=1\n";
Mike Hibler's avatar
Mike Hibler committed
765
	$upline .= "    sysctl net.link.ether.ipfw=0\n";
766
767
768
769
	$upline .= "    sysctl net.link.ether.bridge_cfg=$vlandev,$pdev\n";
	$upline .= "    if [ -z \"`sysctl net.inet.ip.fw.enable 2>/dev/null`\" ]; then\n";
	$upline .= "        kldload ipfw.ko >/dev/null 2>&1\n";
	$upline .= "    fi\n";
770

Mike Hibler's avatar
Mike Hibler committed
771
	#
772
773
774
775
776
777
778
779
780
781
782
	# Setup proxy ARP entries.
	#
	# Since we want to control which entries are proxied on inside/outside
	# we use our multiple routing table support and put the inside IF
	# into a different routing domain.  Then we can enter entries into
	# one routing table or the other.
	#
	# XXX this blows: in order to enter an ARP entry on the vlan0
	# interface, it has to be configured with an address in the same
	# subnet.  So we give vlan0 our same address (the beauty of separate
	# routing tables).  This *shouldn't* confuse anything on the firewall.
Mike Hibler's avatar
Mike Hibler committed
783
784
	#
	if (defined($fwinfo->{MACS})) {
785
	    my $myip = `cat $BOOTDIR/myip`;
786
	    chomp($myip);
787
	    my $mymask = `cat $BOOTDIR/mynetmask`;
788
	    chomp($mymask);
Mike Hibler's avatar
Mike Hibler committed
789

790
791
	    $upline .=
		"    ifconfig $vlandev inet $myip netmask $mymask rtabid 2\n";
Mike Hibler's avatar
Mike Hibler committed
792

793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
	    #
	    # XXX this blows II: for vnodes, we need to proxy arp the vnode
	    # network GW, so we have to give both devices a vnode addr
	    # alias as well! To give us a unique addr, we make sure that the
	    # vnode net is at least /16 and take the last two octets of the
	    # real address. This will work for Utah.
	    #
	    if (defined($fwinfo->{VARS}->{EMULAB_VGWIP}) &&
		defined($fwinfo->{VARS}->{EMULAB_VCNET})) {
		my $vgwip = $fwinfo->{VARS}->{EMULAB_VGWIP};
		my $bits = 0;
		if ($fwinfo->{VARS}->{EMULAB_VCNET} =~ /\/(\d+)$/) {
		    $bits = 32 - $1;
		}
		if ($bits >= 16) {
		    if ($vgwip =~ /^(\d+\.\d+)\.\d+\.\d+$/ && ($vip = $1) &&
			$myip =~ /^\d+\.\d+\.(\d+\.\d+)$/) {
			$vip .= ".$1";
			my $vmask =
			    sprintf "0x%08x", (~0 << $bits) & 0xFFFFFFFF;
			$upline .=
			    "    ifconfig $pdev alias $vip netmask $vmask\n";
			$upline .=
			    "    ifconfig $vlandev alias $vip netmask $vmask rtabid 2\n";
		    }
		}
	    }

821
822
823
824
825
826
827
828
	    # publish servers (including GW) on inside and for us on outside
	    if (defined($fwinfo->{SRVMACS})) {
		my $href = $fwinfo->{SRVMACS};
		while (my ($ip,$mac) = each %$href) {
		    $upline .= "    arp -r 2 -s $ip $mac pub only\n";
		    $upline .= "    arp -s $ip $mac\n";
		}
	    }
Mike Hibler's avatar
Mike Hibler committed
829

830
	    # provide node MACs to outside, and unpublished on inside for us
Mike Hibler's avatar
Mike Hibler committed
831
832
	    my $href = $fwinfo->{MACS};
	    while (my ($node,$mac) = each %$href) {
833
834
		$upline .= "    arp -s $node $mac pub only\n";
		$upline .= "    arp -r 2 -s $node $mac\n";
Mike Hibler's avatar
Mike Hibler committed
835
836
	    }
	}
837
	foreach my $rule (sort { $a->{RULENO} <=> $b->{RULENO}} @fwrules) {
838
839
840
841
842
843
844
845
846
847
	    my $rulestr = $rule->{RULE};
	    if ($logaccept && $rulestr =~ /^(allow|accept|pass|permit)\s.*/) {
		my $action = $1;
		$rulestr =~ s/$action/$action log/;
	    } elsif ($logreject && $rulestr =~ /^(deny|drop)\s.*/) {
		my $action = $1;
		$rulestr =~ s/$action/$action log/;
	    }

	    $upline .= "    ipfw add $rule->{RULENO} $rulestr || {\n";
848
	    $upline .= "        echo 'WARNING: could not load ipfw rule:'\n";
849
850
	    $upline .= "        echo '  $rulestr'\n";
	    $upline .= "        ipfw -q flush\n";
851
852
853
	    $upline .= "        exit 1\n";
	    $upline .= "    }\n";
	}
854
855
856
857
858
859
860
861

	if ($dotcpdump) {
	    $upline .= "    tcpdump -i $vlandev ".
		"-w $LOGDIR/in.tcpdump >/dev/null 2>&1 &\n";
	    $upline .= "    tcpdump -i $pdev ".
		"-w $LOGDIR/out.tcpdump not vlan >/dev/null 2>&1 &\n";
	}

862
863
864
	if ($logaccept || $logreject) {
	    $upline .= "    sysctl net.inet.ip.fw.verbose=1\n";
	}
865
866
867
868
	$upline .= "    sysctl net.inet.ip.fw.enable=1 || {\n";
	$upline .= "        echo 'WARNING: could not enable firewall'\n";
	$upline .= "        exit 1\n";
	$upline .= "    }\n";
869
870
	$upline .= "    sysctl net.link.ether.bridge=1";

871
872
873
874
875
876
877
878
879
880
881
882
883
	#
	# XXX maybe we should be more careful to ensure that the bridge
	# is really down before turning off the firewall.  OTOH, if
	# someone has really hacked the firewall to the extent that they
	# can prevent us from shutting down the bridge, then they should
	# be quite capable of taking down the firewall on their own.
	#
	$downline  = "sysctl net.link.ether.bridge=0 || {\n";
	$downline .= "        echo 'WARNING: could not disable bridge'\n";
	$downline .= "        echo '         firewall left enabled'\n";
	$downline .= "        exit 1\n";
	$downline .= "    }\n";
	$downline .= "    sysctl net.inet.ip.fw.enable=0\n";
884
885
886
	if ($dotcpdump) {
	    $downline .= "    killall tcpdump >/dev/null 2>&1\n";
	}
887
888
889
	if ($logaccept || $logreject) {
	    $downline .= "    sysctl net.inet.ip.fw.verbose=0\n";
	}
890
891
892
893
	$downline .= "    ipfw -q flush\n";
	$downline .= "    sysctl net.link.ether.bridge_cfg=\"\"\n";
	$downline .= "    sysctl net.link.ether.bridge_ipfw=0\n";
	$downline .= "    sysctl net.link.ether.bridge_vlan=1\n";
Mike Hibler's avatar
Mike Hibler committed
894
895
896
	if (defined($fwinfo->{MACS})) {
	    my $href = $fwinfo->{MACS};
	    while (my ($node,$mac) = each %$href) {
897
898
		$downline .= "    arp -d $node pub\n";
		$downline .= "    arp -r 2 -d $node\n";
Mike Hibler's avatar
Mike Hibler committed
899
900
	    }
	}
901
902
903
904
905
906
907
908
909
910
911
	if (defined($fwinfo->{SRVMACS})) {
	    my $href = $fwinfo->{SRVMACS};
	    while (my ($ip,$mac) = each %$href) {
		$downline .= "    arp -d $ip\n";
		$downline .= "    arp -r 2 -d $ip pub\n";
	    }
	}
	$downline .= "    ifconfig $vlandev destroy\n";
	if ($vip) {
	    $downline .= "    ifconfig $pdev -alias $vip";
	}
912
913
914
915
916
917
918
919
920
921

	return ($upline, $downline);
    }

    #
    # Voluntary IP firewall with FreeBSD/IPFW
    #
    $upline  = "if [ -z \"`sysctl net.inet.ip.fw.enable 2>/dev/null`\" ]; then\n";
    $upline .= "        kldload ipfw.ko >/dev/null 2>&1\n";
    $upline .= "    fi\n";
922

923
    foreach my $rule (sort { $a->{RULENO} <=> $b->{RULENO}} @fwrules) {
924
925
926
927
928
929
930
931
932
933
	my $rulestr = $rule->{RULE};
	if ($logaccept && $rulestr =~ /^(allow|accept|pass|permit)\s.*/) {
	    my $action = $1;
	    $rulestr =~ s/$action/$action log/;
	} elsif ($logreject && $rulestr =~ /^(deny|drop)\s.*/) {
	    my $action = $1;
	    $rulestr =~ s/$action/$action log/;
	}

	$upline .= "    ipfw add $rule->{RULENO} $rulestr || {\n";
934
	$upline .= "        echo 'WARNING: could not load ipfw rule:'\n";
935
936
	$upline .= "        echo '  $rulestr'\n";
	$upline .= "        ipfw -q flush\n";
937
938
939
	$upline .= "        exit 1\n";
	$upline .= "    }\n";
    }
940
941
942
943
    $upline .= "    sysctl net.inet.ip.fw.enable=1 || {\n";
    $upline .= "        echo 'WARNING: could not enable firewall'\n";
    $upline .= "        exit 1\n";
    $upline .= "    }\n";
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
    $upline .= "    sysctl net.inet.ip.redirect=0\n";
    $upline .= "    sysctl net.inet.ip.forwarding=1";

    $downline  = "sysctl net.inet.ip.forwarding=0\n";
    $downline .= "    sysctl net.inet.ip.redirect=1\n";
    $downline .= "    ipfw -q flush\n";
    $downline .= "    kldunload ipfw.ko >/dev/null 2>&1";

    return ($upline, $downline);
}

sub os_fwrouteconfig_line($$$)
{
    my ($orouter, $fwrouter, $routestr) = @_;
    my ($upline, $downline);

    #
    # XXX assume the original default route should be used to reach servers.
    #
    # For setting up the firewall, this means we create explicit routes for
    # each host via the original default route.
    #
    # For tearing down the firewall, we just remove the explicit routes
    # and let them fall back on the now re-established original default route.
    #
    $upline  = "for vir in $routestr; do\n";
    $upline .= "        $ROUTE -q delete \$vir >/dev/null 2>&1\n";
    $upline .= "        $ROUTE -q add \$vir $orouter || {\n";
    $upline .= "            echo \"Could not establish route for \$vir\"\n";
    $upline .= "            exit 1\n";
    $upline .= "        }\n";
    $upline .= "    done";

    $downline  = "for vir in $routestr; do\n";
    $downline .= "        $ROUTE -q delete \$vir >/dev/null 2>&1\n";
    $downline .= "    done";

    return ($upline, $downline);
}

984
sub os_config_gre($$$$$$$;$)
985
{
986
987
988
989
990
991
992
993
994
995
996
997
998
    my ($name, $unit, $inetip, $peerip, $mask, $srchost, $dsthost, $tag) = @_;

    if (GENVNODE() && GENVNODETYPE() eq "openvz") {
	$dev = "gre$unit";

	if (system("$IFCONFIGBIN $dev $inetip netmask $mask mtu 1472 up")) {
	    warn("Could not start tunnel $dev!\n");
	    return -1;
	}
	return 0;
    }
    require Socket;
    import Socket;
Ryan Jackson's avatar
Ryan Jackson committed
999

1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
    my $gre = `ifconfig gre create`;
    if ($?) {
	warn("*** Could not create a new gre device.\n");
	return -1;
    }
    if ($gre =~ /^(gre\d*)$/) {
	$gre = $1;
    }
    else {
	warn("*** Cannot parse gre device '$gre'\n");
	return -1;
    }
    if (system("ifconfig $gre $inetip $peerip link1 netmask $mask up") ||
	system("ifconfig $gre tunnel $srchost $dsthost")) {
	warn("Could not start tunnel!\n");
	return -1;
    }
1017
1018
1019
1020
1021
1022
1023
1024
1025
    if (defined($tag)) {
	my $grekey = inet_ntoa(pack("N", $tag));

	system("ifconfig $gre grekey $grekey");
	if ($?) {
	    warn("Could not add key to $gre interface!\n");
	    return -1;
	}
    }
1026
1027
1028
    return 0;
}

1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
sub os_get_disks
{
    my @disks;
    my $dmesgpat = "^([a-z]+\\d+):.* (\\d+)MB.*\$";

    for my $cmd ("/sbin/dmesg", "cat /var/run/dmesg.boot") {
	    my @cmdout = `$cmd`;
	    foreach my $line (@cmdout) {
		if ($line =~ /$dmesgpat/) {
		    my $name = $1;

		    push @disks, $name;
		}
	    }

	    last if (@disks);
    }

    return @disks;
}

Ryan Jackson's avatar
Ryan Jackson committed
1050
1051
1052
1053
1054
sub os_get_ctrlnet_ip
{
	my $iface;
	my $address;

1055
1056
1057
1058
1059
1060
1061
	# just use recorded IP if available
	if (-e "$BOOTDIR/myip") {
	    $myip = `cat $BOOTDIR/myip`;
	    chomp($myip);
	    return $myip;
	}

Ryan Jackson's avatar
Ryan Jackson committed
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
	if (!open CONTROLIF, "$BOOTDIR/controlif") {
		return undef;
	}

	$iface = <CONTROLIF>;
	chomp $iface;

	$iface =~ /(.*)/;
	$iface = $1;

	close CONTROLIF;

	if (!open IFCFG, "$IFCONFIGBIN $iface|") {
		return undef;
	}

	while (<IFCFG>) {
		if (/inet ([0-9.]*) /) {
			$address = $1;
			last;
		}
	}

	close IFCFG;

	return $address;
}

1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
sub os_get_disk_size($)
{
    my ($disk) = @_;
    my $size;

    $disk =~ s#^/dev/##;

    my @cmdout = `$cmd`;

    my $dmesgpat = "^($disk\\d+):.* (\\d+)MB.*\$";
    foreach my $line (@cmdout) {
	if ($line =~ /$dmesgpat/) {
	    my $size = $2;
	    last;
	}
    }

    return $size;
}

sub os_get_partition_info($$)
{
    my ($bootdev, $partition) = @_;

    if (!open(FDISK, "fdisk -s $bootdev |")) {
	print("Failed to run fdisk on $bootdev!");
	return -1;
    }

    # First line looks like "/dev/ad0: 5005 cyl 255 hd 63 sec"
    my $line = <FDISK>;
    if (!defined($line)) {
	print("No fdisk summary info for MBR on $bootdev!\n");
	goto bad;
    }
    if (! ($line =~ /^.*cyl (\d*) hd (\d*) sec/)) {
	print("Invalid fdisk summary info for MBR on $bootdev!\n");
	goto bad;
    }
    while (<FDISK>) {
	if ($_ =~ /^\s*(\d):\s*\d*\s*(\d*)\s*(0x[\w]*)\s*0x[\w]*$/) {
	    if ($1 == $partition) {
		my $plen = $2;
		my $ptype = hex($3);
		close(FDISK);
		return ($plen, $ptype);
	    }
	}
    }
    print("No such partition in fdisk summary info for MBR on $bootdev!\n");
  bad:
    close(FDISK);
    return -1;
}

1145
sub os_nfsmount($$$)
1146
{
1147
1148
    my ($remote,$local,$transport) = @_;
    my $opts = "-b";
1149

1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
    # XXX backward compat: use default
    if (!defined($transport)) {
	;
    }
    elsif ($transport eq "TCP") {
	$opts .= ",tcp";
    }
    elsif ($transport eq "UDP") {
	$opts .= ",udp";
    }
    elsif ($transport eq "osdefault") {
	;
    }

    if (system("/sbin/mount -o $opts $remote $local")) {
1165
1166
1167
1168
1169
1170
	return 1;
    }

    return 0;
}

1171
1172
1173
1174
1175
#
# Create/mount a local filesystem on the extra partition if it hasn't
# already been done.  Returns the resulting mount point (which may be
# different than what was specified as an argument if it already existed).
#
1176
1177
1178
1179
# Note that in FreeBSD, the extra partition is either:
#    XXd0s4a (FreeBSD 10+)
#    XXd0s4e (FreeBSD 9-)
#
1180
1181
1182
1183
1184
sub os_mountextrafs($)
{
    my $dir = shift;
    my $mntpt = "";
    my $log = "$VARDIR/logs/mkextrafs.log";
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
    my $disk = "";
    my $part = "";

    #
    # If the extrafs file was written, use the info from there.
    #
    my $extrafs = libsetup::TMEXTRAFS();
    if (-f "$extrafs" && open(FD, "<$extrafs")) {
	my $line = <FD>;
	close(FD);
	chomp($line);
	if ($line =~ /^PART=(.*)/) {
	    $part = $1;
	    goto makeit;
	}
	if ($line =~ /^DISK=(.*)/) {
	    $disk = $1;
	    goto makeit;
	}
	if ($line =~ /^FS=(.*)/) {
	    $mntpt = $1;
	}

        return $mntpt;
    }
1210
1211
1212
1213
1214

    #
    # XXX this is a most bogus hack right now, we look for partition 4
    # in /etc/fstab.
    #
1215
1216
    my $fstabline = `grep '0s4[ae]' /etc/fstab`;
    if ($fstabline =~ /^\/dev\/\S*0s4[ae]\s+(\S+)\s+/) {
1217
	$mntpt = $1;
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
	return $mntpt;
    }

makeit:
    my $args = "-f";

    if ($part) {
	if ($part =~ /^(\D+\d+)[sp](\d+)$/) {
	    $args .= " -r $1 -s $2";
	}
    } elsif ($disk) {
	$args .= " -r $disk -s 0";
    }

    if (!system("$BINDIR/mkextrafs.pl $args $dir >$log 2>&1")) {
1233
1234
1235
1236
	$mntpt = $dir;
    } else {
	print STDERR "mkextrafs failed, see $log\n";
    }
1237

1238
1239
1240
    return $mntpt;
}

1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
#
# Find unused disk space on a machine.
#
# A spare disk or disk partition is one whose partition ID is zero and is
# "inactive" (not mounted or in use as a swap partition).
#
# If $allparts is non-zero, then we also consider inactive partitions with
# non-zero IDs.
#
# This function returns a hash of:
#   device name (disk) => slice/part number => size (bytes);
# note that a device name => size entry is filled IF the device has no
# partitions.
#
# XXX assumes MBR and not GPT.  GPT-based disks might well appear available,
# we should fix that!
#
sub os_find_freedisk($$)
{
    my ($minsize,$allparts) = @_;
    my %diskinfo = ();

    #
    # Use sysctl to locate disks.
    #
    my @disks = split /\s+/, `sysctl -n kern.disks 2>/dev/null`;
    return %diskinfo if ($?);
    print STDERR "disks: ", join('/', @disks), "\n" if ($debug);

    #
    # Capture all mounted devices
    #
    my %mounts;
    if (!open(FD, "/sbin/mount|")) {
Mike Hibler's avatar
Mike Hibler committed
1275
	print STDERR "find_freedisk: cannot read mount info\n";
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
	return %diskinfo;
    }
    while (<FD>) {
	if (/^\/dev\/(\S+)/) {
	    my $dev = $1;
	    $mounts{$dev} = 1;
	    # full <disk><slice><part> syntax, mark components as "mounted"
	    if ($dev =~ /^(\D+\d+)(s\d+)([a-z])$/) {
		$mounts{"$1$2"} = 2;
		$mounts{$1} = 2;
	    }
	    # <disk><slice> syntax, ditto
	    elsif ($dev =~ /^(\D+\d+)(s\d+)$/) {
		$mounts{$1} = 2;
	    }
	}
    }
    close(FD);

    #
    # Count active swap partitions as mounted
    #
    if (!open(FD, "/usr/sbin/swapinfo|")) {
Mike Hibler's avatar
Mike Hibler committed
1299
	print STDERR "find_freedisk: cannot read swap info\n";
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
	return %diskinfo;
    }
    while (<FD>) {
	if (/^\/dev\/(\S+)/) {
	    my $dev = $1;
	    $mounts{$dev} = 1;
	    # full <disk><slice><part> syntax, mark components as "mounted"
	    if ($dev =~ /^(\D+\d+)(s\d+)([a-z])$/) {
		$mounts{"$1$2"} = 2;
		$mounts{$1} = 2;
	    }
	    # <disk><slice> syntax, ditto
	    elsif ($dev =~ /^(\D+\d+)(s\d+)$/) {
		$mounts{$1} = 2;
	    }
	}
    }
    close(FD);

    print STDERR "found mounts: ", join('/', sort keys %mounts), "\n" if ($debug);

    #
Ryan Jackson's avatar
Ryan Jackson committed
1322
    # For each disk, find any unused space
1323
1324
1325
1326
1327
1328
1329
    #
    foreach my $disk (sort @disks) {
	#
	# Find size of disk
	#
	my $dinfo = `diskinfo $disk 2>/dev/null`;
	if ($?) {
Mike Hibler's avatar
Mike Hibler committed
1330
	    print STDERR "find_freedisk: $disk: could not open\n";
1331
1332
1333
1334
1335
	    next;
	}
	my ($dname,$dsecsize,$dbytes,$dsects) = split /\s+/, $dinfo;
	if ($dname ne $disk ||
	    !defined($dsecsize) || !defined($dbytes) || !defined($dsects)) {
Mike Hibler's avatar
Mike Hibler committed
1336
	    print STDERR "find_freedisk: $disk: could not parse diskinfo\n";
1337
1338
1339
1340
1341
1342
1343
1344
	    next;
	}
	print STDERR "$disk: has $dsects $dsecsize byte sectors\n" if ($debug);

	#
	# See how it is partitioned.
	#
	if (!open(FD, "fdisk -s $disk 2>&1|")) {
Mike Hibler's avatar
Mike Hibler committed
1345
	    print STDERR "find_freedisk: $disk: could not get partitions\n";
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
	    next;
	}
	while (<FD>) {
	    #
	    # No MBR should mean that the entire disk is unused.
	    # But we make sure there are no mounts anywhere on the
	    # disk just to be safe.
	    #
	    if (/invalid fdisk partition table found/ &&
		!defined($mounts{$disk})) {
		print STDERR "$disk: disk: size=$dbytes\n" if ($debug);
Mike Hibler's avatar
Mike Hibler committed
1357
1358
1359
1360
		if ($dbytes >= $minsize