exports_setup.in 18.2 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh B. Stoller's avatar
Leigh B. 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 B. Stoller's avatar
Leigh B. 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 [-n] [-d] [-w]\n".
38
	  "switches and arguments:\n".
39
	  "-w         - wait mode; wait for mountd to finish before exiting\n".
40 41 42
	  "-n         - impotent; dump exports to stdout\n");
    exit(-1);
}
43
my $optlist    = "ndw";
44 45
my $impotent   = 0;
my $debug      = 0;
46
my $waittildone= 0;
47

48 49 50 51 52 53 54
#
# Function phototypes
#

sub fsinit();
sub fsof($);

55 56 57 58 59
#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
60
my $TESTMODE    = @TESTMODE@;
61
my $BOSSNODE    = "@BOSSNODE@";
62
my $FSNODE      = "@FSNODE@";
63 64 65
my $projdir     = "@FSDIR_PROJ@";
my $usersdir    = "@FSDIR_USERS@";
my $groupdir    = "@FSDIR_GROUPS@";
66
my $scratchdir  = "@FSDIR_SCRATCH@";
67
my $DISABLED	= "@DISABLE_EXPORTS_SETUP@";
68
my $WINSUPPORT  = @WINSUPPORT@;
Mike Hibler's avatar
Mike Hibler committed
69
my $ISOLATEADMIN= @ISOLATEADMINS@;
70
my $NOSHAREDFS	= @NOSHAREDFS@;
71
my $LINUX_FSNODE= @LINUX_FSNODE@;
72
my $NFSMAPTOUSER= "@NFSMAPTOUSER@";
73
my $WITHZFS     = @WITHZFS@;
74
my $ZFS_NOEXPORT= @ZFS_NOEXPORT@;
75 76 77 78 79 80 81

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

82
# Note no -n option. We redirect stdin from the new exports file below.
83
my $SSH		= "$TB/bin/sshtb -l root -host $FSNODE";
84
my $PROG	= "$TB/sbin/exports_setup.proxy";
85
my $exportstail = "/var/tmp/exports.tail";
86
my $smbconftail = "/var/tmp/smbconf.tail";
87
my @row; 
88

89
# For determining file server mountpoints (XXX BSD specific)
90 91
my $MOUNTPROG	= ($LINUX_FSNODE ? "/bin/mount" : "/sbin/mount");
# Need the linux equiv for this.
92 93 94 95 96 97 98 99 100 101
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,.*\\\));
}
102

103 104 105
# Cache of dir to FS mappings already found
my %fsofcache;

106 107
#
# Testbed doesn't support changing exports file
108
# or we just do not export filesystems.
109
#
110
if ($DISABLED || $NOSHAREDFS) {
111 112 113
    exit(0);
}

114 115 116 117
#
# 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
118 119
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
120 121
}
# XXX Hacky!
122
if (0 && $TB ne "/usr/testbed") {
123 124
    print STDERR "*** $0:\n".
	         "    Wrong version. Maybe its a development version?\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
125 126 127
    #
    # Let experiment continue setting up.
    # 
128
    exit(0);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
129
}
130 131 132 133 134

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

135 136 137 138
#
# Turn off line buffering on output
#
$| = 1;
139

140
#
141
# Testbed Support libraries
142
# 
143 144 145
use lib "@prefix@/lib";
use libdb;
use libtestbed;
146
use Data::Dumper;
147

148 149 150
my $PROJROOT  = PROJROOT();
my $GROUPROOT = GROUPROOT();
my $USERROOT  = USERROOT();
151
my $SCRATCHROOT  = SCRATCHROOT();
152

153 154 155 156 157 158 159 160 161 162 163 164 165
#
# Check args.
#
my %options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"n"})) {
    $impotent = 1;
}
if (defined($options{"d"})) {
    $debug = 1;
}
166 167 168
if (defined($options{"w"})) {
    $waittildone = 1;
}
169 170 171
usage()
    if (@ARGV);

172
#
173
# We need to serialize this script to avoid a trashed map file.
174
#
175
if (!$TESTMODE) {
176 177 178 179
    if ((my $locked = TBScriptLock("exports", 1)) != TBSCRIPTLOCK_OKAY()) {
	exit(0)
	    if ($locked == TBSCRIPTLOCK_IGNORE);
	fatal("Could not get the lock after a long time!\n");
180 181 182 183 184 185
    }
}

#
# We stick the new map entries into the tail file. First zero it out.
#
186 187
if (!$TESTMODE) {
  open(MAP, ">$exportstail") || fatal("Couldn't open $exportstail\n");
188 189 190
  if ($WINSUPPORT) {
      open(SMBMAP, ">$smbconftail") || fatal("Couldn't open $smbconftail\n");
  }
191 192 193 194 195
} 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");
  }
196 197
} else {
  open(MAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
198 199 200
  if ($WINSUPPORT) {
      open(SMBMAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
  }
201
}
202 203 204 205 206 207 208 209 210

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

print MAP $maphdr;
211 212 213
if ($WINSUPPORT) {
    print SMBMAP $maphdr;
}
214 215

#
216
# First gather up all the nodes that are reserved and the required info.
217 218
# Order by pid,gid,admin first so that they're all grouped together and we
# avoid extra db queries (see lastpid/lastgid/lastadmin).
219
#
220
$nodes_result =
221 222 223
    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 ".
224
		 "from reserved as r ".
225
		 "left join experiments as e on r.pid=e.pid and r.eid=e.eid ".
226 227
		 "left join nodes as n on r.node_id=n.node_id ".
		 "left join node_types as nt on nt.type=n.type ".
228
		 "left join interfaces as i on r.node_id=i.node_id ".
229
		 "left join users as u on e.swapper_idx=u.uid_idx ".
230 231
		 " where i.IP!='NULL' and ".
		 "       i.role='" . TBDB_IFACEROLE_CONTROL() . "' ".
232
		 "       and (n.role='testnode' or n.role='virtnode')".
233
		 "       and nt.isremotenode=0 ".
234
		 "order by r.pid,e.gid,r.eid,u.admin,n.priority");
235

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
#
# 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.
    #
    my $limit = (60 * 60 * 24) * 7;

    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 ".
		     "where ((UNIX_TIMESTAMP(now()) - ".
		     "       UNIX_TIMESTAMP(s.last_activity)) <= $limit) or ".
256 257
		     "      ((UNIX_TIMESTAMP(now()) - ".
		     "       UNIX_TIMESTAMP(s.weblogin_last)) <= $limit) or ".
258 259 260 261 262 263
		     "      admin=1");
    while (my ($uid,$pid) = $active_result->fetchrow_array()) {
	$bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
	$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
	$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
    }
264 265 266 267 268 269 270 271
    #
    # 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 =
	DBQueryFatal("select pid from project_stats ".
		     "where ((UNIX_TIMESTAMP(now()) - ".
272
		     "       UNIX_TIMESTAMP(last_activity)) <= $limit)");
273 274 275 276
    while (my ($pid) = $active_result->fetchrow_array()) {
	$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
	$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
    }
277 278
}

279 280
my %ipgroups    = ();
my %globalsmbshares   = ();
281
my %lastfslist  = ();
282
my @lastsmbshares = ();
283 284
my $lastpid     = "";
my $lastgid     = "";
285
my $lastadmin	= "";
286
my $lasterole   = "";
287

288 289
my @mountpoints = fsinit();

290 291 292 293 294
# 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
295
#
296 297
# Note that we could do this per experiment rather than per node,
# adding all nodes from an experiment to the sublists created.
298 299 300 301 302 303 304
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'};
305 306
    my $isvirt  = $row->{'isvirtnode'};
    my $shared  = (defined($row->{'sharing_mode'}) ? 1 : 0);
307
    my $erole   = $row->{'erole'};
308 309
    my $enonfs  = $row->{'enonfs'};
    my $nnonfs  = $row->{'nnonfs'};
310 311
    my %fslist = ();
    my @dirlist = ();
312
    my @smbshares = ();
313

314 315 316
    # 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)) ||
317
        (!defined($gid)) || (!defined($admin)) || (!defined($ip))) {
318
        print "WARNING: exports_setup: Skipping database row with undefined values\n";
319
	print "         $node_id\n" if defined($node_id);
320 321
	next;
    }
322

323 324 325 326
    # Skip nodes that belong to a "no nfs" experiment or are marked "no nfs".
    next
	if ($enonfs || $nnonfs);

327 328 329
    # Skip non-shared virtnode nodes; NFS mounts are handled differently.
    next
	if ($isvirt && !$shared);
330
    
331 332
    if ($lastpid eq $pid && $lastgid eq $gid && $lasterole eq $erole &&
	(!$ISOLATEADMIN || $lastadmin eq $admin)) {
333 334
	# If this is for the same proj and group again, don't requery the db 
	# and don't recompute everything.
335 336
	%fslist    = %lastfslist;
        @smbshares = @lastsmbshares;
337

338 339 340
    } else {
	$lastpid=$pid;
	$lastgid=$gid;
341
	$lastadmin=$admin;
342
	$lasterole=$erole;
343

344
	if ($erole eq "sharedhost" && !$isvirt && !$WITHZFS) {
345
	    #
346 347 348
	    # Shared local *physical* nodes get toplevel mounts.
	    #
	    # ZFS does not support toplevel mounts. 
349 350
	    #
	    push(@dirlist, "$projdir");
351
	    push(@dirlist, "$groupdir");
352 353 354
	    push(@dirlist, "$scratchdir")
		if ($scratchdir && -d "$SCRATCHROOT");
	    push(@dirlist, "$usersdir");
355 356
	}
	else {
357 358 359 360 361 362
	    # Construct a list of directories accessible from this node.
	    # First the project and group directories.
	    # XXX needs to be fixed for shared experiments?
	    if (-d "$PROJROOT/$pid") {
		push(@dirlist, "$projdir/$pid");
		push(@smbshares, ["proj-$pid", "$projdir/$pid"]);
363 364
	    }
	    else {
365 366 367 368 369 370 371 372 373 374 375 376 377
		print STDERR
		    "*** exports_setup: $PROJROOT/$pid does not exist!\n";
	    }
	
	    if ($gid ne $pid) {
		if (-d "$GROUPROOT/$pid/$gid") {
		    push(@dirlist, "$groupdir/$pid/$gid");
		    push(@smbshares, ["${pid}-${gid}", "$groupdir/$pid/$gid"]);
		}
		else {
		    print STDERR "*** exports_setup: ".
			"$GROUPROOT/$pid/$gid does not exist!\n";
		}
378
	    }
379 380 381 382
	    if ($ZFS_NOEXPORT && $gid eq $pid) {
		$bossexports{"$projdir/$pid"}  = "$projdir/$pid";
		$bossexports{"$groupdir/$pid"} = "$groupdir/$pid";
	    }
383

384 385 386 387
	    if ($scratchdir && -d "$SCRATCHROOT/$pid") {
		push(@dirlist, "$scratchdir/$pid");
		push(@smbshares, ["scratch-$pid", "$scratchdir/$pid"]);
	    }
388

389 390 391 392 393 394 395 396 397
	    # 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 ";
398
	    }
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420

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

		if (-d "$USERROOT/$uid") {
		    push(@dirlist, "$usersdir/$uid");
		    push(@smbshares, [$uid, "$usersdir/$uid"]);
		}
		else {
		    print STDERR "*** exports_setup: ".
			"$USERROOT/$uid does not exist!\n";
		}
421 422 423 424

		if ($ZFS_NOEXPORT) {
		    $bossexports{"$usersdir/$uid"} = "$usersdir/$uid";
		}
425
	    }
426
	}
427
      skip:
428 429 430 431 432 433 434 435 436 437 438 439

	# Build up filesystem sub-lists.
	# Iterate through directory list dividing it according to filesystem.
	foreach my $dir ( @dirlist ) {
	    my $fs = fsof($dir);

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

441
	}
442 443
	%lastfslist    = %fslist;
	@lastsmbshares = @smbshares;
444
    }
445

446 447 448 449 450 451 452
    # 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} }));
453

454 455 456 457 458 459
	if (! defined($ipgroups{$str})) {
	    $ipgroups{$str} = [ $ip ];
	}
	else {
	    push(@{ $ipgroups{$str} }, $ip);
	}
460
    }
461 462

    # Build up Samba share permissions hash
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
    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);
479 480 481
            }
        }
    }
482 483
}

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

492
#
493
# Now spit out each group!
494
#
495
foreach my $str ( keys(%ipgroups) ) {
496
    my @iplist = sort sortbyip @{ $ipgroups{$str} };
497

498 499 500 501 502 503
    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 {
504 505
	print MAP "$str -maproot=$NFSMAPTOUSER @iplist\n";
	print "$str -maproot=$NFSMAPTOUSER @iplist\n"
506 507
	    if ($debug);
    }
508
}
509
if ($ZFS_NOEXPORT) {
510 511 512 513 514 515
    # 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);
516

517 518 519 520 521 522
	if (! defined($bosslists{$fs})) {
	    $bosslists{$fs} = [ $dir ];
	}
	else {
	    push(@{ $bosslists{$fs} }, $dir);
	}
523
    }
524 525 526 527 528 529 530 531 532 533 534
    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"
535
	    if ($debug);
536
	}
537 538
    }
}
539 540 541 542

print MAP "\n";
close(MAP);

543 544 545
#
# Spit out smb shares!
#
546 547
if ($WINSUPPORT) {
    foreach my $share ( keys(%globalsmbshares) ) {
548
        my @iplist = sort sortbyip @{ $globalsmbshares{$share}->{iplist} };
549 550 551 552 553 554 555 556
        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";
    }
557

558 559 560
    print SMBMAP "\n";
    close(SMBMAP);
}
561

562 563 564 565
#
# Fire the new tail file over to the fileserver to finish. We cat the file
# right into it.
#
566 567
if (!$TESTMODE) {
  $UID = 0;
568 569 570
  #
  # Temp Hack! Save a copy of the exports file for debugging.
  #
571
  if ($debug) {
572 573
      my $backup = "$TB/log/exports/" . TBDateTimeFSSafe();
      system("cp $exportstail $backup");
574 575 576
      if ($WINSUPPORT) {
          system("cp $smbconftail $backup");
      }
577
  }
578 579
  if ($impotent) {
      system("/bin/cat $exportstail");
580 581 582
      if ($WINSUPPORT) {
	  system("/bin/cat $smbconftail");
      }
583 584 585 586 587 588
  }
  else {
      # First do the NFS exports
      system("$SSH $PROG < $exportstail") == 0 or
	  fatal("Failed: $SSH $PROG < $exportstail: $?");
      unlink("$exportstail");
589

590 591 592 593 594 595
      # Next the SMB shares
      if ($WINSUPPORT) {
	  system("$SSH $PROG -S < $smbconftail") == 0 or
	      fatal("Failed: $SSH $PROG < $smbconftail: $?");
	  unlink("$smbconftail");
      }
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613

      #
      # 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) {
	  print "WARNING: waiting for mountd to finish...\n";
	  my $testdir = "/proj/" . TBOPSPID();
	  my $tries = 10;
	  while ($tries-- > 0) {
	      if (system("/bin/ls $testdir >/dev/null 2>&1")) {
		  sleep(1);
		  next;
	      }
	      last;
	  }
      }
614
  }
615

616
  #
617
  # Release the lock!
618
  #
619
  TBScriptUnlock();
620
}
621 622 623 624 625 626

exit(0);

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

627 628 629
    TBScriptUnlock()
	if (!$TESTMODE);
    
630
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);
631 632
    die($msg);
}
633

634
#
635 636 637
# 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.
638
#
639 640 641
sub fsinit() {
    my @rawmounts;

642 643 644 645 646 647
    my $saveuid = $UID;
    $UID = 0;
    my $mountinfo = `$SSH $MOUNTPROG`;
    $UID = $saveuid;

    foreach my $mount (split('\n', $mountinfo)) {
648 649 650 651 652 653 654 655 656
	if ($mount =~ /$EXPORT_PAT/) {
	    push(@rawmounts, $1);
	}
    }

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

657
#
658 659
# Return a unique (per-FS) string identifying the filesystem of
# the given path.
660 661 662
#
sub fsof($) {
    my($path) = @_;
663

664 665 666
    if (exists($fsofcache{$path})) {
	return $fsofcache{$path};
    }
667
    foreach my $mount (@mountpoints) {
668 669
	if (index($path, $mount) == 0) {
	    $fsofcache{$path} = $mount;
670 671 672 673 674
	    return $mount;
	}
    }
    print "WARNING: exports_setup: could not find FS for $path\n";
    return "";
675
}