#!/bin/bash
exec /usr/bin/perl -w - $* <<'__END__'

# Can't do "#!/usr/bin/perl -w" because of the way RHCS's
# /usr/share/cluster/script.sh invokes the script file.  It does:
#
#   exec /bin/sh <script> <start|stop|status>
#
# and sh interprets <script> as a shell script despite #!/usr/bin/perl.

use strict;
use Sys::Syslog;
use FileHandle;
use File::Basename;
use Fcntl ':flock';

my $ZIMBRA_HOME = '/opt/zimbra';
my $ZIMBRA_CLUSTER_HOME = '/opt/zimbra-cluster';
my $DATA_HOME = "$ZIMBRA_CLUSTER_HOME/mountpoints";
my $STATUS_FILENAME = "$ZIMBRA_CLUSTER_HOME/status/status.dat";
my $TIMEOUT = 60;    # seconds
my $AGE_LIMIT = 60;  # seconds

my @DATA_DIRS = qw(conf log redolog db/data backup logger/db/data
                   openldap-data postfix/spool);


sub usage() {
    print STDERR <<_USAGE_;
Usage: zmcluctl <start|stop|status>

Zimbra service name must be passed in as OCF_RESKEY_service_name environment
variable.
_USAGE_
    exit(1);
}

sub rootCheck() {
    my ($name, $passwd, $uid) = getpwent();
    if ($uid != 0) {
    print STDERR "This script must be run as root.";
    exit(1);
    }
}

#
# logging
#

sub logSyslog($$) {
    my ($level, $msg) = @_;
    openlog('zimbra-cluster', 'pid', 'local0');
    syslog($level, $msg);
    closelog();
    print STDERR $msg . "\n";
}

sub logError($) {
    my $msg = shift;
    logSyslog('err', $msg);
}

sub logWarning($) {
    my $msg = shift;
    logSyslog('warning', $msg);
}

sub logInfo($) {
    my $msg = shift;
    logSyslog('info', $msg);
}

#
# locking
#

sub lockStatusFile() {
    if (! -f $STATUS_FILENAME) {
        my ($name, $dir) = fileparse($STATUS_FILENAME);
        if (! -d $dir) {
            system("rm -rf $dir; mkdir -p $dir");
        }
        system("rm -rf $STATUS_FILENAME");  # in case it exists but is not a file
    }

    my $fh = new FileHandle;
    if (!open($fh, "+>> $STATUS_FILENAME")) {
        logError("Unable to open $STATUS_FILENAME: $!");
        exit(1);
    }
    $fh->autoflush(1);

    my $locked = 0;
    my $timeout = $TIMEOUT;
    while (!$locked && $timeout-- > 0) {
        $locked = flock($fh, LOCK_EX | LOCK_NB);
        sleep(1);
    }
    if (!$locked) {
        logError("Unable to lock status file; exceeded ${TIMEOUT}-second timeout");
        exit(1);
    }
    return $fh;
}

sub unlockStatusFile($) {
    my $fh = shift;
    flock($fh, LOCK_UN);
    close($fh);
}

sub touchStatusFile() {
    my $now = time;
    utime($now, $now, $STATUS_FILENAME);
}

#
# get/set/clear current service in lock file
#

sub getCurrentServiceFromFH($) {
    my $fh = shift;

    # Was the status file not updated for too long?
    my $age = 0;
    my $now = time();
    if (-f $STATUS_FILENAME) {
        my @s = stat($STATUS_FILENAME);
        if (scalar(@s) >= 10) {
            my $size = $s[7];
            if ($size > 0) {
                my $mtime = $s[9];
                $age = $now - $mtime;
            }
        }
    }

    if ($age < $AGE_LIMIT) {
        $fh->seek(0, 0);
        my $val = $fh->getline() || '';
        chomp($val);
        return $val;
    } else {
        # Some possible reasons for lock file being too old:
        #
        # 1. Node rebooted after a hard failure.
        # 2. Startup is taking too long.

        # Find the actual service that's running.
        my $actual = '';
        my $lcxml = "$ZIMBRA_HOME/conf/localconfig.xml";
        if (-f $lcxml) {
            my $cmd = "su - zimbra -c 'zmlocalconfig -m nokey zimbra_server_hostname'";
            if (open(CMD, "$cmd |")) {
                $actual = <CMD> || '';
                chomp($actual);
                close(CMD);
            }
        }

        # What does the status file claim?
        $fh->seek(0, 0);
        my $claimed = $fh->getline() || '';
        chomp($claimed);

        my $newval = '';
        if ($claimed eq $actual) {
            $newval = $claimed;
        } elsif ($claimed ne '') {
            logInfo("Clearing stale status file.  Old content = " .
                    ($claimed ne '' ? $claimed : "<empty>"));
            $fh->seek(0, 0);
            truncate($fh, 0);
        }
        touchStatusFile();
        return $newval;
    }
}

sub getCurrentService() {
    my $fh = lockStatusFile();
    my $svc = getCurrentServiceFromFH($fh);
    unlockStatusFile($fh);
    return $svc;
}

sub setCurrentService($) {
    my $svcname = shift;
    my $fh = lockStatusFile();
    my $currentService = getCurrentServiceFromFH($fh);

    if ($currentService eq '') {
        $fh->seek(0, 0);
        truncate($fh, 0);
        $fh->print($svcname . "\n");
        touchStatusFile();
        unlockStatusFile($fh);
        return 1;
    } elsif ($currentService eq $svcname) {
        touchStatusFile();
        unlockStatusFile($fh);
        return 1;
    } else {
        logError("This node is already running Zimbra service $currentService.");
        unlockStatusFile($fh);
        return 0;
    }
}

sub clearCurrentService($) {
    my $svcname = shift;
    my $fh = lockStatusFile();
    my $currentService = getCurrentServiceFromFH($fh);

    if ($currentService eq '') {
        unlockStatusFile($fh);
        return 1;
    } elsif ($currentService eq $svcname) {
        $fh->seek(0, 0);
        truncate($fh, 0);
        touchStatusFile();
        unlockStatusFile($fh);
        return 1;
    } else {
        logInfo("This node is already running Zimbra service $currentService.");
        unlockStatusFile($fh);
        return 0;
    }
}

sub touchCurrentService($) {
    my $svcname = shift;
    my $fh = lockStatusFile();
    my $currentService = getCurrentServiceFromFH($fh);

    if ($currentService eq '') {
        unlockStatusFile($fh);
        return 1;
    } elsif ($currentService eq $svcname) {
        touchStatusFile();
        unlockStatusFile($fh);
        return 1;
    } else {
        logInfo("This node is already running Zimbra service $currentService.");
        unlockStatusFile($fh);
        return 0;
    }
}

#
# crontab
#

sub restoreCrontab() {
    my $crontab = "$ZIMBRA_HOME/conf/cron/crontab";
    if (-e $crontab) {
        system("crontab -u zimbra $crontab");
    }
}

sub backupCrontab() {
    my $crondir = "$ZIMBRA_HOME/conf/cron";
    my $crontab = "$crondir/crontab";
    if (! -d $crondir) {
        system("rm -f $crondir; mkdir -p $crondir");
    }
    system("crontab -u zimbra -l > $crontab");
}

#
# symlinks to service mountpoint(s)
#

sub createServiceDataSymlinks($) {
    my $svcname = shift;

    my $dataRoot = "$DATA_HOME/$svcname";
    if (! -e $dataRoot) {
        logError("Missing service data root directory $dataRoot");
        exit(1);
    }

    foreach my $dir (@DATA_DIRS) {
        my $real = "$dataRoot/$dir";
        my $link = "$ZIMBRA_HOME/$dir";
        if (-e $real) {
            if (-l $link) {
                my $rc = unlink($link);
                if (!$rc) {
                    logError("Unable to remove symlink $link");
                    exit(1);
                }
            } elsif (-d $link) {
                # Delete if link target is an empty directory.
                my $rc = rmdir($link);
                if (!$rc) {
                    logError("Unable to delete directory $link");
                    exit(1);
                }
            } elsif (-e $link) {
                logError("A file aleady exists instead of symlink $link");
                exit(1);
            }

            my ($name, $parent) = fileparse($link);
            if (! -e $parent) {
                system("mkdir -p $parent");
                if ($parent ne $ZIMBRA_HOME && $dir ne 'postfix/spool') {
                    system("chown zimbra:zimbra $parent");
                }
            }

            my $rc = symlink($real, $link);
            if (!$rc) {
                logError("Unable to create symlink $link to $real");
                exit(1);
            }
        }
    }
}


#
# main
#

rootCheck();

if (! -d $ZIMBRA_HOME) {
    logError("Missing Zimbra home directory $ZIMBRA_HOME");
    exit(1);
}

my $action = $ARGV[0] || '';
my $svcname = $ENV{'OCF_RESKEY_service_name'};
if (!defined($svcname) || $svcname eq '') {
    logError("Missing service name (OCF_RESKEY_service_name env var not set)");
    exit(1);
}

if ($action eq 'start') {
    if (!setCurrentService($svcname)) {
        # Another Zimbra service is running.
        exit(1);
    }
    createServiceDataSymlinks($svcname);
    restoreCrontab();
    my $rc = system("su - zimbra -c 'zmcontrol start'");
    $rc >>= 8;
    if ($rc != 0) {
        clearCurrentService($svcname);
    }
    exit($rc);
} elsif ($action eq 'stop') {
    my $curr = getCurrentService();
    if ($curr eq $svcname) {
        system("su - zimbra -c 'zmcontrol stop'");
        clearCurrentService($svcname);
        exit(0);
    } elsif ($curr eq '') {
        # No Zimbra service is running.
        exit(0);
    } else {
        # Another Zimbra service is running.
        exit(0);
    }
} elsif ($action eq 'status') {
    my $curr = getCurrentService();
    if ($curr eq '') {
        # No Zimbra service is running.
        exit(1);
    } elsif ($curr ne $svcname) {
        # Another Zimbra service is running.
        exit(1);
    } else {
        touchCurrentService($svcname);
        my $rc = system("su - zimbra -c 'zmcontrol status'");
        $rc >>= 8;
        if ($rc == 0) {
            backupCrontab();
        }
        exit($rc);
    }
}

print STDERR <<_USAGE_;
Usage: zmcluctl <start|stop|status>

The name of the Zimbra service being controlled must be set in
OCF_RESKEY_service_name environment variable.
_USAGE_

exit(1);
