#!/usr/local/bin/perl
#
# DESC:
#   Program is used to sychronize write access to public motd file.
#   Program acquires advisory exclusive lock, then brings up editor
#   to edit file.  On return from editor, motd file will be unlocked.
#   If program cannot acquire exclusive lock, it prints out the current
#   user holding exclusive lock.
#
# WRITTEN BY:
#   Andrew Choi
# Fixed by:
#   Ben Scott
#   Arvin Hsu

use strict;
use Fcntl ':flock';
use Errno;
use Getopt::Std;
use File::Copy;

my $motd = "/etc/motd.public";
my $motdbackup = "/tmp/motd.public.b.$>.$$";	# EUID + PID
my $motdcurrent = "/tmp/motd.public.r.$>.$$";	# EUID + PID
my $motdtemp = "/tmp/motd.public.t.$>.$$";	# EUID + PID
my $motdmerged = "/tmp/motd.public.m.$>.$$";	# EUID + PID

$motd = "/csua/tmp/motd.kinney" if (getpwuid($<))[0] eq 'kinney';
my $editor = $ENV{MOTDEDITOR};
$editor ||= $ENV{VISUAL};
$editor ||= $ENV{EDITOR};
$editor ||= "/usr/bin/ed";

my %opt;

$opt{t} = 600;
getopts("mnst:h?",\%opt);
&Usage if $opt{h};

## Set environment vars for vi users
## Thanks, jon.

$ENV{NEXINIT} ||= $ENV{EXINIT};
unless ( $ENV{NEXINIT} ) {
  unless (open (EXRC,"$ENV{HOME}/.nexrc")) {
    unless (open (EXRC,"$ENV{HOME}/.exrc")) {
      $ENV{NEXINIT} = "set nolock";
    }
  }
  unless ($ENV{NEXINIT}) {
    ($ENV{NEXINIT} = join ("",<EXRC>)) && close EXRC;
  }
}
$ENV{NEXINIT} .= "\nset nolock\n";

# Set alarm for being locked too damn long
$SIG{ALRM} = \&Unlock;
alarm $opt{t};

# Open file as append so that lock can be used on it
open(M, "$motd") || die "open $motd: $!";

# Lock file.  If success, execute edit command and exit
flock(M, LOCK_EX|LOCK_NB);
if ($!{EWOULDBLOCK}) {
    if ($opt{n}) {
	print "Motd currently locked.  Editing anyways.";
	&Edit;
    }
    else {
	flock (M, LOCK_UN);
	print "Motd currently locked\nWait to acquire lock [y/N] ";
	chomp(my $ans = <>);
	if ($ans =~ /^y$/i) {
	    flock(M, LOCK_EX);
	    &Edit;
	}
    }
} else {
    &Edit;
}

close M;
system "/csua/bin/mtd";
exit 0;

sub Unlock {
  flock(M, LOCK_UN);
  print
"\r*** MOTD GOD PRONOUNCES: ***                                                 \r\n*** You have locked the motd for too long, you idle hoser... ***            \r\n*** Your lock has been removed. ***                                         \r\n";
}

sub Usage {
  print STDERR
"$0	Edit /etc/motd.public with locking and merge.

Usage: $0 [-m] [-s] [-t n]
  -t n    Set timeout for releasing lock to n seconds	(default $opt{t})
  -m      Turns auto-merging off. 
  -s      If merge fails, will stomp over other changes.
  -n      Does not wait for lock before allowing edit.
          (the other person may overwrite your post)
";
  exit 2;
}

sub Edit {
    alarm $opt{t};
    print "";
    #check if merge option is desired
    if (!$opt{m}) {
	copy($motd,$motdbackup);
	copy($motd,$motdcurrent);
	my $lastmodtime = (stat($motd))[9];
	system("$editor $motdcurrent");
	my $currmodtime = (stat($motd))[9];
	if ($currmodtime > $lastmodtime) {
	    &Merge;
	}
	else {
	    copy($motdcurrent,$motd);
	}
	unlink </tmp/motd.public.*>;
    }
    else {
	system("$editor $motd");
    }
}


# Attempts to auto-merge using "merge, diff -c | patch, diff | patch"
# If auto-merge fails, allows manual merge
# Saves final version as $motd
sub Merge {
    copy($motd,$motdmerged);
    system("merge -p -q -L \"Other Changes Below\" -L \"Original MOTD\" -L \"Your Changes Above\" $motd $motdbackup $motdcurrent > $motdtemp");
    
    # Check to see if merge failed. if fail, try diff -c | patch
    if (($? >> 8) > 0) {  
	system("diff -c $motdbackup $motdcurrent | patch -s -f $motdmerged 2> /dev/null"); 
	
	# Check to see if diff -c | patch, if fail, try diff | patch
	if (($? >> 8) > 0) {
	    system("diff $motdbackup $motdcurrent | patch -s -f $motdmerged 2> /dev/null");
	    
	    # If everything has failed, put the merge notation in, and let the user edit.
	    if (($? >> 8) > 0) {
		if ($opt{s}) {
		    copy($motdcurrent,$motdmerged);
		}
		else {
		    print "CONCURRENT CHANGES\r\nAll merge & patch attempts have failed.\r\nMerge Manually? [y/n]"; 
		    chomp(my $answ = <>);
		    if ($answ =~ /^y$/i) {
			copy($motdtemp,$motdcurrent);
			copy($motd,$motdbackup);
			my $lastmodtime = (stat($motd))[9];
			system("$editor $motdcurrent");
			my $currmodtime = (stat($motd))[9];
			if ($currmodtime > $lastmodtime) {
			    &Merge;
			}
			else {
			    copy($motdcurrent,$motdmerged);
			}
		    }
		    else {
			print "Stomp on other people's changes? [y/n]";
			chomp(my $answ = <>);
			if ($answ =~ /^y$/i) {
			    copy($motdcurrent,$motdmerged);
			}
		    }
		}
	    }
	}
    }
    else {
	# Merge was successful, copy it over.
	copy($motdtemp,$motdmerged);
    }
    copy($motdmerged,$motd);
}

