exports_setup.in 10.7 KB
Newer Older
1
#!/usr/bin/perl -wT
Leigh Stoller's avatar
Leigh Stoller committed
2 3 4

#
# EMULAB-COPYRIGHT
5
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
Leigh Stoller's avatar
Leigh Stoller committed
6 7 8
# All rights reserved.
#

9 10 11 12 13 14 15
use English;

#
# 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
#
16 17
# This script always does the right thing, so it does not matter who calls it. 
#
18 19 20
# usage: exports_setup
#

21 22 23 24 25 26 27
#
# Function phototypes
#

sub fsinit();
sub fsof($);

28 29 30 31 32
#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
33
my $TESTMODE    = @TESTMODE@;
34
my $FSNODE      = "@FSNODE@";
35 36 37
my $projdir     = "@FSDIR_PROJ@";
my $usersdir    = "@FSDIR_USERS@";
my $groupdir    = "@FSDIR_GROUPS@";
38
my $scratchdir  = "@FSDIR_SCRATCH@";
39
my $DISABLED	= "@DISABLE_EXPORTS_SETUP@";
40
my $WINSUPPORT  = @WINSUPPORT@;
41 42

# Note no -n option. We redirect stdin from the new exports file below.
43
my $SSH		= "$TB/bin/sshtb -l root -host $FSNODE";
44
my $PROG	= "$TB/sbin/exports_setup.proxy";
45
my $exportstail = "/var/tmp/exports.tail";
46 47
my $smbconftail = "/var/tmp/smbconf.tail";
my $dbg		= 1;
48
my @row; 
49

50 51 52 53
# For determining file server mountpoints (XXX BSD specific)
my $MOUNTPROG	= "/sbin/mount";
my $EXPORT_PAT	= q(on ([\S]+)\s\(.*NFS exported.*\));

54 55 56 57 58 59 60
#
# Testbed doesn't support changing exports file
#
if ($DISABLED) {
    exit(0);
}

61 62 63 64
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
Leigh Stoller's avatar
Leigh Stoller committed
65 66
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
67 68
}
# XXX Hacky!
69
if (0 && $TB ne "/usr/testbed") {
70 71
    print STDERR "*** $0:\n".
	         "    Wrong version. Maybe its a development version?\n";
Leigh Stoller's avatar
Leigh Stoller committed
72 73 74
    #
    # Let experiment continue setting up.
    # 
75
    exit(0);
Leigh Stoller's avatar
Leigh Stoller committed
76
}
77 78 79 80 81

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

82 83 84 85
#
# Turn off line buffering on output
#
$| = 1;
86

87
#
88
# Testbed Support libraries
89
# 
90 91 92
use lib "@prefix@/lib";
use libdb;
use libtestbed;
93

94 95 96
my $PROJROOT  = PROJROOT();
my $GROUPROOT = GROUPROOT();
my $USERROOT  = USERROOT();
97
my $SCRATCHROOT  = SCRATCHROOT();
98

99
#
100
# We need to serialize this script to avoid a trashed map file.
101
#
102
if (!$TESTMODE) {
103 104 105 106
    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");
107 108 109 110 111 112
    }
}

#
# We stick the new map entries into the tail file. First zero it out.
#
113 114
if (!$TESTMODE) {
  open(MAP, ">$exportstail") || fatal("Couldn't open $exportstail\n");
115 116 117
  if ($WINSUPPORT) {
      open(SMBMAP, ">$smbconftail") || fatal("Couldn't open $smbconftail\n");
  }
118 119
} else {
  open(MAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
120 121 122
  if ($WINSUPPORT) {
      open(SMBMAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
  }
123
}
124 125 126 127 128 129 130 131 132

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

print MAP $maphdr;
133 134 135
if ($WINSUPPORT) {
    print SMBMAP $maphdr;
}
136 137

#
138
# First gather up all the nodes that are reserved and the required info.
139 140
# Order by pid,gid first so that they're all grouped together and we avoid
# extra db queries.
141
#
142 143
# VIRTNODE HACK: Virtual nodes are special, so do not export. (isvirtnode).
#
144
$nodes_result =
145
    DBQueryFatal("select r.node_id,r.pid,r.eid,e.gid,i.IP from reserved as r ".
146 147
		 "left join experiments as e on r.pid=e.pid and r.eid=e.eid ".
		 "left join nodes on r.node_id=nodes.node_id ".
148
		 "left join node_types on node_types.type=nodes.type ".
149
		 "left join interfaces as i on r.node_id=i.node_id ".
150 151
		 " where i.IP!='NULL' and ".
		 "       i.role='" . TBDB_IFACEROLE_CONTROL() . "' ".
152
		 "       and nodes.role='testnode' ".
153
		 "       and node_types.isvirtnode=0 ".
154
		 "       and node_types.isremotenode=0 ".
155
		 "order by r.pid,e.gid,r.eid,nodes.priority");
156

157 158
my %ipgroups    = ();
my %globalsmbshares   = ();
159
my %lastfslist  = ();
160
my @lastsmbshares = ();
161 162
my $lastpid     = "";
my $lastgid     = "";
163

164 165
my @mountpoints = fsinit();

166 167 168 169 170
# 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
171
#
172 173 174
# Note that we could do this per experiment rather than per node,
# adding all nodes from an experiment to the sublists created.
while (@row = $nodes_result->fetchrow_array) {
175 176
    my $node_id = $row[0];
    my $pid     = $row[1];
177
    my $eid     = $row[2];
178
    my $gid     = $row[3];
179 180 181
    my $ip      = $row[4];
    my %fslist = ();
    my @dirlist = ();
182
    my @smbshares = ();
183

184 185 186 187 188 189 190 191
    # 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)) ||
        (!defined($gid)) || (!defined($ip))) {
        print "WARNING: exports_setup: Skipping database row with undefined values\n";
	next;
    }

192
    if ($lastpid eq $pid && $lastgid eq $gid) {
193

194 195
	# If this is for the same proj and group again, don't requery the db 
	# and don't recompute everything.
196 197
	%fslist    = %lastfslist;
        @smbshares = @lastsmbshares;
198

199
    } else {
200

201 202
	$lastpid=$pid;
	$lastgid=$gid;
203

204 205 206
	# Construct a list of directories accessible from this node.
	# First the project and group directories.
	# XXX needs to be fixed for shared experiments?
207 208
	if (-d "$PROJROOT/$pid") {
	    push(@dirlist, "$projdir/$pid");
209
            push(@smbshares, ["proj-$pid", "$projdir/$pid"]);
210 211 212 213 214
	}
	else {
	    print STDERR "*** exports_setup: $PROJROOT/$pid does not exist!\n";
	}
	
215
	if ($gid ne $pid) {
216
	    if (-d "$GROUPROOT/$pid/$gid") {
217 218
		push(@dirlist, "$groupdir/$pid/$gid");
                push(@smbshares, ["${pid}-${gid}", "$groupdir/$pid/$gid"]);
219 220 221 222 223
	    }
	    else {
		print STDERR "*** exports_setup: ".
		    "$GROUPROOT/$pid/$gid does not exist!\n";
	    }
224 225
	}

226 227 228 229 230
	if ($scratchdir && -d "$SCRATCHROOT/$pid") {
	    push(@dirlist, "$scratchdir/$pid");
            push(@smbshares, ["scratch-$pid", "$scratchdir/$pid"]);
	}

231 232 233 234
	# Determine the users that can access this node, and add those
	# users' directories to the list.
	# XXX needs to be fixed for shared experiments?
	$users_result =
235
	    DBQueryFatal("select distinct g.uid from group_membership as g ".
236
			 "left join users as u on u.uid_idx=g.uid_idx ".
237 238 239 240
			 "where g.pid='$pid' and g.gid='$gid' and ".
			 "      (g.trust!='none' and ".
			 "       u.webonly=0 and ".
			 "       u.status='" . USERSTATUS_ACTIVE() . "')");
241 242 243

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

245 246
	    if (-d "$USERROOT/$uid") {
		push(@dirlist, "$usersdir/$uid");
247
		push(@smbshares, [$uid, "$usersdir/$uid"]);
248 249 250 251 252
	    }
	    else {
		print STDERR "*** exports_setup: ".
		    "$USERROOT/$uid does not exist!\n";
	    }
253
	}
254 255 256 257 258 259 260 261 262 263 264 265

	# 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);
	    }
266

267
	}
268

269 270
	%lastfslist    = %fslist;
        @lastsmbshares = @smbshares;
271
    }
272

273 274 275 276 277 278 279
    # 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} }));
280

281 282 283 284 285 286
	if (! defined($ipgroups{$str})) {
	    $ipgroups{$str} = [ $ip ];
	}
	else {
	    push(@{ $ipgroups{$str} }, $ip);
	}
287
    }
288 289

    # Build up Samba share permissions hash
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
    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);
306 307 308
            }
        }
    }
309 310 311
}

#
312
# Now spit out each group!
313
#
314
foreach my $str ( keys(%ipgroups) ) {
315
    my @iplist = @{ $ipgroups{$str} };    
316

317
    print MAP "$str -maproot=root @iplist\n";
318
}
319 320 321 322

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

323 324 325
#
# Spit out smb shares!
#
326 327 328 329 330 331 332 333 334 335 336
if ($WINSUPPORT) {
    foreach my $share ( keys(%globalsmbshares) ) {
        my @iplist = @{ $globalsmbshares{$share}->{iplist} };
        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";
    }
337

338 339 340
    print SMBMAP "\n";
    close(SMBMAP);
}
341

342 343 344 345
#
# Fire the new tail file over to the fileserver to finish. We cat the file
# right into it.
#
346 347
if (!$TESTMODE) {
  $UID = 0;
348 349 350
  #
  # Temp Hack! Save a copy of the exports file for debugging.
  #
351 352 353
  if ($dbg) {
      my $backup = "$TB/log/exports/" . TBDateTimeFSSafe();
      system("cp $exportstail $backup");
354 355 356
      if ($WINSUPPORT) {
          system("cp $smbconftail $backup");
      }
357
  }
358 359

  # First do the NFS exports
360
  system("$SSH $PROG < $exportstail") == 0 or
361
    fatal("Failed: $SSH $PROG < $exportstail: $?");
362
  unlink("$exportstail");
363

364
  # Next the SMB shares
365 366 367 368 369
  if ($WINSUPPORT) {
      system("$SSH $PROG -S < $smbconftail") == 0 or
          fatal("Failed: $SSH $PROG < $smbconftail: $?");
      unlink("$smbconftail");
  }
370

371
  #
372
  # Release the lock!
373
  #
374
  TBScriptUnlock();
375
}
376 377 378 379 380 381

exit(0);

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

382 383 384
    TBScriptUnlock()
	if (!$TESTMODE);
    
385
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);
386 387
    die($msg);
}
388

389
#
390 391 392
# 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.
393
#
394 395 396
sub fsinit() {
    my @rawmounts;

397 398 399 400 401 402
    my $saveuid = $UID;
    $UID = 0;
    my $mountinfo = `$SSH $MOUNTPROG`;
    $UID = $saveuid;

    foreach my $mount (split('\n', $mountinfo)) {
403 404 405 406 407 408 409 410 411
	if ($mount =~ /$EXPORT_PAT/) {
	    push(@rawmounts, $1);
	}
    }

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

412
#
413 414
# Return a unique (per-FS) string identifying the filesystem of
# the given path.
415 416 417
#
sub fsof($) {
    my($path) = @_;
418

419 420 421 422 423 424 425
    foreach my $mount (@mountpoints) {
	if ($path =~ /^$mount/) {
	    return $mount;
	}
    }
    print "WARNING: exports_setup: could not find FS for $path\n";
    return "";
426
}