exports_setup.in 21.4 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh Stoller's avatar
Leigh Stoller committed
2
#
3
# Copyright (c) 2000-2015 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 27 28 29 30 31

#
# 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
#
32 33
# This script always does the right thing, so it does not matter who calls it. 
#
34 35 36
sub usage()
{
    print(STDERR
37
	  "Usage: exports_setup [-B] [-i] [-n] [-d] [-w]\n".
38
	  "switches and arguments:\n".
39
	  "-B         - just create the list of valid mounts for boss (does not affect the fs exports file or mountd)\n".
40
	  "-i         - incremental (differential) update (if mounted supports it)\n".
41
	  "-w         - wait mode; wait for mountd to finish before exiting\n".
42 43 44
	  "-n         - impotent; dump exports to stdout\n");
    exit(-1);
}
45 46
my $optlist    = "Bindw";
my $bosslistonly = 0;
47 48
my $impotent   = 0;
my $debug      = 0;
49
my $waittildone= 0;
50
my $incremental= 0;
51

52 53 54 55 56 57 58
#
# Function phototypes
#

sub fsinit();
sub fsof($);

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

# XXX for TESTMODE: output to stdout
my $TOSTDOUT	= 0;
if ($TOSTDOUT) {
    $TESTMODE = 1;
}

88
# Note no -n option. We redirect stdin from the new exports file below.
89
my $SSH		= "$TB/bin/sshtb -l root -host $FSNODE";
90
my $PROG	= "$TB/sbin/exports_setup.proxy";
91
my $exportstail = "/var/tmp/exports.tail";
92
my $smbconftail = "/var/tmp/smbconf.tail";
93
my $bossmountfile = "$TB/etc/validmounts.txt";
94
my @row; 
95

96
# For determining file server mountpoints (XXX BSD specific)
97 98
my $MOUNTPROG	= ($LINUX_FSNODE ? "/bin/mount" : "/sbin/mount");
# Need the linux equiv for this.
99 100 101 102 103 104 105 106 107 108
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.
#
if ($ZFS_NOEXPORT) {
    $EXPORT_PAT = q(on ([\S]+)\s\\\([uz]fs,.*\\\));
}
109

110 111 112
# Cache of dir to FS mappings already found
my %fsofcache;

113 114
#
# Testbed doesn't support changing exports file
115
# or we just do not export filesystems.
116
#
117
if ($DISABLED || $NOSHAREDFS) {
118 119 120
    exit(0);
}

121 122 123 124
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
Leigh Stoller's avatar
Leigh Stoller committed
125 126
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
127 128
}
# XXX Hacky!
129
if (0 && $TB ne "/usr/testbed") {
130 131
    print STDERR "*** $0:\n".
	         "    Wrong version. Maybe its a development version?\n";
Leigh Stoller's avatar
Leigh Stoller committed
132 133 134
    #
    # Let experiment continue setting up.
    # 
135
    exit(0);
Leigh Stoller's avatar
Leigh Stoller committed
136
}
137 138 139 140 141

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

142 143 144 145
#
# Turn off line buffering on output
#
$| = 1;
146

147
#
148
# Testbed Support libraries
149
# 
150 151 152
use lib "@prefix@/lib";
use libdb;
use libtestbed;
153
use Data::Dumper;
154

155 156 157
my $PROJROOT  = PROJROOT();
my $GROUPROOT = GROUPROOT();
my $USERROOT  = USERROOT();
158
my $SCRATCHROOT  = SCRATCHROOT();
159

160 161 162 163 164 165 166
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
167 168 169
if (defined($options{"B"})) {
    $bosslistonly = 1;
}
170 171 172 173 174 175
if (defined($options{"n"})) {
    $impotent = 1;
}
if (defined($options{"d"})) {
    $debug = 1;
}
176 177 178
if (defined($options{"w"})) {
    $waittildone = 1;
}
179
if (defined($options{"i"})) {
180 181 182 183 184
    if ($INC_MOUNTD) {
	$incremental = 1;
    } else {
	print "WARNING: incremental updates not supported, ignoring option\n";
    }
185
}
186 187 188
usage()
    if (@ARGV);

189 190 191 192 193 194
if ($bosslistonly) {
    if (!$WITHZFS || $WITHAMD) {
	print STDERR "-B only makes sense with ZFS and not AMD\n";
	exit(1);
    }
} elsif ($ZFS_NOEXPORT && !$waittildone) {
195 196
    print "WARNING: forcing wait mode\n";
    $waittildone = 1;
197 198 199 200
    if ($INC_MOUNTD) {
	print "WARNING: forcing incremental updates\n";
	$incremental = 1;
    }
201 202
}

203
#
204
# We need to serialize this script to avoid a trashed map file.
205
#
206
if (!$TESTMODE) {
207
    if ((my $locked = TBScriptLock("exports", 0)) != TBSCRIPTLOCK_OKAY()) {
208 209 210
	exit(0)
	    if ($locked == TBSCRIPTLOCK_IGNORE);
	fatal("Could not get the lock after a long time!\n");
211 212 213 214 215 216
    }
}

#
# We stick the new map entries into the tail file. First zero it out.
#
217 218
if (!$TESTMODE) {
  open(MAP, ">$exportstail") || fatal("Couldn't open $exportstail\n");
219 220 221
  if ($WINSUPPORT) {
      open(SMBMAP, ">$smbconftail") || fatal("Couldn't open $smbconftail\n");
  }
222 223 224 225 226
} 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");
  }
227 228
} else {
  open(MAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
229 230 231
  if ($WINSUPPORT) {
      open(SMBMAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
  }
232
}
233 234 235 236 237 238 239 240 241

my $maphdr = 
    "\n".
    "#\n".
    "# DO NOT EDIT below this point. Auto generated entries!\n".
    "#\n".
    "\n";

print MAP $maphdr;
242 243 244
if ($WINSUPPORT) {
    print SMBMAP $maphdr;
}
245 246

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

267 268 269 270 271 272 273 274 275 276 277
#
# 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.
    #
278
    my $limit = 3600 * 24 * 7;
279 280 281 282 283 284

    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 ".
285
		     "left join projects as p on p.pid=m.pid ".
286
		     "where u.status='" . USERSTATUS_ACTIVE() . "' and ".
287
		     "      p.approved=1 and ".
288 289 290 291 292
		     "      (((UNIX_TIMESTAMP(now()) - ".
		     "        UNIX_TIMESTAMP(s.last_activity)) <= $limit) or ".
		     "       ((UNIX_TIMESTAMP(now()) - ".
		     "        UNIX_TIMESTAMP(s.weblogin_last)) <= $limit) or ".
		     "       admin=1)");
293 294 295 296 297
    while (my ($uid,$pid) = $active_result->fetchrow_array()) {
	$bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
	$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
	$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
    }
298 299 300 301 302 303
    #
    # 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 =
304 305
	DBQueryFatal("select p.pid from projects as p ".
		     "left join project_stats as s on s.pid=p.pid ".
306
		     "where ((UNIX_TIMESTAMP(now()) - ".
307 308
		     "       UNIX_TIMESTAMP(s.last_activity)) <= $limit) and ".
		     "      p.approved=1");
309 310 311 312
    while (my ($pid) = $active_result->fetchrow_array()) {
	$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
	$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
    }
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329

    #
    # 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";
    }
330 331
}

332 333
my %ipgroups    = ();
my %globalsmbshares   = ();
334
my %lastfslist  = ();
335
my @lastsmbshares = ();
336 337
my $lastpid     = "";
my $lastgid     = "";
338
my $lastadmin	= "";
339
my $lasterole   = "";
340

341 342
my @mountpoints = fsinit();

343 344 345 346 347
# 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
348
#
349 350
# Note that we could do this per experiment rather than per node,
# adding all nodes from an experiment to the sublists created.
351 352 353 354 355 356 357
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'};
358 359
    my $isvirt  = $row->{'isvirtnode'};
    my $shared  = (defined($row->{'sharing_mode'}) ? 1 : 0);
360
    my $erole   = $row->{'erole'};
361 362
    my $enonfs  = $row->{'enonfs'};
    my $nnonfs  = $row->{'nnonfs'};
363 364
    my %fslist = ();
    my @dirlist = ();
365
    my @smbshares = ();
366

367 368 369
    # 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)) ||
370
        (!defined($gid)) || (!defined($admin)) || (!defined($ip))) {
371
        print "WARNING: exports_setup: Skipping database row with undefined values\n";
372
	print "         $node_id\n" if defined($node_id);
373 374
	next;
    }
375

376 377 378 379
    # Skip nodes that belong to a "no nfs" experiment or are marked "no nfs".
    next
	if ($enonfs || $nnonfs);

380 381 382
    # Skip non-shared virtnode nodes; NFS mounts are handled differently.
    next
	if ($isvirt && !$shared);
383
    
384 385
    if ($lastpid eq $pid && $lastgid eq $gid && $lasterole eq $erole &&
	(!$ISOLATEADMIN || $lastadmin eq $admin)) {
386 387
	# If this is for the same proj and group again, don't requery the db 
	# and don't recompute everything.
388 389
	%fslist    = %lastfslist;
        @smbshares = @lastsmbshares;
390

391 392 393
    } else {
	$lastpid=$pid;
	$lastgid=$gid;
394
	$lastadmin=$admin;
395
	$lasterole=$erole;
396

397
	if ($erole eq "sharedhost" && !$isvirt && !$WITHZFS) {
398
	    #
399 400 401
	    # Shared local *physical* nodes get toplevel mounts.
	    #
	    # ZFS does not support toplevel mounts. 
402 403
	    #
	    push(@dirlist, "$projdir");
404
	    push(@dirlist, "$groupdir");
405 406 407
	    push(@dirlist, "$scratchdir")
		if ($scratchdir && -d "$SCRATCHROOT");
	    push(@dirlist, "$usersdir");
408 409
	}
	else {
410 411 412
	    # Construct a list of directories accessible from this node.
	    # First the project and group directories.
	    # XXX needs to be fixed for shared experiments?
413 414
	    push(@dirlist, "$projdir/$pid");
	    push(@smbshares, ["proj-$pid", "$projdir/$pid"]);
415 416
	
	    if ($gid ne $pid) {
417 418
		push(@dirlist, "$groupdir/$pid/$gid");
		push(@smbshares, ["${pid}-${gid}", "$groupdir/$pid/$gid"]);
419
	    }
420 421 422 423
	    if ($ZFS_NOEXPORT && $gid eq $pid) {
		$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
		$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
	    }
424

425
	    if ($scratchdir) {
426 427 428
		push(@dirlist, "$scratchdir/$pid");
		push(@smbshares, ["scratch-$pid", "$scratchdir/$pid"]);
	    }
429

430 431 432 433 434 435 436 437 438
	    # 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 ";
439
	    }
440 441 442 443 444 445 446 447 448 449 450 451 452 453

	    $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];

454 455
		push(@dirlist, "$usersdir/$uid");
		push(@smbshares, [$uid, "$usersdir/$uid"]);
456 457 458 459

		if ($ZFS_NOEXPORT) {
		    $bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
		}
460
	    }
461
	}
462
      skip:
463 464 465 466 467

	# Build up filesystem sub-lists.
	# Iterate through directory list dividing it according to filesystem.
	foreach my $dir ( @dirlist ) {
	    my $fs = fsof($dir);
468 469 470 471
	    if (!$fs) {
		print "WARNING: no filesystem for '$dir', ignored\n";
		next;
	    }
472 473 474 475 476 477 478

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

480
	}
481 482
	%lastfslist    = %fslist;
	@lastsmbshares = @smbshares;
483
    }
484

485 486 487 488 489 490 491
    # 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} }));
492

493 494 495 496 497 498
	if (! defined($ipgroups{$str})) {
	    $ipgroups{$str} = [ $ip ];
	}
	else {
	    push(@{ $ipgroups{$str} }, $ip);
	}
499
    }
500 501

    # Build up Samba share permissions hash
502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517
    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);
518 519 520
            }
        }
    }
521 522
}

523 524 525 526 527 528 529 530 531
#
# 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 = ();

532 533 534 535 536 537 538 539
# 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]);
}

540
#
541
# Now spit out each group!
542
#
543
foreach my $str ( keys(%ipgroups) ) {
544
    my @iplist = sort sortbyip @{ $ipgroups{$str} };
545

546 547 548 549 550 551
    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 {
552 553
	print MAP "$str -maproot=$NFSMAPTOUSER @iplist\n";
	print "$str -maproot=$NFSMAPTOUSER @iplist\n"
554 555
	    if ($debug);
    }
556
}
557
if ($ZFS_NOEXPORT) {
558 559 560 561 562 563
    # Build up filesystem sub-lists.
    # Iterate through directory list dividing it according to filesystem.
    my %bosslists = ();
    
    foreach my $dir (keys(%bossexports)) {
	my $fs = fsof($dir);
564 565 566 567
	if (!$fs) {
	    print "WARNING: no filesystem for '$dir', ignored\n";
	    next;
	}
568

569 570 571 572 573 574
	if (! defined($bosslists{$fs})) {
	    $bosslists{$fs} = [ $dir ];
	}
	else {
	    push(@{ $bosslists{$fs} }, $dir);
	}
575
    }
576 577 578 579 580 581 582 583 584 585 586
    foreach my $fs (keys(%bosslists)) {
	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"
587
	    if ($debug);
588
	}
589 590 591 592 593

	# remember all valid mount points
	if ($bossdirlist) {
	    push(@bossmounts, $fs);
	}
594 595
    }
}
596 597 598

close(MAP);

599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
#
# 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,".
617
	    " boss will not mount anything!\n";
618 619 620 621 622 623
	if (!$TESTMODE) {
	    unlink("bossmountfile.new", $bossmountfile);
	}
    }
}

624 625 626 627 628 629 630 631 632 633 634 635 636
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);
}

637 638 639
#
# Spit out smb shares!
#
640 641
if ($WINSUPPORT) {
    foreach my $share ( keys(%globalsmbshares) ) {
642
        my @iplist = sort sortbyip @{ $globalsmbshares{$share}->{iplist} };
643 644 645 646 647 648 649 650
        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";
    }
651

652 653 654
    print SMBMAP "\n";
    close(SMBMAP);
}
655

656 657 658 659
#
# Fire the new tail file over to the fileserver to finish. We cat the file
# right into it.
#
660 661
if (!$TESTMODE) {
  $UID = 0;
662 663 664
  #
  # Temp Hack! Save a copy of the exports file for debugging.
  #
665
  if ($debug) {
666 667
      my $backup = "$TB/log/exports/" . TBDateTimeFSSafe();
      system("cp $exportstail $backup");
668 669 670
      if ($WINSUPPORT) {
          system("cp $smbconftail $backup");
      }
671
  }
672 673
  if ($impotent) {
      system("/bin/cat $exportstail");
674 675 676
      if ($WINSUPPORT) {
	  system("/bin/cat $smbconftail");
      }
677 678 679
      if ($bossdirlist) {
	  system("/bin/cat $bossmountfile.new");
      }
680 681
  }
  else {
682 683
      my $arg = ($incremental ? "-i" : "");

684
      # First do the NFS exports
685
      system("$SSH $PROG $arg < $exportstail") == 0 or
686 687
	  fatal("Failed: $SSH $PROG < $exportstail: $?");
      unlink("$exportstail");
688

689 690 691 692 693 694
      # Next the SMB shares
      if ($WINSUPPORT) {
	  system("$SSH $PROG -S < $smbconftail") == 0 or
	      fatal("Failed: $SSH $PROG < $smbconftail: $?");
	  unlink("$smbconftail");
      }
695 696 697 698 699 700 701

      #
      # 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) {
702 703 704
	  my $testdir = "/proj/" . TBOPSPID();
	  TBDebugTimeStampsOn();
	  TBDebugTimeStamp("Waiting for mountd to finish...");
705 706 707 708 709 710 711 712
	  my $tries = 10;
	  while ($tries-- > 0) {
	      if (system("/bin/ls $testdir >/dev/null 2>&1")) {
		  sleep(1);
		  next;
	      }
	      last;
	  }
713
	  TBDebugTimeStamp("Wait done");
714
      }
715 716 717 718 719 720 721

      #
      # Move the new boss autofs mount map in place.
      #
      if ($bossdirlist) {
	  rename("$bossmountfile.new", $bossmountfile);
      }
722
  }
723

724
  #
725
  # Release the lock!
726
  #
727
  TBScriptUnlock();
728
}
729 730 731 732 733 734

exit(0);

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

735 736 737
    TBScriptUnlock()
	if (!$TESTMODE);
    
738
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);
739 740
    die($msg);
}
741

742
#
743 744 745
# 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.
746
#
747 748 749
sub fsinit() {
    my @rawmounts;

750 751 752 753 754 755
    my $saveuid = $UID;
    $UID = 0;
    my $mountinfo = `$SSH $MOUNTPROG`;
    $UID = $saveuid;

    foreach my $mount (split('\n', $mountinfo)) {
756 757 758 759 760 761 762 763 764
	if ($mount =~ /$EXPORT_PAT/) {
	    push(@rawmounts, $1);
	}
    }

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

765
#
766 767
# Return a unique (per-FS) string identifying the filesystem of
# the given path.
768 769 770
#
sub fsof($) {
    my($path) = @_;
771

772 773 774
    if (exists($fsofcache{$path})) {
	return $fsofcache{$path};
    }
775
    foreach my $mount (@mountpoints) {
776 777
	if (index($path, $mount) == 0) {
	    $fsofcache{$path} = $mount;
778 779 780 781 782
	    return $mount;
	}
    }
    print "WARNING: exports_setup: could not find FS for $path\n";
    return "";
783
}