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

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

9
10
11
12
13
14
15
16
use English;
use Fcntl ':flock';

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

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

sub fsinit();
sub fsof($);

29
30
31
32
33
#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
34
my $TESTMODE    = @TESTMODE@;
35
my $FSNODE      = "@FSNODE@";
36
37
38
my $projdir     = "@FSDIR_PROJ@";
my $usersdir    = "@FSDIR_USERS@";
my $groupdir    = "@FSDIR_GROUPS@";
39
my $DISABLED	= "@DISABLE_EXPORTS_SETUP@";
Kirk Webb's avatar
   
Kirk Webb committed
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";
Kirk Webb's avatar
   
Kirk Webb committed
44
my $PROG	= "$TB/sbin/exports_setup.proxy";
45
my $exportstail = "/var/tmp/exports.tail";
Kirk Webb's avatar
   
Kirk Webb committed
46
my $smbconftail = "/var/tmp/smbconf.tail";
47
my $lockfile    = "/var/tmp/testbed_exports_lockfile";
Kirk Webb's avatar
   
Kirk Webb committed
48
my $dbg		= 1;
49
my @row; 
50

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

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

62
63
64
65
#
# 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
66
67
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
68
69
}
# XXX Hacky!
70
if (0 && $TB ne "/usr/testbed") {
71
72
    print STDERR "*** $0:\n".
	         "    Wrong version. Maybe its a development version?\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
73
74
75
    #
    # Let experiment continue setting up.
    # 
76
    exit(0);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
77
}
78
79
80
81
82

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

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

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

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

99
100
101
102
#
# We need to serialize this script to avoid a trashed map file. Use
# a dummy file in /var/tmp, opened for writing and flock'ed. 
#
103
if (!$TESTMODE) {
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
    open(LOCK, ">>$lockfile") || fatal("Couldn't open $lockfile\n");
    $count = 0;
    if (flock(LOCK, LOCK_EX|LOCK_NB) == 0) {
	#
	# If we don't get it the first time, we wait for:
	# 1) The lock to become free, in which case we do our thing
	# 2) The time on the lock to change, in which case we wait for that process
	#    to finish
	#
	my $oldlocktime = (stat(LOCK))[9];
	my $gotlock = 0;
	while (1) {
	    print "Another exports update in progress, waiting for it to finish\n";
	    if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
		# OK, got the lock, we can do what we're supposed to
		$gotlock = 1;
		last;
	    }
	    $locktime = (stat(LOCK))[9];
	    if ($locktime != $oldlocktime) {
		$oldlocktime = $locktime;
		last;
	    }
	    if ($count++ > 20)  {
		fatal("Could not get the lock after a long time!\n");
	    }
	    sleep(1);
	}

	$count = 0;
	#
	# If we didn't get the lock, wait for the processes that did to finish
	#
	if (!$gotlock) {
138
139
140
141
142
143
144
145
	    while (1) {
		if ((stat(LOCK))[9] != $oldlocktime) {
		    exit(0);
		}
		if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
		    close(LOCK);
		    exit(0);
		}
146
		if ($count++ > 20)  {
147
		    fatal("Process with the lock didn't finish after a long time!\n");
148
149
150
151
		}
		sleep(1); 
	    }
	}
152
153
154
    }
}

155
156
157
158
159
160
#
# Perl-style touch(1)
#
my $now = time;
utime $now, $now, $lockfile;

161
162
163
#
# We stick the new map entries into the tail file. First zero it out.
#
164
165
if (!$TESTMODE) {
  open(MAP, ">$exportstail") || fatal("Couldn't open $exportstail\n");
Kirk Webb's avatar
   
Kirk Webb committed
166
167
168
  if ($WINSUPPORT) {
      open(SMBMAP, ">$smbconftail") || fatal("Couldn't open $smbconftail\n");
  }
169
170
} else {
  open(MAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
Kirk Webb's avatar
   
Kirk Webb committed
171
172
173
  if ($WINSUPPORT) {
      open(SMBMAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
  }
174
}
Kirk Webb's avatar
   
Kirk Webb committed
175
176
177
178
179
180
181
182
183

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

print MAP $maphdr;
Kirk Webb's avatar
   
Kirk Webb committed
184
185
186
if ($WINSUPPORT) {
    print SMBMAP $maphdr;
}
187
188

#
189
# First gather up all the nodes that are reserved and the required info.
190
191
# Order by pid,gid first so that they're all grouped together and we avoid
# extra db queries.
192
#
193
194
# VIRTNODE HACK: Virtual nodes are special, so do not export. (isvirtnode).
#
195
$nodes_result =
196
    DBQueryFatal("select r.node_id,r.pid,r.eid,e.gid,i.IP from reserved as r ".
197
198
		 "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 ".
199
		 "left join node_types on node_types.type=nodes.type ".
200
		 "left join interfaces as i on r.node_id=i.node_id ".
201
202
		 " where i.IP!='NULL' and ".
		 "       i.role='" . TBDB_IFACEROLE_CONTROL() . "' ".
203
		 "       and nodes.role='testnode' ".
204
		 "       and node_types.isvirtnode=0 ".
205
		 "       and node_types.isremotenode=0 ".
206
		 "order by r.pid,e.gid,r.eid,nodes.priority");
207

Kirk Webb's avatar
   
Kirk Webb committed
208
209
my %ipgroups    = ();
my %globalsmbshares   = ();
210
my %lastfslist  = ();
Kirk Webb's avatar
   
Kirk Webb committed
211
my @lastsmbshares = ();
212
213
my $lastpid     = "";
my $lastgid     = "";
214

215
216
my @mountpoints = fsinit();

217
218
219
220
221
# 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
222
#
223
224
225
# 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) {
226
227
    my $node_id = $row[0];
    my $pid     = $row[1];
228
    my $eid     = $row[2];
229
    my $gid     = $row[3];
230
231
232
    my $ip      = $row[4];
    my %fslist = ();
    my @dirlist = ();
Kirk Webb's avatar
   
Kirk Webb committed
233
    my @smbshares = ();
234

235
236
237
238
239
240
241
242
    # 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;
    }

243
    if ($lastpid eq $pid && $lastgid eq $gid) {
244

245
246
	# 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
247
248
	%fslist    = %lastfslist;
        @smbshares = @lastsmbshares;
249

250
    } else {
251

252
253
	$lastpid=$pid;
	$lastgid=$gid;
254

255
256
257
	# Construct a list of directories accessible from this node.
	# First the project and group directories.
	# XXX needs to be fixed for shared experiments?
258
259
	if (-d "$PROJROOT/$pid") {
	    push(@dirlist, "$projdir/$pid");
Kirk Webb's avatar
   
Kirk Webb committed
260
            push(@smbshares, ["proj-$pid", "$projdir/$pid"]);
261
262
263
264
265
	}
	else {
	    print STDERR "*** exports_setup: $PROJROOT/$pid does not exist!\n";
	}
	
266
	if ($gid ne $pid) {
267
	    if (-d "$GROUPROOT/$pid/$gid") {
Kirk Webb's avatar
   
Kirk Webb committed
268
269
		push(@dirlist, "$groupdir/$pid/$gid");
                push(@smbshares, ["${pid}-${gid}", "$groupdir/$pid/$gid"]);
270
271
272
273
274
	    }
	    else {
		print STDERR "*** exports_setup: ".
		    "$GROUPROOT/$pid/$gid does not exist!\n";
	    }
275
276
277
278
279
280
	}

	# 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 =
281
282
283
284
285
286
	    DBQueryFatal("select distinct g.uid from group_membership as g ".
			 "left join users as u on u.uid=g.uid ".
			 "where g.pid='$pid' and g.gid='$gid' and ".
			 "      (g.trust!='none' and ".
			 "       u.webonly=0 and ".
			 "       u.status='" . USERSTATUS_ACTIVE() . "')");
287
288
289

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

291
292
	    if (-d "$USERROOT/$uid") {
		push(@dirlist, "$usersdir/$uid");
Kirk Webb's avatar
   
Kirk Webb committed
293
		push(@smbshares, [$uid, "$usersdir/$uid"]);
294
295
296
297
298
	    }
	    else {
		print STDERR "*** exports_setup: ".
		    "$USERROOT/$uid does not exist!\n";
	    }
299
	}
300
301
302
303
304
305
306
307
308
309
310
311

	# 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);
	    }
Kirk Webb's avatar
   
Kirk Webb committed
312

313
	}
314

Kirk Webb's avatar
   
Kirk Webb committed
315
316
	%lastfslist    = %fslist;
        @lastsmbshares = @smbshares;
317
    }
318

319
320
321
322
323
324
325
    # 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} }));
326

327
328
329
330
331
332
	if (! defined($ipgroups{$str})) {
	    $ipgroups{$str} = [ $ip ];
	}
	else {
	    push(@{ $ipgroups{$str} }, $ip);
	}
333
    }
Kirk Webb's avatar
   
Kirk Webb committed
334
335

    # Build up Samba share permissions hash
Kirk Webb's avatar
   
Kirk Webb committed
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
    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
352
353
354
            }
        }
    }
355
356
357
}

#
358
# Now spit out each group!
359
#
360
foreach my $str ( keys(%ipgroups) ) {
361
    my @iplist = @{ $ipgroups{$str} };    
362

363
    print MAP "$str -maproot=root @iplist\n";
364
}
365
366
367
368

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

Kirk Webb's avatar
   
Kirk Webb committed
369
370
371
#
# Spit out smb shares!
#
Kirk Webb's avatar
   
Kirk Webb committed
372
373
374
375
376
377
378
379
380
381
382
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";
    }
Kirk Webb's avatar
   
Kirk Webb committed
383

Kirk Webb's avatar
   
Kirk Webb committed
384
385
386
    print SMBMAP "\n";
    close(SMBMAP);
}
Kirk Webb's avatar
   
Kirk Webb committed
387

388
389
390
391
#
# Fire the new tail file over to the fileserver to finish. We cat the file
# right into it.
#
392
393
if (!$TESTMODE) {
  $UID = 0;
394
395
396
  #
  # Temp Hack! Save a copy of the exports file for debugging.
  #
397
398
399
  if ($dbg) {
      my $backup = "$TB/log/exports/" . TBDateTimeFSSafe();
      system("cp $exportstail $backup");
Kirk Webb's avatar
   
Kirk Webb committed
400
401
402
      if ($WINSUPPORT) {
          system("cp $smbconftail $backup");
      }
403
  }
Kirk Webb's avatar
   
Kirk Webb committed
404
405

  # First do the NFS exports
406
  system("$SSH $PROG < $exportstail") == 0 or
407
    fatal("Failed: $SSH $PROG < $exportstail: $?");
408
  unlink("$exportstail");
409

Kirk Webb's avatar
   
Kirk Webb committed
410
  # Next the SMB shares
Kirk Webb's avatar
   
Kirk Webb committed
411
412
413
414
415
  if ($WINSUPPORT) {
      system("$SSH $PROG -S < $smbconftail") == 0 or
          fatal("Failed: $SSH $PROG < $smbconftail: $?");
      unlink("$smbconftail");
  }
Kirk Webb's avatar
   
Kirk Webb committed
416

417
418
419
420
421
  #
  # Close the lock file. Exiting releases it, but might as well.
  #
  close(LOCK);
}
422
423
424
425
426
427

exit(0);

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

428
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);
429
430
    die($msg);
}
431

432
#
433
434
435
# 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.
436
#
437
438
439
sub fsinit() {
    my @rawmounts;

440
441
442
443
444
445
    my $saveuid = $UID;
    $UID = 0;
    my $mountinfo = `$SSH $MOUNTPROG`;
    $UID = $saveuid;

    foreach my $mount (split('\n', $mountinfo)) {
446
447
448
449
450
451
452
453
454
	if ($mount =~ /$EXPORT_PAT/) {
	    push(@rawmounts, $1);
	}
    }

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

455
#
456
457
# Return a unique (per-FS) string identifying the filesystem of
# the given path.
458
459
460
#
sub fsof($) {
    my($path) = @_;
461

462
463
464
465
466
467
468
    foreach my $mount (@mountpoints) {
	if ($path =~ /^$mount/) {
	    return $mount;
	}
    }
    print "WARNING: exports_setup: could not find FS for $path\n";
    return "";
469
}