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

#
4
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
# 
# {{{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 Stoller's avatar
Leigh Stoller committed
24 25
#

26
use English;
Leigh Stoller's avatar
Leigh Stoller committed
27
use Errno;
28
use Fcntl ':flock';
Kirk Webb's avatar
Kirk Webb committed
29
use Getopt::Std;
30 31 32 33 34 35 36 37

#
# Create and /etc/exports file based on current reserved table and project
# members.
#
# usage: exports_setup
#

Kirk Webb's avatar
Kirk Webb committed
38
my %opts = ();
39
getopts('Si', \%opts);
Kirk Webb's avatar
Kirk Webb committed
40

41 42 43
#
# Configure variables
#
44
my $TBOPS       = "@TBOPSEMAIL@";
45
my $LINUX_FSNODE= @LINUX_FSNODE@;
46
my $INC_MOUNTD  = @INCREMENTAL_MOUNTD@;
47
my $DIFFDIR	= "@prefix@/log/exports";
48
my $NFSROOT     = "@NFSMFS_ROOT@";
49

Kirk Webb's avatar
Kirk Webb committed
50 51 52 53 54 55 56 57
my $etcdir;
my $exports;
my $exportsnew;
my $exportsback;
my $exportshead;
my $exportstail;
my $pidfile;
my $daemon;
58
my $incremental;
59
my $issamba = 0;
Kirk Webb's avatar
Kirk Webb committed
60 61 62

# Are we modifying the Samba config file or the NFS exports?
if (defined($opts{'S'})) {
63
    $issamba	 = 1;
64
    $etcdir      = ($LINUX_FSNODE ? "/etc/samba" : "/usr/local/etc");
Kirk Webb's avatar
Kirk Webb committed
65 66 67 68 69
    $exports	 = "$etcdir/smb.conf";
    $exportsnew  = "$etcdir/smb.conf.new";
    $exportsback = "$etcdir/smb.conf.backup";
    $exportshead = "$etcdir/smb.conf.head";
    $exportstail = "$etcdir/smb.conf.tail";
70
    if (-r "/var/run/samba/smbd.pid") {
71
	$pidfile = "/var/run/samba/smbd.pid";
72
    } elsif (-r "/var/run/smbd.pid") {
73
	$pidfile = "/var/run/smbd.pid";
74 75
    } else {
	fatal("Cannot locate pidfile for smbd\n");
76
    }
Kirk Webb's avatar
Kirk Webb committed
77 78 79 80 81 82 83 84 85 86 87
    $daemon      = "smbd";
}
else {
    $etcdir      = "/etc";
    $exports	 = "$etcdir/exports";
    $exportsnew  = "$etcdir/exports.new";
    $exportsback = "$etcdir/exports.backup";
    $exportshead = "$etcdir/exports.head";
    $exportstail = "$etcdir/exports.tail";
    $pidfile     = "/var/run/mountd.pid";
    $daemon      = "mountd";
88
    $exportfs    = ($LINUX_FSNODE ? "/usr/sbin/exportfs -ra" : undef);
Kirk Webb's avatar
Kirk Webb committed
89

90 91
    # incremental only applies to mountd
    if (defined($opts{'i'})) {
92 93 94 95 96
	if ($INC_MOUNTD) {
	    $incremental = 1;
	} else {
	    print "WARNING: incremental updates not supported, ignoring option\n";
	}
97
    }
98 99
}

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
my $dbg		= 0;
my @row;

#
# We don't want to run this script unless its the real version.
#
if ($UID != 0) {
    die("Must be root!");
}

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

$| = 1; #Turn off line buffering on output

116 117 118 119 120 121
#
# Testbed Support libraries
# 
use lib "@prefix@/lib";
use libtestbed;

122
#
123
# Create a new exports file by concatonating a bunch of pieces
124
#
125 126 127
sub buildexports($$$) {
    my ($out, $head, $tail) = @_;

128
    #
129
    # Generate a warning so that no one tries to edit the file by hand
130
    #
131 132 133 134 135 136 137 138 139 140 141 142 143
    open(OUT, ">$out") or
	fatal("Couldn't create $out\n");

    print OUT <<EOF;
#
# ******************************************************************
# DO NOT EDIT THIS FILE. IT IS A CREATION, A FIGMENT, A CONTRIVANCE!
#
# Edit $exportshead, then run exports_setup on boss.
# ******************************************************************
#
EOF
    ;
144 145

    #
146
    # Now tack on the head and tail of the file.
147
    #
148 149 150 151 152
    foreach my $file ($head, $tail) {
	open(IN, "<$file") or
	    fatal("Failed to concat $file to $out\n");
	while (<IN>) {
	    print OUT "$_";
153
	}
154
	close(IN);
155
    }
156 157 158

    close(OUT);
    chmod(0644, $out);
159 160 161
}

#
162
# Take our input and write it to the tail file.
163
#
164 165
# We do this without any pre-checking of the contents to optimize the
# relatively common case of being called when nothing has changed.
166
#
167 168 169 170 171 172 173
# XXX one exception: if this is an NFS-based MFS filesystem, we have to
# check and see if the FS has been renamed. When we do a "node_admin off"
# we rename the "pcX" FS to "pcX-DEAD" because we don't want to yank the
# FS out from under the node while it is still running. But this is done
# on "fs" (nfsmfs_setup.proxy) and "boss" doesn't know about it, so it will
# continue to tell us "pcX" should be exported.
#
174 175
open(TAIL, ">$exportstail") || fatal("Couldn't open $exportstail\n");
while (<STDIN>) {
176 177 178 179
    if (/^($NFSROOT\/\S+)/ && ! -d "$1" && -d "$1-DEAD") {
	my $str = $1;
	s/$str/$str-DEAD/;
    }
180 181 182 183
    print TAIL $_;
}
close(TAIL);
chmod(0444, $exportstail);
184

185
buildexports($exportsnew, $exportshead, $exportstail);
186

187 188 189
#
# Do nothing if no change.
#
190 191 192 193 194
# Note that for now, we are actually saving the diffs so we can
# compare to the new differential behavior of mountd in case of
# an inconsisency. But only for mountd, not smbd.
#
my $outfile;
195
if ($issamba) {
196 197
    system("/usr/bin/diff -q $exports $exportsnew >/dev/null");
} else {
198 199 200 201 202
    if (-d "$DIFFDIR") {
	$outfile = "$DIFFDIR/diff." . time();
    } else {
	$outfile = "/dev/null";
    }
203 204
    system("/usr/bin/diff $exports $exportsnew >$outfile 2>&1");
}
205 206
if (! $?) {
#    print "No changes to $exports; skipping ...\n";
207
    unlink($outfile)
208
	if ($outfile && $outfile ne "/dev/null");
209 210 211
    exit(0);
}

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
#
# Go back and re-parse the file and stat each of the exported filesystems
# to make sure they exist. Otherwise mountd may prematurely fail. We used
# to do this on the boss-side, but that would stat the directories across
# NFS, and in some cases, before the filesystem was even exported!
#
# Note that we do not do this for samba as we assume that the samba
# invocation (-S) of this script will follow the normal invocation and
# have the same directories.
#
if (!$issamba) {
    my $changed = 0;
    
    open(TAIL, "<$exportstail") || fatal("Couldn't open $exportstail\n");
    open(NTAIL, ">$exportstail.new") || fatal("Couldn't open $exportstail.new\n");
    while(<TAIL>) {
	if ($_ !~ /^\//) {
	    print NTAIL $_;
	    next;
	}

	my @tokens = ();
	my $numdirs = 0;
	my $dirsdone = 0;
	foreach my $token (split) {
	    if (!$dirsdone) {
		# starts with a slash, assume it is an exported dir
		if ($token =~ /^\//) {
		    if (! -d "$token") {
			print STDERR "$token: does not exist, ignored\n";
			$changed++;
			next;
		    }
		    $numdirs++;
		} else {
		    $dirsdone = 1;
		}
	    }
	    push(@tokens, $token);
	}

	if ($numdirs > 0) {
	    print NTAIL join(' ', @tokens), "\n";
	}
    }
    close(TAIL);
    close(NTAIL);

    #
    # If there was a bogus directory in the tail file, we have to recreate
    # the new exports file again.
    #
    if ($changed) {
	buildexports($exportsnew, $exportshead, "$exportstail.new");
    }
    unlink("$exportstail.new");
}

270 271 272 273 274 275 276 277 278 279 280 281
#
# Back up the existing exports, and then mv in the new one.
#
system("cp $exports $exportsback") == 0 or
    fatal("Could not back up $exports to $exportsback\n");

system("mv $exportsnew $exports") == 0 or
    fatal("Could not mv $exportsnew to $exports\n");

# Avoid accidental editing.
chmod(0444, $exports);

282
my $checkstamp = 0;
283 284 285 286 287 288 289
if (!$LINUX_FSNODE) {
    my $daemonpid = `cat $pidfile`;
    $daemonpid =~ s/\n//;
    # untaint
    if ($daemonpid =~ /^([-\@\w.]+)$/) {
	$daemonpid = $1;
    }
290 291 292 293 294 295 296 297 298

    #
    # Utah feature: if mountd is maintaining a timestamp file, we use
    # that to determine when it has finished processing the exports.
    #
    # XXX since mountd always blindly updates the file, we just remove it
    # here so we don't have to bother checking the content, we just have to
    # wait for it to exist.
    #
299
    if (!$issamba && unlink("/var/run/mountd.ts") != 0) {
300 301 302
	$checkstamp = 1;
    }

303 304 305 306 307 308 309 310
    if ($incremental) {
	if (kill('USR1', $daemonpid) == 0) {
	    fatal("Could not kill(USR1) process $daemonpid ($daemon): $!");
	}
    } else {
	if (kill('HUP', $daemonpid) == 0) {
	    fatal("Could not kill(HUP) process $daemonpid ($daemon): $!");
	}
311
    }
312
}
313 314
else {
    # Not supporting Samba at this time. 
315
    if (!$issamba) {
316 317 318 319 320 321 322
	#
	# run exportfs. linux handles exports changes much more gracefully
	# then freebsd does.
	#
        system($exportfs) == 0 or
            fatal("Could not run $exportfs\n");
    }
Leigh Stoller's avatar
Leigh Stoller committed
323
}
324

325
#
326
# In FreeBSD, must allow time to react since HUP'ing mountd causes all
327 328
# mounts to briefly become invalid. Our caller (exports_setup) checks for
# this, but we need to make sure that processing has at least started!
329
#
330 331 332
# Note that Utah has hacked mountd to write a timestamp when it is done.
# Hence we can tell exactly when it has finished. In this case, our caller
# will not wait at all.
333
#
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
if ($checkstamp) {
    # On Utah Emulab with thousands of mount points, this can take 15 seconds!
    my $wtime = $incremental ? 8 : 15;

    for (my $i = 0; $i < $wtime; $i++) {
	print "exports_setup.proxy: waiting for mountd to finish ($i)...\n";
	if (-e "/var/run/mountd.ts") {
	    print "exports_setup.proxy: mountd done.\n";
	    last;
	}
	sleep(1);
    }
} else {
    sleep(1);
}
349

350 351 352 353 354
exit(0);

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

355
    SENDMAIL($TBOPS, "Exports Setup Failed", $msg);    
356 357 358
    die($msg);
}