exports_setup.in 23.8 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh Stoller's avatar
Leigh Stoller committed
2
#
3
# Copyright (c) 2000-2019 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 Stoller's avatar
Leigh Stoller committed
23
#
24
use English;
25
use Getopt::Std;
26
use Sys::Syslog;
27 28 29 30 31 32

#
# Create an /etc/exports.tail file based on current reserved table and project
# members. Fire that tail over to the fileserver where it is concatenated with
# the head file to become the new /etc/exports
#
33 34
# This script always does the right thing, so it does not matter who calls it. 
#
35 36 37
sub usage()
{
    print(STDERR
38
	  "Usage: exports_setup [-B] [-i] [-n] [-d] [-w]\n".
39
	  "switches and arguments:\n".
40
	  "-B         - just create the list of valid mounts for boss (does not affect the fs exports file or mountd)\n".
41
	  "-i         - incremental (differential) update (if mounted supports it)\n".
42
	  "-w         - wait mode; wait for mountd to finish before exiting\n".
43 44 45
	  "-n         - impotent; dump exports to stdout\n");
    exit(-1);
}
46 47
my $optlist    = "Bindw";
my $bosslistonly = 0;
48 49
my $impotent   = 0;
my $debug      = 0;
50
my $waittildone= 0;
51
my $incremental= 0;
52

53 54 55 56 57 58
#
# Function phototypes
#

sub fsinit();
sub fsof($);
59
sub logit($);
60

61 62 63 64 65
#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
66
my $TESTMODE    = @TESTMODE@;
67
my $BOSSNODE    = "@BOSSNODE@";
68
my $FSNODE      = "@FSNODE@";
69 70 71
my $projdir     = "@FSDIR_PROJ@";
my $usersdir    = "@FSDIR_USERS@";
my $groupdir    = "@FSDIR_GROUPS@";
72
my $scratchdir  = "@FSDIR_SCRATCH@";
73
my $DISABLED	= "@DISABLE_EXPORTS_SETUP@";
74
my $WINSUPPORT  = @WINSUPPORT@;
75
my $ISOLATEADMIN= @ISOLATEADMINS@;
76
my $NOSHAREDFS	= @NOSHAREDFS@;
77
my $LINUX_FSNODE= @LINUX_FSNODE@;
78
my $NFSMAPTOUSER= "@NFSMAPTOUSER@";
79
my $WITHZFS     = @WITHZFS@;
80
my $ZFS_NOEXPORT= @ZFS_NOEXPORT@;
81
my $OPSVM_ENABLE= @OPSVM_ENABLE@;
82
my $WITHAMD     = @WITHAMD@;
83
my $INC_MOUNTD  = @INCREMENTAL_MOUNTD@;
84
my $NOVNODENFS	= @NOVIRTNFSMOUNTS@;
85
my $TBLOG	= "@TBLOGFACIL@";
86
my $NFSMFSROOT	= "@NFSMFS_ROOT@";
87

88 89 90 91
# XXX for the moment we still allow Windows support without Samba,
# but Windows support means Samba support in this script.
$WINSUPPORT = 0;

92 93 94 95 96 97
# XXX for TESTMODE: output to stdout
my $TOSTDOUT	= 0;
if ($TOSTDOUT) {
    $TESTMODE = 1;
}

98
# Note no -n option. We redirect stdin from the new exports file below.
99
my $SSH		= "$TB/bin/sshtb -l root -host $FSNODE";
100
my $PROG	= "$TB/sbin/exports_setup.proxy";
101
my $exportstail = "/var/tmp/exports.tail";
102
my $smbconftail = "/var/tmp/smbconf.tail";
103
my $bossmountfile = "$TB/etc/validmounts.txt";
104
my @row; 
105

106
# For determining file server mountpoints (XXX BSD specific)
107 108
my $MOUNTPROG	= ($LINUX_FSNODE ? "/bin/mount" : "/sbin/mount");
# Need the linux equiv for this.
109 110 111 112 113 114 115
my $EXPORT_PAT	= q(on ([\S]+)\s\\\(.*NFS exported.*\\\));

#
# XXX If there are no static exports (to boss) for filesystems, then
# "NFS exported" will not be set. Instead we just look for all mounted
# ufs or zfs filesystems.
#
116
if ($ZFS_NOEXPORT || ($OPSVM_ENABLE && $WITHZFS)) {
117 118
    $EXPORT_PAT = q(on ([\S]+)\s\\\([uz]fs,.*\\\));
}
119

120 121 122
# Cache of dir to FS mappings already found
my %fsofcache;

123 124
#
# Testbed doesn't support changing exports file
125
# or we just do not export filesystems.
126
#
127
if ($DISABLED || $NOSHAREDFS) {
128 129 130
    exit(0);
}

131 132 133 134
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
Leigh Stoller's avatar
Leigh Stoller committed
135 136
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
137 138
}
# XXX Hacky!
139
if (0 && $TB ne "/usr/testbed") {
140 141
    print STDERR "*** $0:\n".
	         "    Wrong version. Maybe its a development version?\n";
Leigh Stoller's avatar
Leigh Stoller committed
142 143 144
    #
    # Let experiment continue setting up.
    # 
145
    exit(0);
Leigh Stoller's avatar
Leigh Stoller committed
146
}
147 148 149 150 151

# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

152 153 154 155
#
# Turn off line buffering on output
#
$| = 1;
156

157
#
158
# Testbed Support libraries
159
# 
160 161 162
use lib "@prefix@/lib";
use libdb;
use libtestbed;
163
use Data::Dumper;
164

165 166 167
my $PROJROOT  = PROJROOT();
my $GROUPROOT = GROUPROOT();
my $USERROOT  = USERROOT();
168
my $SCRATCHROOT  = SCRATCHROOT();
169

170 171 172 173 174 175 176
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
177 178 179
if (defined($options{"B"})) {
    $bosslistonly = 1;
}
180 181 182 183 184 185
if (defined($options{"n"})) {
    $impotent = 1;
}
if (defined($options{"d"})) {
    $debug = 1;
}
186 187 188
if (defined($options{"w"})) {
    $waittildone = 1;
}
189
if (defined($options{"i"})) {
190
    $incremental = 1;
191
}
192 193 194
usage()
    if (@ARGV);

195 196 197 198 199 200 201
# Setup syslog early to record warnings
openlog("exports_setup", "pid", $TBLOG);

if ($incremental && !$INC_MOUNTD) {
    logit("WARNING: incremental updates not supported, ignoring option");
    $incremental = 0;
}
202 203 204 205 206 207
if ($bosslistonly) {
    if (!$WITHZFS || $WITHAMD) {
	print STDERR "-B only makes sense with ZFS and not AMD\n";
	exit(1);
    }
} elsif ($ZFS_NOEXPORT && !$waittildone) {
208
    logit("WARNING: forcing wait mode");
209
    $waittildone = 1;
210
    if ($INC_MOUNTD) {
211
	logit("WARNING: forcing incremental updates");
212 213
	$incremental = 1;
    }
214 215
}

216
#
217
# We need to serialize this script to avoid a trashed map file.
218
#
219
if (!$TESTMODE) {
220
    if ((my $locked = TBScriptLock("exports", 0)) != TBSCRIPTLOCK_OKAY()) {
221 222 223
	exit(0)
	    if ($locked == TBSCRIPTLOCK_IGNORE);
	fatal("Could not get the lock after a long time!\n");
224 225 226 227 228 229
    }
}

#
# We stick the new map entries into the tail file. First zero it out.
#
230 231
if (!$TESTMODE) {
  open(MAP, ">$exportstail") || fatal("Couldn't open $exportstail\n");
232 233 234
  if ($WINSUPPORT) {
      open(SMBMAP, ">$smbconftail") || fatal("Couldn't open $smbconftail\n");
  }
235 236 237 238 239
} elsif ($TOSTDOUT) {
  open(MAP, ">/dev/stdout") || fatal("Couldn't open /dev/stdout\n");
  if ($WINSUPPORT) {
      open(SMBMAP, ">/dev/stdout") || fatal("Couldn't open /dev/stdout\n");
  }
240 241
} else {
  open(MAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
242 243 244
  if ($WINSUPPORT) {
      open(SMBMAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
  }
245
}
246

247
#
248
# First gather up all the nodes that are reserved and the required info.
249 250
# Order by pid,gid,admin first so that they're all grouped together and we
# avoid extra db queries (see lastpid/lastgid/lastadmin).
251
#
252
$nodes_result =
253 254
    DBQueryFatal("select r.node_id,r.pid,r.eid,e.gid,".
		 "       e.nonfsmounts as enonfs,n.nonfsmounts as nnonfs,".
255
		 "       i.IP,u.admin,r.sharing_mode,r.erole,nt.isvirtnode, ".
256
		 "       e.nfsmounts as e_nfsmounts, ".
257
		 "       n.nfsmounts as n_nfsmounts, ".
258 259
		 "       va.attrvalue as routable_ip, ".
		 "       n.op_mode,n.next_op_mode ".
260
		 "from reserved as r ".
261
		 "left join experiments as e on r.pid=e.pid and r.eid=e.eid ".
262 263
		 "left join nodes as n on r.node_id=n.node_id ".
		 "left join node_types as nt on nt.type=n.type ".
264
		 "left join interfaces as i on r.node_id=i.node_id ".
265
		 "left join users as u on e.swapper_idx=u.uid_idx ".
266 267 268
		 "left join virt_node_attributes as va on ".
		 "  va.pid=r.pid and va.eid=r.eid and va.vname=r.vname ".
		 "    and va.attrkey='routable_control_ip' ".
269 270
		 " where i.IP!='NULL' and ".
		 "       i.role='" . TBDB_IFACEROLE_CONTROL() . "' ".
271
		 "       and (n.role='testnode' or n.role='virtnode')".
272
		 "       and nt.isremotenode=0 ".
273
		 "order by r.pid,e.gid,r.eid,u.admin,n.priority");
274

275 276 277 278 279 280 281 282 283 284 285
#
# ZFS, without automatic exports of all /users; need to explicitly export
# user directories to boss. First get all web active and admins, and below
# we will add all other user exports to the list.
#
my $bossexports = ();

if ($WITHZFS && $ZFS_NOEXPORT) {
    #
    # Find all web active users within last seven days, plus all admins.
    #
286
    my $limit = 3600 * 24 * 7;
287 288 289 290 291 292

    my $active_result =
	DBQueryFatal("select distinct u.uid,m.pid from user_stats as s ".
		     "left join users as u on u.uid_idx=s.uid_idx ".
		     "left join group_membership as m on ".
		     "     m.uid_idx=u.uid_idx and m.pid=m.gid ".
293
		     "left join projects as p on p.pid=m.pid ".
294
		     "where u.status='" . USERSTATUS_ACTIVE() . "' and ".
295
		     "      p.approved=1 and ".
296 297 298 299 300
		     "      (((UNIX_TIMESTAMP(now()) - ".
		     "        UNIX_TIMESTAMP(s.last_activity)) <= $limit) or ".
		     "       ((UNIX_TIMESTAMP(now()) - ".
		     "        UNIX_TIMESTAMP(s.weblogin_last)) <= $limit) or ".
		     "       admin=1)");
301 302 303 304 305
    while (my ($uid,$pid) = $active_result->fetchrow_array()) {
	$bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
	$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
	$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
    }
306 307 308 309 310 311
    #
    # Similar to above, but look for active projects instead of users.
    # We often have to access project directories when there is
    # no active experiment or user. 
    #
    $active_result =
312 313
	DBQueryFatal("select p.pid from projects as p ".
		     "left join project_stats as s on s.pid=p.pid ".
314
		     "where ((UNIX_TIMESTAMP(now()) - ".
315 316
		     "       UNIX_TIMESTAMP(s.last_activity)) <= $limit) and ".
		     "      p.approved=1");
317 318 319 320
    while (my ($pid) = $active_result->fetchrow_array()) {
	$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
	$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
    }
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337

    #
    # Well, this is to catch nonlocal users who are not members of a
    # project yet. 
    #
    $active_result =
	DBQueryFatal("select u.uid from user_stats as s ".
		     "left join users as u on u.uid_idx=s.uid_idx ".
		     "where u.status='" . USERSTATUS_ACTIVE() . "' and ".
		     "      u.nonlocal_id is not null and ".
		     "      (((UNIX_TIMESTAMP(now()) - ".
		     "        UNIX_TIMESTAMP(s.last_activity)) <= $limit) or ".
		     "       ((UNIX_TIMESTAMP(now()) - ".
		     "        UNIX_TIMESTAMP(s.weblogin_last)) <= $limit))");
    while (my ($uid) = $active_result->fetchrow_array()) {
	$bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
    }
338 339
}

340 341
my %ipgroups    = ();
my %globalsmbshares   = ();
342
my %lastfslist  = ();
343
my @lastsmbshares = ();
344 345
my $lastpid     = "";
my $lastgid     = "";
346
my $lastadmin	= "";
347
my $lasterole   = "";
348
my @nfsmfsnodes = ();
349

350 351
my @mountpoints = fsinit();

352 353 354 355 356
# For each node:
#	determine the list of directories accessible
#	split the list into sublists based on filesystems
#	  (i.e., all directories in the same FS are in the same sublist)
#	add the node to each sublist
357
#
358 359
# Note that we could do this per experiment rather than per node,
# adding all nodes from an experiment to the sublists created.
360 361 362 363 364 365 366
while ($row = $nodes_result->fetchrow_hashref) {
    my $node_id = $row->{'node_id'};
    my $pid     = $row->{'pid'};
    my $eid     = $row->{'eid'};
    my $gid     = $row->{'gid'};
    my $ip      = $row->{'IP'};
    my $admin	= $row->{'admin'};
367 368
    my $isvirt  = $row->{'isvirtnode'};
    my $shared  = (defined($row->{'sharing_mode'}) ? 1 : 0);
369
    my $erole   = $row->{'erole'};
370 371
    my $enonfs  = $row->{'enonfs'};
    my $nnonfs  = $row->{'nnonfs'};
372 373
    my $enfs    = $row->{'e_nfsmounts'};
    my $nnfs    = $row->{'n_nfsmounts'};
374 375
    my $routable= ((defined($row->{'routable_ip'}) &&
		    $row->{'routable_ip'} eq "true") ? 1 : 0);
376 377
    my %fslist = ();
    my @dirlist = ();
378
    my @smbshares = ();
379

380 381 382
    # Sanity check - don't try this if any of the above are not defined - we
    # may end up with a bad line in exports
    if ((!defined($node_id)) || (!defined($pid)) || (!defined($eid)) ||
383
        (!defined($gid)) || (!defined($admin)) || (!defined($ip))) {
384 385
        logit("WARNING: skipping database row with undefined values".
	      defined($node_id) ? " ($node_id)" : "");
386 387
	next;
    }
388

389 390 391 392 393 394 395 396 397 398 399
    # If we support NFS-based MFS, remember nodes that need an MFS exported
    # XXX we do this before eliminating non-nfs experiment nodes
    if ($NFSMFSROOT && $WITHZFS && $ZFS_NOEXPORT) {
	if (defined($row->{'op_mode'}) && defined($row->{'next_op_mode'}) &&
	    ($row->{'op_mode'} eq "PXEFBSD" ||
	     $row->{'next_op_mode'} eq "PXEFBSD") &&
	    TBNodeNFSAdmin($node_id)) {
	    push(@nfsmfsnodes, $node_id);
	}
    }

400 401
    # Skip nodes that belong to a "no nfs" experiment or are marked "no nfs".
    next
402 403
	if ($enonfs || $nnonfs || $enfs eq "none" ||
	    (defined($nnfs) && $nnfs eq "none"));
404

405 406 407
    # Skip non-shared virtnode nodes; NFS mounts are handled differently.
    next
	if ($isvirt && !$shared);
408
    
409
    # Skip shared virtnode nodes too, if NFS disabled and non-routable IP
410
    next
411
	if ($NOVNODENFS && $isvirt && $shared && !$routable);
412

413 414
    if ($lastpid eq $pid && $lastgid eq $gid && $lasterole eq $erole &&
	(!$ISOLATEADMIN || $lastadmin eq $admin)) {
415 416
	# If this is for the same proj and group again, don't requery the db 
	# and don't recompute everything.
417 418
	%fslist    = %lastfslist;
        @smbshares = @lastsmbshares;
419

420 421 422
    } else {
	$lastpid=$pid;
	$lastgid=$gid;
423
	$lastadmin=$admin;
424
	$lasterole=$erole;
425

426
	if ($erole eq "sharedhost" && !$isvirt && !$WITHZFS) {
427
	    #
428 429 430
	    # Shared local *physical* nodes get toplevel mounts.
	    #
	    # ZFS does not support toplevel mounts. 
431 432
	    #
	    push(@dirlist, "$projdir");
433
	    push(@dirlist, "$groupdir");
434 435 436
	    push(@dirlist, "$scratchdir")
		if ($scratchdir && -d "$SCRATCHROOT");
	    push(@dirlist, "$usersdir");
437 438
	}
	else {
439 440 441
	    # Construct a list of directories accessible from this node.
	    # First the project and group directories.
	    # XXX needs to be fixed for shared experiments?
442 443
	    push(@dirlist, "$projdir/$pid");
	    push(@smbshares, ["proj-$pid", "$projdir/$pid"]);
444 445
	
	    if ($gid ne $pid) {
446 447
		push(@dirlist, "$groupdir/$pid/$gid");
		push(@smbshares, ["${pid}-${gid}", "$groupdir/$pid/$gid"]);
448
	    }
449 450 451 452
	    if ($ZFS_NOEXPORT && $gid eq $pid) {
		$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
		$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
	    }
453

454
	    if ($scratchdir) {
455 456 457
		push(@dirlist, "$scratchdir/$pid");
		push(@smbshares, ["scratch-$pid", "$scratchdir/$pid"]);
	    }
458

459 460 461 462 463 464 465 466 467
	    # Determine the users that can access this node, and add those
	    # users' directories to the list.
	    # XXX needs to be fixed for shared experiments?
	    #
	    # Note that if we are isolating admins, only those users with
	    # the same admin status as the swapper are allowed.
	    my $adminclause = "";
	    if ($ISOLATEADMIN) {
		$adminclause = "u.admin=$admin and ";
468
	    }
469 470 471 472 473 474 475 476 477 478 479 480 481 482

	    $users_result =
		DBQueryFatal("select distinct ".
			     " g.uid from group_membership as g ".
			     "left join users as u on u.uid_idx=g.uid_idx ".
			     "where g.pid='$pid' and g.gid='$gid' and ".
			     "      (g.trust!='none' and ".
			     "       u.webonly=0 and ".
			     "       $adminclause ".
			     "       u.status='" . USERSTATUS_ACTIVE() . "')");

	    while (@usersrow = $users_result->fetchrow_array) {
		my $uid = $usersrow[0];

483 484
		push(@dirlist, "$usersdir/$uid");
		push(@smbshares, [$uid, "$usersdir/$uid"]);
485 486 487 488

		if ($ZFS_NOEXPORT) {
		    $bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
		}
489
	    }
490
	}
491
      skip:
492 493 494 495 496

	# Build up filesystem sub-lists.
	# Iterate through directory list dividing it according to filesystem.
	foreach my $dir ( @dirlist ) {
	    my $fs = fsof($dir);
497
	    if (!$fs) {
498
		logit("WARNING: no filesystem for '$dir', ignored");
499 500
		next;
	    }
501 502 503 504 505 506 507

	    if (! defined($fslist{$fs})) {
		$fslist{$fs} = [ $dir ];
	    }
	    else {
		push(@{ $fslist{$fs} }, $dir);
	    }
508

509
	}
510 511
	%lastfslist    = %fslist;
	@lastsmbshares = @smbshares;
512
    }
513

514 515 516 517 518 519 520
    # For each FS directory list, create a hash key out of its directory list.
    foreach my $fs ( keys(%fslist) ) {
	#
	# Convert dirlist to a string and use that as a hash index to group
	# IPs together with the same set of mounts.
	#
	my $str = join(" ", sort(@{ $fslist{$fs} }));
521

522 523 524 525 526 527
	if (! defined($ipgroups{$str})) {
	    $ipgroups{$str} = [ $ip ];
	}
	else {
	    push(@{ $ipgroups{$str} }, $ip);
	}
528
    }
529 530

    # Build up Samba share permissions hash
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
    if ($WINSUPPORT) {
        foreach my $shareptr ( @smbshares ) {
            my ($share, $path) = @{$shareptr};
            if (! defined($globalsmbshares{$share}->{iplist})) {
                $globalsmbshares{$share}->{path}   = $path;
                $globalsmbshares{$share}->{iplist} = [ $ip ];
            }
            else {
                # Make sure there are no share name collisions first!
                if ($globalsmbshares{$share}->{path} ne $path) {
                    fatal("Share name collision!\n".
                          "sharename:     $share\n".
                          "original path: $globalsmbshares{$share}->{path}\n".
                          "new path:      $path\n");
                }
                push(@{ $globalsmbshares{$share}->{iplist} }, $ip);
547 548 549
            }
        }
    }
550 551
}

552 553 554 555 556 557 558 559 560
#
# When using FreeBSD autofs on boss, we generate a list of valid directories
# that can be mounted. We use this in an executable map script to avoid
# attempting (and failing after several seconds) to mount arbitrary paths
# under /users, /proj, and /groups.
#
my $bossdirlist = ($WITHZFS && !$WITHAMD) ? 1 : 0;
my @bossmounts = ();

561 562 563 564 565 566 567 568
# just cuz
sub sortbyip {
    my @ao = split('\.', $a);
    my @bo = split('\.', $b);
    return ($ao[0] <=> $bo[0] || $ao[1] <=> $bo[1] ||
	    $ao[2] <=> $bo[2] || $ao[3] <=> $bo[3]);
}

569
#
570
# Now spit out each group!
571
#
572
foreach my $str (sort(keys(%ipgroups))) {
573
    my @iplist = sort sortbyip @{ $ipgroups{$str} };
574

575 576 577 578 579 580
    if ($LINUX_FSNODE) {
	print MAP "$str -rw,no_root_squash,no_subtree_check @iplist\n";
	print "$str -rw,no_root_squash,no_subtree_check @iplist\n"
	    if ($debug);
    }
    else {
581 582
	print MAP "$str -maproot=$NFSMAPTOUSER @iplist\n";
	print "$str -maproot=$NFSMAPTOUSER @iplist\n"
583 584
	    if ($debug);
    }
585
}
586
if ($ZFS_NOEXPORT) {
587 588 589 590
    # Build up filesystem sub-lists.
    # Iterate through directory list dividing it according to filesystem.
    my %bosslists = ();
    
591
    foreach my $dir (sort(keys(%bossexports))) {
592
	my $fs = fsof($dir);
593
	if (!$fs) {
594
	    logit("WARNING: no filesystem for '$dir', ignored");
595 596
	    next;
	}
597

598 599 600 601 602 603
	if (! defined($bosslists{$fs})) {
	    $bosslists{$fs} = [ $dir ];
	}
	else {
	    push(@{ $bosslists{$fs} }, $dir);
	}
604
    }
605
    foreach my $fs (sort(keys(%bosslists))) {
606 607 608 609 610 611 612 613 614 615
	my $str  = join(" ", @{ $bosslists{$fs} });

	if ($LINUX_FSNODE) {
	    print MAP "$str -rw,no_root_squash,no_subtree_check $BOSSNODE\n";
	    print "$str -rw,no_root_squash,no_subtree_check $BOSSNODE\n"
		if ($debug);
	}
	else {
	    print MAP "$str -maproot=$NFSMAPTOUSER $BOSSNODE\n";
	    print "$str -maproot=$NFSMAPTOUSER $BOSSNODE\n"
616
	    if ($debug);
617
	}
618 619 620 621 622

	# remember all valid mount points
	if ($bossdirlist) {
	    push(@bossmounts, $fs);
	}
623
    }
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641

    #
    # If we have any NFS-based MFS filesystems, export those.
    #
    foreach my $node (sort(@nfsmfsnodes)) {
	my $str = "$NFSMFSROOT/$node";

	if ($LINUX_FSNODE) {
	    print MAP "$str -rw,no_root_squash,no_subtree_check $node\n";
	    print "$str -rw,no_root_squash,no_subtree_check $node\n"
		if ($debug);
	}
	else {
	    print MAP "$str -maproot=$NFSMAPTOUSER $node\n";
	    print "$str -maproot=$NFSMAPTOUSER $node\n"
		if ($debug);
	}
    }
642
}
643 644
close(MAP);

645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
#
# Spit out an autofs map if needed. We will move this into place once we
# know that the fs node has updated its exports.
#
# If $ZFS_NOEXPORT, we use info from %bossexports that we computed above
# Otherwise, we use @mountpoints which is export info we got from ops
#
if ($bossdirlist) {
    if (open(BM, ">$bossmountfile.new")) {
	my $list = $ZFS_NOEXPORT ? \@bossmounts : \@mountpoints;
	foreach my $dir (sort @$list) {
	    print BM "$dir\n"
		if ($dir ne "/");
	}
	close(BM);
    } else {
	$bossdirlist = 0;
	print STDERR "*** $0: WARNING: could not update $bossmountfile,".
663
	    " boss will not mount anything!\n";
664 665 666 667 668 669
	if (!$TESTMODE) {
	    unlink("bossmountfile.new", $bossmountfile);
	}
    }
}

670 671 672 673 674 675 676 677 678 679 680 681 682
if ($bosslistonly) {
    if (!$TESTMODE && $bossdirlist) {
	if ($impotent) {
	    system("/bin/cat $bossmountfile.new");
	} else {
	    rename("$bossmountfile.new", $bossmountfile);
	}
	unlink("$exportstail", "$smbconftail", "$bossmountfile.new");
	TBScriptUnlock();
    }
    exit(0);
}

683 684 685
#
# Spit out smb shares!
#
686
if ($WINSUPPORT) {
687
    foreach my $share (sort(keys(%globalsmbshares))) {
688
        my @iplist = sort sortbyip @{ $globalsmbshares{$share}->{iplist} };
689 690 691 692 693 694 695 696
        my $path   = $globalsmbshares{$share}->{path};

        print SMBMAP "[$share]\n";
        print SMBMAP "\tpath        = $path\n";
        print SMBMAP "\tbrowsable   = no\n";
        print SMBMAP "\twritable    = yes\n";
        print SMBMAP "\thosts allow = @iplist\n\n";
    }
697

698 699 700
    print SMBMAP "\n";
    close(SMBMAP);
}
701

702 703 704 705
#
# Fire the new tail file over to the fileserver to finish. We cat the file
# right into it.
#
706 707
if (!$TESTMODE) {
  $UID = 0;
708 709 710
  #
  # Temp Hack! Save a copy of the exports file for debugging.
  #
711
  if ($debug) {
712 713
      my $backup = "$TB/log/exports/" . TBDateTimeFSSafe();
      system("cp $exportstail $backup");
714 715 716
      if ($WINSUPPORT) {
          system("cp $smbconftail $backup");
      }
717
  }
718 719
  if ($impotent) {
      system("/bin/cat $exportstail");
720 721 722
      if ($WINSUPPORT) {
	  system("/bin/cat $smbconftail");
      }
723 724 725
      if ($bossdirlist) {
	  system("/bin/cat $bossmountfile.new");
      }
726
  }
727 728 729 730 731 732 733
  elsif ($OPSVM_ENABLE) {
      logit("Invoking exports_setup.proxy on boss ...");
      system("$PROG < $exportstail") == 0 or
	  fatal("Failed: $PROG < $exportstail: $?");
      logit("exports_setup.proxy done");
      unlink("$exportstail");
  }
734
  else {
735 736
      my $arg = ($incremental ? "-i" : "");

737
      # First do the NFS exports
738
      logit("Invoking exports_setup.proxy...");
739
      system("$SSH $PROG $arg < $exportstail") == 0 or
740
	  fatal("Failed: $SSH $PROG < $exportstail: $?");
741
      logit("exports_setup.proxy done");
742
      unlink("$exportstail");
743

744 745
      # Next the SMB shares
      if ($WINSUPPORT) {
746
	  logit("Invoking exports_setup.proxy (samba)...");
747 748
	  system("$SSH $PROG -S < $smbconftail") == 0 or
	      fatal("Failed: $SSH $PROG < $smbconftail: $?");
749
	  logit("exports_setup.proxy (samba) done");
750 751
	  unlink("$smbconftail");
      }
752 753 754 755 756 757 758

      #
      # Attempt to see if mountd is done. The theory:
      #   with older mountd's ls will fail,
      #   with newer mountd's ls will hang til mountd is done.
      #
      if ($waittildone) {
759
	  my $testdir = "/proj/" . TBOPSPID();
760
	  logit("Waiting for mountd to finish...");
761 762 763 764 765 766 767 768
	  my $tries = 10;
	  while ($tries-- > 0) {
	      if (system("/bin/ls $testdir >/dev/null 2>&1")) {
		  sleep(1);
		  next;
	      }
	      last;
	  }
769
	  logit("Wait done");
770
      }
771 772

      #
773
      # Move the new boss autofs mount map in place and flush the cache.
774 775 776
      #
      if ($bossdirlist) {
	  rename("$bossmountfile.new", $bossmountfile);
777 778 779
	  if (-x "/usr/sbin/automount") {
	      system("/usr/sbin/automount -c");
	  }
780
      }
781
  }
782

783
  #
784
  # Release the lock!
785
  #
786
  TBScriptUnlock();
787
}
788 789 790 791 792 793

exit(0);

sub fatal {
    local($msg) = $_[0];

794 795 796
    TBScriptUnlock()
	if (!$TESTMODE);
    
797
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);
798 799
    die($msg);
}
800

801
#
802 803 804
# Get mount info from the FS node and use that to determine actual mount
# points for exported filesystems.  We generate a list of mount points from
# longest to shortest so that we will match the most specific one in fsof.
805
#
806 807 808
sub fsinit() {
    my @rawmounts;

809 810 811 812 813 814
    my $saveuid = $UID;
    $UID = 0;
    my $mountinfo = `$SSH $MOUNTPROG`;
    $UID = $saveuid;

    foreach my $mount (split('\n', $mountinfo)) {
815 816 817 818 819 820 821 822 823
	if ($mount =~ /$EXPORT_PAT/) {
	    push(@rawmounts, $1);
	}
    }

    sub revlen { length($b) <=> length($a) };
    return sort revlen @rawmounts;
}

824
#
825 826
# Return a unique (per-FS) string identifying the filesystem of
# the given path.
827 828 829
#
sub fsof($) {
    my($path) = @_;
830

831 832 833
    if (exists($fsofcache{$path})) {
	return $fsofcache{$path};
    }
834
    foreach my $mount (@mountpoints) {
835 836
	if (index($path, $mount) == 0) {
	    $fsofcache{$path} = $mount;
837 838 839
	    return $mount;
	}
    }
840
    logit("WARNING: could not find FS for $path");
841
    return "";
842
}
843 844 845 846 847 848 849 850 851 852 853 854

sub logit($)
{
    my ($message) = @_;
    syslog("info", $message);
    print "$message\n"
	if ($debug);
}

END {
    closelog();
}