exports_setup.in 8.16 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-2003 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
22
23
24
25
26
# usage: exports_setup
#

#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
27
my $TESTMODE    = @TESTMODE@;
28
my $FSNODE      = "@FSNODE@";
29
30
31
my $projdir     = "@FSDIR_PROJ@";
my $usersdir    = "@FSDIR_USERS@";
my $groupdir    = "@FSDIR_GROUPS@";
32
33

# Note no -n option. We redirect stdin from the new exports file below.
34
my $SSH		= "$TB/bin/sshtb -l root -host $FSNODE";
35
36
37
my $PROG	= "/usr/testbed/sbin/exports_setup.proxy";
my $exportstail = "/var/tmp/exports.tail";
my $lockfile    = "/var/tmp/testbed_exports_lockfile";
38
my $dbg		= 0;
39
my @row; 
40
41
42
43
44

#
# 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
45
46
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
47
48
}
# XXX Hacky!
49
if (0 && $TB ne "/usr/testbed") {
50
51
    print STDERR "*** $0:\n".
	         "    Wrong version. Maybe its a development version?\n";
Leigh B. Stoller's avatar
Leigh B. Stoller committed
52
53
54
    #
    # Let experiment continue setting up.
    # 
55
    exit(0);
Leigh B. Stoller's avatar
Leigh B. Stoller committed
56
}
57
58
59
60
61

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

62
63
64
65
#
# Turn off line buffering on output
#
$| = 1;
66

67
#
68
# Testbed Support libraries
69
# 
70
71
72
use lib "@prefix@/lib";
use libdb;
use libtestbed;
73
74
75
76
77

#
# 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. 
#
78
if (!$TESTMODE) {
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
    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) {
113
114
115
116
117
118
119
120
	    while (1) {
		if ((stat(LOCK))[9] != $oldlocktime) {
		    exit(0);
		}
		if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
		    close(LOCK);
		    exit(0);
		}
121
		if ($count++ > 20)  {
122
		    fatal("Process with the lock didn't finish after a long time!\n");
123
124
125
126
		}
		sleep(1); 
	    }
	}
127
128
129
    }
}

130
131
132
133
134
135
#
# Perl-style touch(1)
#
my $now = time;
utime $now, $now, $lockfile;

136
137
138
#
# We stick the new map entries into the tail file. First zero it out.
#
139
140
141
142
143
if (!$TESTMODE) {
  open(MAP, ">$exportstail") || fatal("Couldn't open $exportstail\n");
} else {
  open(MAP, ">/dev/null") || fatal("Couldn't open /dev/null\n");
}
144
145
146
147
148
149
150
print MAP "\n";
print MAP "#\n";
print MAP "# DO NOT EDIT below this point. Auto generated entries!\n";
print MAP "#\n";
print MAP "\n";

#
151
# First gather up all the nodes that are reserved and the required info.
152
153
# Order by pid,gid first so that they're all grouped together and we avoid
# extra db queries.
154
#
155
156
# VIRTNODE HACK: Virtual nodes are special, so do not export. (isvirtnode).
#
157
158
159
160
$nodes_result =
    DBQueryFatal("select r.node_id,r.pid,r.eid,e.gid,i.IP from reserved as r ".
		 "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 ".
161
		 "left join node_types on node_types.type=nodes.type ".
162
163
164
		 "left join interfaces as i on r.node_id=i.node_id ".
		 " and i.card=node_types.control_net ".
		 " where i.IP!='NULL' and r.node_id not like 'sh%' ".
165
		 "       and node_types.isvirtnode=0 ".
166
		 "       and node_types.isremotenode=0 ".
167
		 "order by r.pid,e.gid,r.eid,nodes.priority");
168

169
my %ipgroups	= ();
170
171
172
my %lastfslist  = ();
my $lastpid     = "";
my $lastgid     = "";
173

174
175
176
177
178
# 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
179
#
180
181
182
# 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) {
183
184
    my $node_id = $row[0];
    my $pid     = $row[1];
185
    my $eid     = $row[2];
186
    my $gid     = $row[3];
187
188
189
    my $ip      = $row[4];
    my %fslist = ();
    my @dirlist = ();
190

191
192
193
194
195
196
197
198
    # 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;
    }

199
    if ($lastpid eq $pid && $lastgid eq $gid) {
200

201
202
203
	# If this is for the same proj and group again, don't requery the db 
	# and don't recompute everything.
	%fslist = %lastfslist;
204

205
    } else {
206

207
208
	$lastpid=$pid;
	$lastgid=$gid;
209

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
	# Construct a list of directories accessible from this node.
	# First the project and group directories.
	# XXX needs to be fixed for shared experiments?
	push(@dirlist, "$projdir/$pid");
	if ($gid ne $pid) {
	    push(@dirlist, "$groupdir/$pid/$gid");
	}

	# 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 =
	    DBQueryFatal("select distinct uid from group_membership ".
			 "where pid='$pid' and gid='$gid' and trust!='none'");

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

228
	    push(@dirlist, "$usersdir/$uid");
229
	}
230
231
232
233
234
235
236
237
238
239
240
241

	# 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);
	    }
242
	}
243
244

	%lastfslist = %fslist;
245
    }
246

247
248
249
250
251
252
253
    # 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} }));
254

255
256
257
258
259
260
	if (! defined($ipgroups{$str})) {
	    $ipgroups{$str} = [ $ip ];
	}
	else {
	    push(@{ $ipgroups{$str} }, $ip);
	}
261
262
263
264
    }
}

#
265
# Now spit out each group!
266
#
267
268
269
270
foreach my $str ( keys(%ipgroups) ) {
    my @iplist = @{ $ipgroups{$str} };    

    print MAP "$str -maproot=root @iplist\n";
271
}
272
273
274
275
276
277
278
279

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

#
# Fire the new tail file over to the fileserver to finish. We cat the file
# right into it.
#
280
281
if (!$TESTMODE) {
  $UID = 0;
282
283
284
  #
  # Temp Hack! Save a copy of the exports file for debugging.
  #
285
286
287
288
  if ($dbg) {
      my $backup = "$TB/log/exports/" . TBDateTimeFSSafe();
      system("cp $exportstail $backup");
  }
289
  
290
  system("$SSH $PROG < $exportstail") == 0 or
291
    fatal("Failed: $SSH $PROG < $exportstail: $?");
292
  unlink("$exportstail");
293

294
295
296
297
298
  #
  # Close the lock file. Exiting releases it, but might as well.
  #
  close(LOCK);
}
299
300
301
302
303
304

exit(0);

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

305
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);
306
307
    die($msg);
}
308

309
#
310
311
# Return a unique (per-FS) string identifying the filesystem of
# the given path.
312
#
313
314
315
316
317
318
319
320
321
322
323
# XXX for the moment, we just return the first component of the path
# assuming everything is mounted at the top level.  Clearly this needs
# to be fixed.
#
# Fixing probably involves either parsing the output of mount or df to
# compare against or using $dev as returned by the stat call.  Both of
# these options require ssh callouts to the ops node where the actual
# filesystems are.
#
sub fsof($) {
    my($path) = @_;
324

325
326
    $path =~ s#^(/[^/]*).*#$1#;
    return $path;
327
}