#!/usr/bin/perl -w
#
# Copyright (c) 2011 University of Utah and the Flux Group.
# 
# {{{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/>.
# 
# }}}
#

#
# This is a hack to avoid Emulab and systemd conflicts.  Basically, sometimes
# our MFSes add swap devices to /etc/fstab that are incorrect (i.e., /dev/hda 
# instead of /dev/sda).  We could always add 'noauto' to the mount options so 
# systemd would ignore it, but we have legacy or deployed MFSes to deal with.
# When systemd encounters one of these, it tries to start the device and halts
# the boot process for a long time while waiting for the device to "start".
#
# We can't edit the bogus /etc/fstab entry before system reads it, because 
# because system reads it while the root is still mounted read-only, very early
# on in the startup process.
#
# systemd doesn't allow us to remove units (a swap device is a unit, just like 
# a service is a unit), so all we can do is, before the swap unit runs, cancel
# any pending systemd jobs before they try to initialize the the bogus device(s).
#

use English;
use strict;

# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }

my $FIXER = "$BINDIR/fixup-fstab-swaps -E";

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

open(LOG,">>$LOGDIR/emulab-systemd-swap.log");

my @output = `$FIXER`;
my %removed = ();
my %added = ();

foreach my $line (@output) {
    chomp($line);
    if ($line =~ /^Removing.*\/dev\/(.*)$/) {
	$removed{$1} = 0;
    }
    elsif ($line =~ /^Using\s+\/dev\/([^ ]+)\s+.*$/) {
	$added{$1} = 0;
    }
}

foreach my $rdev (keys(%removed)) {
    # it got added back, so don't bother deleting the systemd unit.
    if (exists($added{$rdev})) {
	delete $removed{$rdev};
    }
}

if (!keys(%removed)) {
    print LOG "No bogus Emulab swap devs to fix.\n";
    exit(0);
}

print LOG "Will try to remove systemd jobs for bogus swap devices: " .
    join(' ',keys(%removed)) . "\n";

@output = `systemctl --full list-jobs`;

foreach my $line (@output) {
    chomp($line);
    if ($line =~ /^\s*(\d+)\s+([^\s]+)\s+/) {
	my ($job,$unit) = ($1,$2);
	foreach my $rdev (keys(%removed)) {
	    if ($unit =~ /^dev-$rdev\.(swap|device)$/) {
		print LOG "Emulab canceling bogus swap device init job ($job,$unit).\n";
		system("systemctl cancel $job");
		$removed{$rdev} += 1;
	    }
	}
    }
}

foreach my $rdev (keys(%removed)) {
    if ($removed{$rdev} == 2) {
	delete $removed{$rdev};
	print LOG "Successfully canceled systemd jobs for bogus swap device $rdev.\n";
    }
}

if (keys(%removed)) {
    print LOG "Failed to cancel systemd jobs for bogus swap devices: " .
	join(' ',keys(%removed)) . "\n";
    exit(keys(%removed));
}

exit(0);
