All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

exports_setup.in 23.7 KB
Newer Older
1
#!/usr/bin/perl -w
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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@";
Kirk Webb's avatar
 
Kirk Webb committed
74
my $WINSUPPORT  = @WINSUPPORT@;
Mike Hibler's avatar
Mike Hibler committed
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 92 93

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

94
# Note no -n option. We redirect stdin from the new exports file below.
95
my $SSH		= "$TB/bin/sshtb -l root -host $FSNODE";
Kirk Webb's avatar
 
Kirk Webb committed
96
my $PROG	= "$TB/sbin/exports_setup.proxy";
97
my $exportstail = "/var/tmp/exports.tail";
Kirk Webb's avatar
 
Kirk Webb committed
98
my $smbconftail = "/var/tmp/smbconf.tail";
99
my $bossmountfile = "$TB/etc/validmounts.txt";
100
my @row; 
101

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

116 117 118
# Cache of dir to FS mappings already found
my %fsofcache;

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

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

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

148 149 150 151
#
# Turn off line buffering on output
#
$| = 1;
152

153
#
154
# Testbed Support libraries
155
# 
156 157 158
use lib "@prefix@/lib";
use libdb;
use libtestbed;
159
use Data::Dumper;
160

161 162 163
my $PROJROOT  = PROJROOT();
my $GROUPROOT = GROUPROOT();
my $USERROOT  = USERROOT();
164
my $SCRATCHROOT  = SCRATCHROOT();
165

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

191 192 193 194 195 196 197
# 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;
}
198 199 200 201 202 203
if ($bosslistonly) {
    if (!$WITHZFS || $WITHAMD) {
	print STDERR "-B only makes sense with ZFS and not AMD\n";
	exit(1);
    }
} elsif ($ZFS_NOEXPORT && !$waittildone) {
204
    logit("WARNING: forcing wait mode");
205
    $waittildone = 1;
206
    if ($INC_MOUNTD) {
207
	logit("WARNING: forcing incremental updates");
208 209
	$incremental = 1;
    }
210 211
}

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

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

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

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

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

    #
    # 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";
    }
334 335
}

Kirk Webb's avatar
 
Kirk Webb committed
336 337
my %ipgroups    = ();
my %globalsmbshares   = ();
338
my %lastfslist  = ();
Kirk Webb's avatar
 
Kirk Webb committed
339
my @lastsmbshares = ();
340 341
my $lastpid     = "";
my $lastgid     = "";
342
my $lastadmin	= "";
343
my $lasterole   = "";
344
my @nfsmfsnodes = ();
345

346 347
my @mountpoints = fsinit();

348 349 350 351 352
# 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
353
#
354 355
# Note that we could do this per experiment rather than per node,
# adding all nodes from an experiment to the sublists created.
356 357 358 359 360 361 362
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'};
363 364
    my $isvirt  = $row->{'isvirtnode'};
    my $shared  = (defined($row->{'sharing_mode'}) ? 1 : 0);
365
    my $erole   = $row->{'erole'};
366 367
    my $enonfs  = $row->{'enonfs'};
    my $nnonfs  = $row->{'nnonfs'};
368 369
    my $enfs    = $row->{'e_nfsmounts'};
    my $nnfs    = $row->{'n_nfsmounts'};
370 371
    my $routable= ((defined($row->{'routable_ip'}) &&
		    $row->{'routable_ip'} eq "true") ? 1 : 0);
372 373
    my %fslist = ();
    my @dirlist = ();
Kirk Webb's avatar
 
Kirk Webb committed
374
    my @smbshares = ();
375

376 377 378
    # 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)) ||
379
        (!defined($gid)) || (!defined($admin)) || (!defined($ip))) {
380 381
        logit("WARNING: skipping database row with undefined values".
	      defined($node_id) ? " ($node_id)" : "");
382 383
	next;
    }
384

385 386 387 388 389 390 391 392 393 394 395
    # 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);
	}
    }

396 397
    # Skip nodes that belong to a "no nfs" experiment or are marked "no nfs".
    next
398 399
	if ($enonfs || $nnonfs || $enfs eq "none" ||
	    (defined($nnfs) && $nnfs eq "none"));
400

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

409 410
    if ($lastpid eq $pid && $lastgid eq $gid && $lasterole eq $erole &&
	(!$ISOLATEADMIN || $lastadmin eq $admin)) {
411 412
	# If this is for the same proj and group again, don't requery the db 
	# and don't recompute everything.
Kirk Webb's avatar
 
Kirk Webb committed
413 414
	%fslist    = %lastfslist;
        @smbshares = @lastsmbshares;
415

416 417 418
    } else {
	$lastpid=$pid;
	$lastgid=$gid;
419
	$lastadmin=$admin;
420
	$lasterole=$erole;
421

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

450
	    if ($scratchdir) {
451 452 453
		push(@dirlist, "$scratchdir/$pid");
		push(@smbshares, ["scratch-$pid", "$scratchdir/$pid"]);
	    }
454

455 456 457 458 459 460 461 462 463
	    # 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 ";
464
	    }
465 466 467 468 469 470 471 472 473 474 475 476 477 478

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

479 480
		push(@dirlist, "$usersdir/$uid");
		push(@smbshares, [$uid, "$usersdir/$uid"]);
481 482 483 484

		if ($ZFS_NOEXPORT) {
		    $bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
		}
485
	    }
486
	}
487
      skip:
488 489 490 491 492

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

	    if (! defined($fslist{$fs})) {
		$fslist{$fs} = [ $dir ];
	    }
	    else {
		push(@{ $fslist{$fs} }, $dir);
	    }
Kirk Webb's avatar
 
Kirk Webb committed
504

505
	}
506 507
	%lastfslist    = %fslist;
	@lastsmbshares = @smbshares;
508
    }
509

510 511 512 513 514 515 516
    # 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} }));
517

518 519 520 521 522 523
	if (! defined($ipgroups{$str})) {
	    $ipgroups{$str} = [ $ip ];
	}
	else {
	    push(@{ $ipgroups{$str} }, $ip);
	}
524
    }
Kirk Webb's avatar
 
Kirk Webb committed
525 526

    # Build up Samba share permissions hash
Kirk Webb's avatar
 
Kirk Webb committed
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
    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);
Kirk Webb's avatar
 
Kirk Webb committed
543 544 545
            }
        }
    }
546 547
}

548 549 550 551 552 553 554 555 556
#
# 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 = ();

557 558 559 560 561 562 563 564
# 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]);
}

565
#
566
# Now spit out each group!
567
#
568
foreach my $str (sort(keys(%ipgroups))) {
569
    my @iplist = sort sortbyip @{ $ipgroups{$str} };
570

571 572 573 574 575 576
    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 {
577 578
	print MAP "$str -maproot=$NFSMAPTOUSER @iplist\n";
	print "$str -maproot=$NFSMAPTOUSER @iplist\n"
579 580
	    if ($debug);
    }
581
}
582
if ($ZFS_NOEXPORT) {
583 584 585 586
    # Build up filesystem sub-lists.
    # Iterate through directory list dividing it according to filesystem.
    my %bosslists = ();
    
587
    foreach my $dir (sort(keys(%bossexports))) {
588
	my $fs = fsof($dir);
589
	if (!$fs) {
590
	    logit("WARNING: no filesystem for '$dir', ignored");
591 592
	    next;
	}
593

594 595 596 597 598 599
	if (! defined($bosslists{$fs})) {
	    $bosslists{$fs} = [ $dir ];
	}
	else {
	    push(@{ $bosslists{$fs} }, $dir);
	}
600
    }
601
    foreach my $fs (sort(keys(%bosslists))) {
602 603 604 605 606 607 608 609 610 611
	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"
612
	    if ($debug);
613
	}
614 615 616 617 618

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

    #
    # 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);
	}
    }
638
}
639 640
close(MAP);

641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
#
# 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,".
659
	    " boss will not mount anything!\n";
660 661 662 663 664 665
	if (!$TESTMODE) {
	    unlink("bossmountfile.new", $bossmountfile);
	}
    }
}

666 667 668 669 670 671 672 673 674 675 676 677 678
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);
}

Kirk Webb's avatar
 
Kirk Webb committed
679 680 681
#
# Spit out smb shares!
#
Kirk Webb's avatar
 
Kirk Webb committed
682
if ($WINSUPPORT) {
683
    foreach my $share (sort(keys(%globalsmbshares))) {
684
        my @iplist = sort sortbyip @{ $globalsmbshares{$share}->{iplist} };
Kirk Webb's avatar
 
Kirk Webb committed
685 686 687 688 689 690 691 692
        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";
    }
Kirk Webb's avatar
 
Kirk Webb committed
693

Kirk Webb's avatar
 
Kirk Webb committed
694 695 696
    print SMBMAP "\n";
    close(SMBMAP);
}
Kirk Webb's avatar
 
Kirk Webb committed
697

698 699 700 701
#
# Fire the new tail file over to the fileserver to finish. We cat the file
# right into it.
#
702 703
if (!$TESTMODE) {
  $UID = 0;
704 705 706
  #
  # Temp Hack! Save a copy of the exports file for debugging.
  #
707
  if ($debug) {
708 709
      my $backup = "$TB/log/exports/" . TBDateTimeFSSafe();
      system("cp $exportstail $backup");
Kirk Webb's avatar
 
Kirk Webb committed
710 711 712
      if ($WINSUPPORT) {
          system("cp $smbconftail $backup");
      }
713
  }
714 715
  if ($impotent) {
      system("/bin/cat $exportstail");
716 717 718
      if ($WINSUPPORT) {
	  system("/bin/cat $smbconftail");
      }
719 720 721
      if ($bossdirlist) {
	  system("/bin/cat $bossmountfile.new");
      }
722
  }
723 724 725 726 727 728 729
  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");
  }
730
  else {
731 732
      my $arg = ($incremental ? "-i" : "");

733
      # First do the NFS exports
734
      logit("Invoking exports_setup.proxy...");
735
      system("$SSH $PROG $arg < $exportstail") == 0 or
736
	  fatal("Failed: $SSH $PROG < $exportstail: $?");
737
      logit("exports_setup.proxy done");
738
      unlink("$exportstail");
Kirk Webb's avatar
 
Kirk Webb committed
739

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

      #
      # 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) {
755
	  my $testdir = "/proj/" . TBOPSPID();
756
	  logit("Waiting for mountd to finish...");
757 758 759 760 761 762 763 764
	  my $tries = 10;
	  while ($tries-- > 0) {
	      if (system("/bin/ls $testdir >/dev/null 2>&1")) {
		  sleep(1);
		  next;
	      }
	      last;
	  }
765
	  logit("Wait done");
766
      }
767 768

      #
769
      # Move the new boss autofs mount map in place and flush the cache.
770 771 772
      #
      if ($bossdirlist) {
	  rename("$bossmountfile.new", $bossmountfile);
773 774 775
	  if (-x "/usr/sbin/automount") {
	      system("/usr/sbin/automount -c");
	  }
776
      }
Kirk Webb's avatar
 
Kirk Webb committed
777
  }
Kirk Webb's avatar
 
Kirk Webb committed
778

779
  #
780
  # Release the lock!
781
  #
782
  TBScriptUnlock();
783
}
784 785 786 787 788 789

exit(0);

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

790 791 792
    TBScriptUnlock()
	if (!$TESTMODE);
    
793
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);
794 795
    die($msg);
}
796

797
#
798 799 800
# 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.
801
#
802 803 804
sub fsinit() {
    my @rawmounts;

805 806 807 808 809 810
    my $saveuid = $UID;
    $UID = 0;
    my $mountinfo = `$SSH $MOUNTPROG`;
    $UID = $saveuid;

    foreach my $mount (split('\n', $mountinfo)) {
811 812 813 814 815 816 817 818 819
	if ($mount =~ /$EXPORT_PAT/) {
	    push(@rawmounts, $1);
	}
    }

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

820
#
821 822
# Return a unique (per-FS) string identifying the filesystem of
# the given path.
823 824 825
#
sub fsof($) {
    my($path) = @_;
826

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

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

END {
    closelog();
}