diff --git a/clusterd/clusterd b/clusterd/clusterd index 510cfb7..fe40fef 100755 --- a/clusterd/clusterd +++ b/clusterd/clusterd @@ -579,6 +579,7 @@ our %UDP_HANDLER= 'restart' => \&udp_rx_restart, 'flush-gidcache' => \&udp_rx_flush_gidcache, 'make-automaps' => \&udp_rx_make_automaps, + 'reexport' => \&udp_rx_reexport, 'log' => \&udp_rx_log, 'exec' => \&udp_rx_exec, 'push' => \&udp_rx_push, @@ -1089,6 +1090,10 @@ sub udp_rx_make_automaps { system '/sbin/make-automaps'; } +sub udp_rx_reexport { + system '/usr/bin/mxmount --reexport-only'; +} + #----------- tcp mgmt console ----------------------------- our $MGMT_PORT=234; @@ -1824,6 +1829,7 @@ usage: $0 [options] --exec @node cmd [args...] # execute cmd on node --flush-gidcache # flush rpc auth.unix.gid cache on all nodes --make-automaps # execute /usr/sbin/make-automaps on all nodes + --reexport # execute /usr/bin/mxmount --reexport-only on all nodes --lsof=pattern @@ -1851,6 +1857,7 @@ GetOptions 'send-restart' => \$options{'send-restart'}, 'flush-gidcache' => \$options{'flush-gidcache'}, 'make-automaps' => \$options{'make-automaps'}, + 'reexport' => \$options{'reexport'}, 'lsof=s' => \$options{'lsof'}, ) or die USAGE; @@ -1885,6 +1892,10 @@ if (defined $options{'push'}) { sync_cluster_pw() or die "$CLUSTER_PW_FILE: $!\n"; $donald_s=new My::Select::INET(Proto=>'udp') or die "$!\n"; udp_broadcast_message($donald_s,'make-automaps'); +} elsif (defined $options{'reexport'}) { + sync_cluster_pw() or die "$CLUSTER_PW_FILE: $!\n"; + $donald_s=new My::Select::INET(Proto=>'udp') or die "$!\n"; + udp_broadcast_message($donald_s,'reexport'); } elsif (defined $options{'daemon'}) { $options{'kill'} and Donald::Tools::kill_previous_server('clusterd') and sleep 2; diff --git a/install.sh b/install.sh index 9bdf6e0..fcbf487 100755 --- a/install.sh +++ b/install.sh @@ -127,4 +127,6 @@ install_data clusterd/clusterd.service "$DESTDIR$systemdunitdi install_exec clusterd/clusterd "$DESTDIR$usr_sbindir/clusterd" install_exec setuid/setuid "$DESTDIR$usr_sbindir/setuid" install_exec uvpn/uvpn "$DESTDIR$usr_bindir/uvpn" +install_exec mxmount/mxmount "$DESTDIR$usr_bindir/mxmount" +install_data mxmount/mxmount.service "$DESTDIR$systemdunitdir/mxmount.service" exit diff --git a/mxmount/mxmount b/mxmount/mxmount new file mode 100755 index 0000000..11e3056 --- /dev/null +++ b/mxmount/mxmount @@ -0,0 +1,284 @@ +#! /usr/local/system/perl/bin/perl + +use strict; +use warnings; +use Sys::Hostname; +use Getopt::Long; + +my $configfile = "/etc/mxmounts"; +my $exports = "/etc/exports"; + +my $exportstag = "# DO NOT EDIT BELOW THIS LINE # created by /usr/bin/mxmount #"; + +my $fullhostname; +my $hostname; +my @lines; +my @exports; +my %V; +my %D; + +our $USAGE=<<"_EOF_"; +usage: $0 + $0 --reexport-only +_EOF_ + +our ($opt_reexport_only); + +$fullhostname = hostname(); +($hostname) = $fullhostname =~ /^(.*?)\./; + +%D = (); +%V = ( + DEFAULT_MOUNT_OPTIONS => '', + DEFAULT_EXPORT_OPTIONS => '', + DEFAULT_MOUNT_PREFIX => '/', + + SHORTHOST => $hostname, +); + +@lines = read_file($configfile); +@exports = read_file_raw($exports); + +@lines = parse_variables(@lines); + +@lines = parse_data(@lines); + +foreach(@lines) { + print STDERR "skipping: '$_'\n"; +} + +add_data0_if_not_present(); + +my %options; +GetOptions ( + 'reexport-only' => \$opt_reexport_only, +) or die $USAGE; +@ARGV and die $USAGE; + +mount_all() unless $opt_reexport_only; +create_exports(); +system("exportfs -ra"); + +sub safe_qx { open my $pipe,'-|',@_; return join('',<$pipe>) } + +sub add_data0_if_not_present { + my $allmp = $D{$hostname}; + + $allmp = [] unless (defined $allmp); + + foreach my $mp (sort { $a->{mountpoint} cmp $b->{mountpoint} } @$allmp ) { + if ($mp->{label} eq "data0") { + return; + } + if ($mp->{mountpoint} eq "$V{DEFAULT_MOUNT_PREFIX}/$hostname/0") { + print STDERR "$mp->{mountpoint} already blocked by $mp->{label}\n"; + return; + } + } + parse_data("$hostname !data0"); +} + +sub create_exports { + my $allmp = $D{$hostname}; + my $newexport=system('hostconfig newexport')==0; + + open(EXPORTS, '>', $exports) or die "can't open $exports: $!"; + + foreach my $exp (@exports) { + chomp($exp); + next if($exp =~ /^\s*$/); + last if($exp eq $exportstag); + print EXPORTS "$exp\n"; + } + print EXPORTS "\n$exportstag\n\n"; + + foreach my $mp (@$allmp) { + next if($mp->{noexport}); + unless ($newexport) { + my @CMD = ($mp->{mountpoint}, $mp->{exportopts}); + print join " ", "$exports: ", @CMD, "\n"; + print EXPORTS join " ", @CMD, "\n"; + } else { + my ($mountpoint,$exportopts)=($mp->{mountpoint}, $mp->{exportopts}); # '/amd/theinternet/1','@amd(sync,rw,...)' + my ($hostspec,$optspec)=$exportopts=~/^([^(]+)(.*)/; # '@amd','(sync,rw,...)' + my ($opts)=$optspec=~/\((.*)\)/; # 'sync,rw,...' + my $hosts=''; + warn "export $mountpoint to $hostspec opts $opts\n"; + if (my ($group) = $hostspec=~/^@(.+)/) { + $hosts=safe_qx('/usr/sbin/hostconfig','--list',$group); # expanded group + $hosts or warn "group $group is empty\n"; + } else { + $hosts=$hostspec; # single host + } + $hosts and printf EXPORTS "%s -%s %s\n",$mountpoint,$opts,$hosts; + } + } + close EXPORTS; +} + +sub mount_all { + my $allmp = $D{$hostname}; + my @CMD; + foreach my $mp (sort { $a->{mountpoint} cmp $b->{mountpoint} } @$allmp) { + + @CMD = (); + + push @CMD, 'mount', "LABEL=$mp->{label}", $mp->{mountpoint}; + + if($mp->{fs}) { + push @CMD, '-t', $mp->{fs}; + } + if($mp->{mountopts}) { + push @CMD, '-o', $mp->{mountopts}; + } + system('mkdir','-p',$mp->{mountpoint}); + print STDERR join " ", @CMD, "\n"; + system(@CMD); + } +} + +sub parse_data { + my @lines = @_; + my @invalid = (); + my @data; + + my $rest; + + foreach(@lines) { + @data = split /\s+/, $_; + + unless($data[1]) { + push @invalid, $_; + next; + } + + my $D = {}; + + $D->{line} = $_; + + $D->{host} = $data[0]; + $D->{label} = $data[1]; + if($D->{label} =~ /^!(.*)/) { + $D->{noexport} = 1; + $D->{label} = $1; + } + + if($D->{label} =~ /(.*):(.*)/) { + $D->{label} = $1; + $D->{mountpoint} = $2; + } else { + if($D->{label} =~ /^X/) { # X: decent controller based raid + $D->{mountpoint} = 'X/' . $D->{label}; + $D->{label} = lc($D->{label}); + } elsif($D->{label} =~ /^C/) { # C: controller based raid, confidential + $D->{mountpoint} = 'C/' . $D->{label}; + $D->{label} = lc($D->{label}); + if (!$data[3]) { + $D->{noexport} = 1; + } + } elsif($D->{label} =~ /^D/) { # D: scratch software raid (mdadm) + $D->{mountpoint} = 'D/' . $D->{label}; + $D->{label} = lc($D->{label}); + } elsif($D->{label} =~ /^M/) { # M: decent software raid (mdadm) + $D->{mountpoint} = 'M/' . $D->{label}; + $D->{label} = lc($D->{label}); + } elsif($D->{label} =~ /^data(.*)/) { + $D->{mountpoint} = $1; + } else { + warn "mxmount: unknown shortlabel '$D->{label}'.. skipping.."; + next; + } + } + if($D->{mountpoint} !~ m(^\/)) { + $D->{mountpoint} = "$V{DEFAULT_MOUNT_PREFIX}/" . $D->{mountpoint}; + } + + $D->{fs} = 'auto'; + $D->{mountopts} = $data[2] ? $data[2] : $V{DEFAULT_MOUNT_OPTIONS}; + if($D->{mountopts} =~ /\[(.*)\](.*)/) { + $D->{fs} = $1; + $D->{mountopts} = $2; + } + + $D->{exportopts} = $data[3] ? $data[3] : $V{DEFAULT_EXPORT_OPTIONS}; + + foreach(qw(host label mountpoint fs mountopts exportopts )) { + $D->{$_} = expand_variables($D->{$_}); + } + push @{$D{$D->{host}}}, $D; + } + return @invalid; +} + +sub expand_variables { + my $s = shift; + + foreach my $k (keys %V) { + $s =~ s/$k/$V{$k}/g; + } + return $s; +} + +sub parse_variables { + my @lines = @_; + my @invalid = (); + + my ($key, $value); + + foreach(@lines) { + if(($key, $value) = /^(\S+)=\s*(.*)$/) { + $V{$key} = $value; + } else { + push @invalid,expand_variables($_); + } + } + return @invalid; +} + +sub read_file { + my $file = shift; + open F, "$file" or die "can't open $file: $!\n"; + + my @lines=(); + my $line=""; + my $cont=0; + + while() { + chomp; + next if(/^\s*#/ or /^\s*$/); + + $cont=0; + + s/#.*$//; # remove comments.. + + $_ = $line . $_; + + if(s/\\\s*$//) { + # line continous in next line.. + $cont=1; + } + + $line = $_; + + unless($cont) { + $line =~ s/\s+/ /g; + $line =~ s/^\s+//; + $line =~ s/\s+$//; + push @lines, $line; + $line=""; + } + } + close F; + return @lines; +} + +sub read_file_raw { + my $file = shift; + my @lines; + + open F, "$file" or return; + @lines = ; + close F; + + return @lines; +} diff --git a/mxmount/mxmount.service b/mxmount/mxmount.service new file mode 100644 index 0000000..6434001 --- /dev/null +++ b/mxmount/mxmount.service @@ -0,0 +1,12 @@ +[Unit] +Description=MX mount local data filessystems +After=mxraid.startup.service +ConditionPathExists=/etc/mxmounts + +[Service] +Type=oneshot +ExecStart=/usr/bin/mxmount +RemainAfterExit=yes + +[Install] +WantedBy=local-fs.target