#!/usr/bin/perl
# -*- Mode: CPerl -*-
#
# patch the configuration files
# takes one argument - the PATCHDIR, typically underinstall/patch/<patchnum>

use Getopt::Long;		# for GetOptions
use File::Compare;		# for compare
use File::Basename;		# for dirname
use File::Path;			# for mkpath
use File::Copy;			# for move, copy

#
# usage
#
sub usage
{
print <<EOF;

Usage: $0 [--domain <domain>|ALL] <patchdir>

  --domain <domain>|ALL
    The hosted domain to operate on, or ALL for all hosted domains
    You can also use the short form of the switch -d instead of --domain

  <patchdir>: The patch directory area created during patchadd.
    e.g. $basedir/install/patch/118207-41

EOF
  die "  Exiting";
}

########################################
# Special code for files that have moved locations
# - return_header.opt
#   - moved config location: from config/locale/C/ to config template
# uses Global variable: $basedir
########################################
sub save_movedfiles {
  log_only("-- save_movedfiles being run\n");
  if ( ! -f "$basedir/config/return_header.opt" && -f "$basedir/config/locale/C/return_header.opt" ) {
    log_only ("copy $basedir/config/locale/C/return_header.opt $basedir/config/return_header.opt\n");
    copy ("$basedir/config/locale/C/return_header.opt", "$basedir/config/return_header.opt");
  }
}

########################################
# dryrun or devinstall
# used to determine owner/permission of new config files
# output to install_all.list
########################################
sub run_devinstall_dryrun {
  my($pkglist,$Cmd);
  log_only("-- Running run_devinstall_dryrun\n");
  $pkglist="pkgcfg:config:msg:msg_en:imta:msma:webmail";
  #
  $Cmd="$basedir/lib/devinstall -n -m -v -l $pkglist -i $basedir/lib/config-templates/config.ins $basedir/lib/config-templates $basedir/lib/jars $basedir/lib > $PATCHDIR/install_all.list 2>> $LOGFILE";
  logonly_cmd($Cmd);
}

########################################
# routine to add a final new line to a file if it does not have one
#
# diff3 will core dump if the file does not end in a newline
# so add a new line if it is missing
#
# - arg: the file
#
# temp variable: TMPFILE file descriptor
########################################
sub add_newline {
  my($file) = @_;
  my($end);
  open(TMPFILE, "$file") or die ("Cannot open $file -");
  seek(TMPFILE,-1,2);		# go to last char
  read(TMPFILE,$end,1);
  close(TMPFILE);
  if ($end eq "\n") {
    return;
  } else {
    log_only("--Adding extra line to $file\n");
    open(TMPFILE, ">>$file");
    print TMPFILE "\n";
    close(TMPFILE);
  }
}

########################################
# get_templates
# return old and new template files
#
########################################
sub get_templates {
  my($tfile) = @_;
  # usual case
  $tfile_old = "$BACKOUTDIR/$tfile";
  $tfile_new = "$basedir/$tfile";
  # special cases
  if ($tfile eq "lib/config-templates/job_controller.cnf") {
    log_only("copy $tfile_old $tfile_old.old.sed\n");
    # to preserve permissions
    copy("$tfile_old", "$tfile_old.old.sed");
    $Cmd = "/bin/sed -e 's:<ext.exe>::' $tfile_old > $tfile_old.old.sed";
    logonly_cmd($Cmd);
    log_only("copy $tfile_new $tfile_old.new.sed\n");
    # to preserve permissions
    copy("$tfile_new", "$tfile_old.new.sed");
    $Cmd = "/bin/sed -e 's:<ext.exe>::' $tfile_new > $tfile_old.new.sed";
    logonly_cmd($Cmd);
    $tfile_new = "$tfile_old.new.sed";
    $tfile_old = "$tfile_old.old.sed";
  }
  return($tfile_old,$tfile_new);
}


########################################
# patch config files
# - attempt to patch the user files, but do not actually overwrite
# - These are config files that do not have substitutions in them.
#   so they are "easier" to patch
#
# uses global variable $PATCHDIR
# creates NEWCONFIG file descriptor - writes $PATCHDIR/newconfig.list
# creates CONFIG    file descriptor - reads $PATCHDIR/config.list
# sets $tfile - template file
# sets $cfile - config file
########################################
sub patch_config {
  my($lineno,$stat);
  my($tfile,$cfile,$tfile_old,$tfile_new);
  log_only("-- patch_config being run\n");
  log_msg("-- Checking default domain\n");
  open(NEWCONFIG, ">$PATCHDIR/newconfig.list") or die("Cannot open $PATCHDIR/newconfig.list for writing -");

  open(CONFIG, "$PATCHDIR/config.list") or die ("Cannot open $PATCHDIR/config.list for reading -");
  $lineno = 0;
  while (<CONFIG>) {
    $lineno = $lineno + 1;
    chomp;
    # $tfile - template file, a path relative to $basedir
    # $cfile - config file, a path relative to $basedir
    ($tfile, $cfile) = split(/ /,$_,2);
    # preliminary check
    $stat = check_file($cfile);
    if ($stat == 0) {
      next;
    }
    # old and new template files
    ($tfile_old, $tfile_new) = get_templates($tfile);
    if ($tfile =~ /.*\.(gif|jpg|ico|png|jar)$/) {
      patch_binfile($tfile, $cfile, $tfile_old, $tfile_new);
    } elsif ($cfile eq "config/msg.conf") {
      patch_msgconf($tfile,$cfile);
    } else {
      patch_configfile($tfile,$cfile, $tfile_old, $tfile_new);
    }
  }
  close (NEWCONFIG);
  close (CONFIG);
}

########################################
# patch a single binary config file
#
# args: $tfile - template file
#       $cfile - config file
#       $tfile_old - old template file
#       $tfile_new - new template file
#
# uses global variable: $basedir, $SAVEDIR
# writes to NEWCONFIG file descriptor
# updates global variables: $NEWCONFIGS, $NEWEBINS
########################################
sub patch_binfile {
  my($tfile,$cfile,$tfile_old,$tfile_new) = @_;
  my($cmp_tfiles,$cmp_cfile,$dir);
  log_only("-- Running patch_binfile $tfile $cfile $tfile_old $tfile_new\n");
  # if ($tfile_old != $tfile_new && $tfile_new != $cfile) do something
  $cmp_tfiles = compare("$tfile_old", "$tfile_new");
  $cmp_cfile = compare("$tfile_new", "$basedir/$cfile");
  log_only("-- cmp_tfile = $cmp_tfiles cmp_cfile = $cmp_cfile\n");
  # cmp status is 0 if files are identical
  if ($cmp_tfiles != 0 && $cmp_cfile != 0) {
    # backup the $cfile, do not overwrite an existing backup
    $dir = dirname("$SAVEDIR/$cfile");
    if ( ! -d "$dir" ) {
      log_only("mkpath $dir 0 0755\n");
      mkpath("$dir",0,0755);
    }
    if ( ! -f "$SAVEDIR/$cfile" ) {
      log_only("copy $basedir/$cfile $SAVEDIR/$cfile\n");
      copy("$basedir/$cfile", "$SAVEDIR/$cfile");
    }
    if ( -f "$basedir/$cfile" ) {
      log_only("copy $basedir/$cfile $SAVEDIR/$cfile.new\n");
      copy("$basedir/$cfile", "$SAVEDIR/$cfile.new");
    } else {
      create_newconfigfile($cfile);
    }
    log_only("copy $tfile_new $SAVEDIR/$cfile.new\n");
    copy("$tfile_new", "$SAVEDIR/$cfile.new");
    $NEWCONFIGS = $NEWCONFIGS + 1;
    $NEWBINS = $NEWBINS + 1;
    log_msg("-- New: $basedir/$cfile\n");
    print NEWCONFIG "$cfile\n";
  } else {
    unlink("$SAVEDIR/$cfile.new");
    log_only("-- No patch required for $basedir/$cfile\n");
  }
}

########################################
# patch a single text config file
# using 3 way diff
#
# diff3 will core dump if the file does not end in a newline
# so add a new line if it is missing. Do not have to do it for 3rd
# file because we will make sure from now on we will not have files like 
# this.
#
# args: $tfile - template file
#       $cfile - config file
#       $tfile_old - old template file
#       $tfile_new - new template file
#
# uses global variable: $basedir, $SAVEDIR, $BACKOUTDIR, $UNAME
# writes to NEWCONFIG file descriptor
# updates global variables: $NEWCONFIGS, $NEWEBINS, $CONFLICTS
#
# tmp variable: EMPTYFILE file descriptor
########################################
sub patch_configfile {
  my($tfile,$cfile,$tfile_old,$tfile_new) = @_;
  log_only("-- Running patch_configfile $tfile $cfile $tfile_old $tfile_new\n");
  my ($file1, $file2, $file3);
  my ($dir,$Cmd,$stat,$diff3flag);
  # The 3 files for diff3
  $file1 = "$SAVEDIR/$cfile.new";
  $file2 = "$tfile_old";
  $file3 = "$tfile_new";
  # backup the $cfile, do not overwrite an existing backup
  $dir = dirname("$SAVEDIR/$cfile");
  if ( ! -d "$dir" ) {
    log_only("mkpath $dir 0 0755\n");
    mkpath("$dir",0,0755);
  }
  # create file1: $SAVEDIR/$cfile.new
  if ( -f "$basedir/$cfile" ) {
    if ( ! -f "$SAVEDIR/$cfile" ) {
      log_only("copy $basedir/$cfile $SAVEDIR/$cfile\n");
      copy("$basedir/$cfile", "$SAVEDIR/$cfile");
    }
    log_only("copy $basedir/$cfile $SAVEDIR/$cfile.new\n");
    copy("$basedir/$cfile", "$SAVEDIR/$cfile.new");
    add_newline($file1);
  } else {
    create_newconfigfile($cfile);
  }
  # file2
  #   if this is a new file
  if ( ! -f "$file2" ) {
    open (EMPTYFILE, ">$PATCHDIR/empty_file") or die ("could not create $PATCHDIR/empty_file");
    close (EMPTYFILE);
    $file2 = "$PATCHDIR/empty_file";
  } else {
    add_newline ($file2);
  }
  # see if $file3 exists
  if ( ! -f "$file3" ) {
    open (EMPTYFILE, ">$PATCHDIR/empty_file") or die ("could not create $PATCHDIR/empty_file");
    close (EMPTYFILE);
    $file3 = "$PATCHDIR/empty_file";
  }
  #
  $Cmd = "/usr/bin/diff3 -E $file1 $file2 $file3 > $SAVEDIR/$cfile.script 2>> $LOGFILE";
  $stat = logonly_cmd($Cmd);

  # set diff3flag - nonzero mean diff3 failure. diff3 return status is
  # different on different platforms
  $diff3flag = 0;
  if ( $UNAME eq "SunOS" ) {
    # nonzero means failure
    $diff3flag = $stat;
  } elsif ( $UNAME eq "Linux" ) {
    # failure only when return status is 2
    if ( $stat == 2 ) {
      $diff3flag = 2;
    }
  } else {
    $diff3flag = $stat;
  }

  if ( $diff3flag != 0 ) {
    log_msg("--\n");
    log_msg("-- diff3 failed with status=$stat for\n");
    log_msg("--   $basedir/$cfile\n");
    log_msg("-- Please merge changes by hand from the new file\n");
    log_msg("--   $basedir/$tfile\n");
  } else {
    # "echo wq" on Solaris is redundant, but is required on Linux
    $Cmd = "(/bin/cat $SAVEDIR/$cfile.script; /bin/echo 'wq') | /bin/ex -s $SAVEDIR/$cfile.new >> $LOGFILE 2>&1";
    logonly_cmd($Cmd);
    log_only("compare $basedir/$cfile $SAVEDIR/$cfile.new\n");
    $stat = compare("$basedir/$cfile", "$SAVEDIR/$cfile.new");
    if ( $stat != 0 ) {
      $Cmd = "/bin/grep -s '^=======\$' $SAVEDIR/$cfile.script >> $LOGFILE 2>&1";
      $stat = logonly_cmd($Cmd);
      $NEWCONFIGS = $NEWCONFIGS + 1;
      if ( $stat == 0 ) {
        log_msg("-- New with Conflicts: $basedir/$cfile\n");
        $CONFLICTS = $CONFLICTS + 1;
      } else {
        log_msg("-- New: $basedir/$cfile\n");
      }
      print NEWCONFIG "$cfile\n";
    } else {
      unlink("$SAVEDIR/$cfile.new");
      log_only("-- No patch required for $basedir/$cfile\n");
      unlink("$SAVEDIR/$cfile.script");
    }
  }
}

########################################
# this is called when a new config file is created by the patch
# the $SAVEDIR/$cfile.new is created with the right owner/permission
# owner/permission data taken from the $PATCHDIR/install_all.list created
# by run_devinstall_dryrun()
# Input: $cfile - patch relative to $basedir, e.g. config/smime.conf
# Output: chmod/chgrp/chmod of $SAVEDIR/$cfile.new
#
# temp variable: EMPTYFILE file descriptor
#                INSTALL_ALL file descriptor
########################################
sub create_newconfigfile {
  my($cfile) = @_;
  my($basefile);
  my($type,$owner,$group,$perm,$filename);
  log_only("-- create_newconfigfile $cfile\n");
  open (EMPTYFILE, ">$SAVEDIR/$cfile.new") or die ("could not create $SAVEDIR/$cfile.new");
  close (EMPTYFILE);
  # for config/html/* files, set owner/group to be runtime user/group. I find
  # this by looking up the user/group for config/magic.dat. It should also have
  # the perm of 644
  if ( $cfile =~ "config/html/") {
    $basefile = "config/magic.dat";
  } else {
    $basefile = $cfile;
  }
  open(INSTALL_ALL, "$PATCHDIR/install_all.list") or die ("Cannot open $PATCHDIR/install_all.list");
  while(<INSTALL_ALL>) {
    if ($_ =~ "$basefile") {
      ($type,$owner,$group,$perm,$filename) = split(/\s+/,$_);
      $Cmd = "/bin/chown $owner $SAVEDIR/$cfile.new >> $LOGFILE 2>&1";
      logonly_cmd($Cmd);
      $Cmd = "/bin/chgrp $group $SAVEDIR/$cfile.new >> $LOGFILE 2>&1";
      logonly_cmd($Cmd);
      $Cmd = "/bin/chmod $perm $SAVEDIR/$cfile.new  >> $LOGFILE 2>&1";
      logonly_cmd($Cmd);
    }
  }
  close(INSTALL_ALL);
}

########################################
# special hard coded stuff for things I do not handle generically
# - job_controller.cnf has a line added that has a substitution variable
#   in it. bug 6307120
#
# uses global variables: $SAVEDIR
########################################
sub special_hacks {
  my ($Cmd);
  log_only("-- special_hacks\n");
  if ( -f "$SAVEDIR/config/job_controller.cnf.new" ) {
    log_only("move $SAVEDIR/config/job_controller.cnf.new $SAVEDIR/config/job_controller.cnf.sed\n");
    move("$SAVEDIR/config/job_controller.cnf.new", "$SAVEDIR/config/job_controller.cnf.sed");
    # to preserve permissions 
    copy("$SAVEDIR/config/job_controller.cnf.sed", "$SAVEDIR/config/job_controller.cnf.new");
    $Cmd = "/bin/sed -e 's:<ext.exe>::' $SAVEDIR/config/job_controller.cnf.sed > $SAVEDIR/config/job_controller.cnf.new";
    logonly_cmd($Cmd);
  }
}

########################################
# determine the full path to this script's location
# set the variable scriptDir
#
# make $currDir and $scriptDir globally available
########################################
sub get_my_dir {
  $currDir=`pwd`;
  chomp $currDir;
  $scriptDir = dirname("$0");
  chdir $scriptDir;
  $scriptDir =`pwd`;
  chomp $scriptDir;
  chdir $currDir;
}

########################################
# Read list of deleted config variables
#  returns a hash whose keys are the deleted variables
########################################
sub ReadDeletedConfig
{
  my ($file) = @_;
  my (%deleted);
  if (! -e $file) {
    return(%deleted);
  }
  open(DELETEFILE, "<$file") or die "Cannot open $file";
  $lineno = 0;
  while (<DELETEFILE>) {
    $lineno = $lineno + 1;
    chomp;
    if ($_ =~ "^#") {		# comment lines
      next;
    } elsif ($_ eq "") {	# blank lines
      next;
    } else {
    }
    $deleted{$_} = "";
  }
  close (DELETEFILE);
  return(%deleted);
}

########################################
# Read a property file, return a hash table
# ARGs
#  conffile -  is the name of the file
#  hasset
#     if true, only read lines of the form "set key = value"
#     otherwise, lines are of the form "key = value"
# Returns a hashtable
########################################
sub ReadProp
{
  my($conffile,$hasset) = @_;
  log_only("-- Running ReadProp $conffile\n");
  my($lineno, $key, $value, %config);
  open(MSGCONF, "<$conffile") or die "Cannot open $conffile";
  $lineno = 0;
  while (<MSGCONF>) {
    $lineno = $lineno + 1;
    chomp;
    #print "DEBUG $lineno - |$_|\n";
    if ($_ =~ /^#/) {		# comment lines
      #print "DEBUG comment line $lineno detected in $conffile - |$_|\n";
      next;
    } elsif ($_ eq "") {	# blank lines
      #print "DEBUG empty line $lineno detected in $conffile - |$_|\n";
      next;
    } elsif ($_ =~ /^\s+$/) {	# whitespace lines
      #print "DEBUG whitespace line $lineno detected in $conffile - |$_|\n";
      next;
    } elsif ($_ !~ /=/) {	# ignore lines that do not have =
      #print "DEBUG no = in line $lineno detected in $conffile - |$_|\n";
      next;
    } elsif ($hasset && $_ !~ /^set/) {
      # ignore lines that do not start with set
      #print "DEBUG no ^set in line $lineno detected in $conffile - |$_|\n";
      next;
    } else {
    }
    ($key,$value) = split(/=/,$_,2);

    # remove leading and trailing whitespace
    $value =~ s/^\s+//;
    $value =~ s/\s+$//;
    $key =~ s/^\s+//;
    $key =~ s/\s+$//;
    # if it has leading and trailing double quotes, then remove them
#    if ($value =~ /^"/ && $value =~ /"$/) {
#      $value =~ s/^"//;
#      $value =~ s/"$//;
#    }
#    if ($key =~ /^"/ && $key =~ /"$/) {
#      $key =~ s/^"//;
#      $key =~ s/"$//;
#    }
    # if the key is "set <var>" then get rid of the "set "
    # This is for Devsetup.properties property file
    $key =~ s/^set //;

    # Account for cases where there are multivalued attributes
    if (!defined $config{$key})
    {
      # insert the keyvalue pair into a the %config hash table
      $config{$key} = $value ;
    } else {
      # oh oh should not happen
      log_msg ("SHOULD NOT HAPPEN - duplicate key $key val = $config{$key} val2 = $value\n");
      log_msg("  Occurred at line $lineno of the file $conffile\n");
      die ("Duplicate key");
    }

  }
  close(MSGCONF);
  return(%config);
}

########################################
# do substitutions
# args
#  $var - the variable to do substitions on
#  %subs - hashtable, substituted <$key> in $var with $value
########################################
sub doSubs
{
  my ($var, %subs) = @_;
  my ($key);

  # minimal match (i.e. not greedy) of things between angle brackets,
  # remember what was matched, and store it in $1
  #print "QQQ doSubs $var\n";

  while ($var =~ /<(.*?)>/) {
    if ( exists ($subs{$1}) ) {
      $sub = $subs{$1};		# look it up
      $var =~ s/<$1>/$sub/g;	# do the substitution
    } else {
      # okay dang, do it the hard way
      log_msg("WARNING found angle brackets with no sub for $1 in $var\n");
      foreach $key (keys(%subs)) {
	$sub = $subs{$key};
	$var =~ s/<$key>/$sub/g;
      }
      log_msg("  End result: $var\n");
      last;
    }
  }
  return ($var);
}

########################################
# Lifted this code from configproc.pl so that the same sorting is
# done
#
# sort function that takes into account the name parts (dot separated)
# a name located lower in the hierarchy should come later in the order
########################################
sub sortfn {
    local($result, $tmp);
    local($adots, $alast, $aprefix, $aleave);
    local($bdots, $blast, $bprefix, $bleave);
    local($sep);

    $result = $a cmp $b;
    $sep = ".";

# get $adots (number of dots in $a) and $a prefix (up to the last dot)    
    $tmp=0;
    $length = length($a);
    $alast=-1;
    for ($adots = 0;$tmp < $length;$adots++, $tmp++) {
	$tmp = index($a, $sep, $tmp);
	if ($tmp == -1) {
	    last;
	}
	$alast = $tmp;
    }
    $aleave = substr($a, $alast+1);
    $aprefix = $alast == -1 ? $sep : substr($a, 0, $alast);

# get $bdots (number of dots in $b) and $b prefix (up to the last dot)    
    $tmp=0;
    $length = length($b);
    $blast=-1;
    for ($bdots = 0;$tmp < $length;$bdots++, $tmp++) {
	$tmp = index($b, $sep, $tmp);
	if ($tmp == -1) {
	    last;
	}
	$blast = $tmp;
    }
    $bleave = substr($b, $blast+1);
    $bprefix = @blast == -1 ? $sep : substr($b, 0, $blast);

#    printf("%s(%s) %s(%s)\n",$a, $aprefix, $b, $bprefix);
# if b has more dots than a and both shared the same prefix then
# a should be first in order and similarly for the reverse
    if ($adots < $bdots) {
	if (($adots == 0) || (($aprefix eq substr($b, 0, $alast)) && 
			      (substr($b, $alast, 1) eq $sep))) {
	    $result = -1;
	}
    }
    elsif ($adots > $bdots) {
	if (($bdots == 0) || (($bprefix eq substr($a, 0, $blast)) && 
			      (substr($a, $blast, 1) eq $sep))) {
	    $result = 1;
	}
    }
    return $result;
}

########################################
# compare the values of configutil variables
#
# does a string compare, but also takens into consideration
#  that the string may be quoted
# args
#   $v1 - first value
#   $v2 - second value
# returns
#   0 if equal
#   1 if non-equal
########################################
sub compareVals
{
  my($v1,$v2) = @_;
  # if it has leading and trailing double quotes, then remove them
  if ($v1 =~ /^"/ && $v1 =~ /"$/) {
    $v1 =~ s/^"//;
    $v1 =~ s/"$//;
  }
  if ($v2 =~ /^"/ && $v2 =~ /"$/) {
    $v2 =~ s/^"//;
    $v2 =~ s/"$//;
  }

  if ($v1 ne $v2) {
    return 1;
  } else {
    return 0;
  }
}

########################################
# patch_msgconf
# args
#   $tfile - template file relative to $basedir
#   $cfile - config file relative to $basedir
# notes
#  $basedir/lib/config-templates/Devsetup.properties - read in for
#       substitutions
#  $SAVEDIR/$cfile     - saved config file
#  $SAVEDIR/$cfile.new - full path to new merged file
#  $BACKOUTDIR/$tfile  - full path to old template file
#  $basedir/$tfile     - full path to new template file
#  move all the $basedir/config/msg.conf.<timestamp> files
#  move all the $basedir/config/local.conf.<timestamp> files
#  move $basedir/config/local.conf - its existence triggers configutil
#     automigration to msg.conf
#  uninstall-newconfig restoring the msg.conf.<timestamp> and
#     local.conf.<timestamp> files is done in the uninstall-newconfig script
#
#  uses compareVals to compare values
########################################
sub patch_msgconf
{
  my($tfile,$cfile) = @_;
  log_only("-- Running patch_msgconf $tfile $cfile\n");
  my ($conflict, $f);
  $conflict = 0;

  # backup $basedir/$cfile, do not overwrite an existing backup
  # also create $SAVEDIR/$cfile.new
  $dir = dirname("$SAVEDIR/$cfile");
  if ( ! -d "$dir" ) {
    log_only("mkpath $dir 0 0755\n");
    mkpath("$dir",0,0755);
  }
  if ( -f "$basedir/$cfile" ) {
    if ( ! -f "$SAVEDIR/$cfile" ) {
      log_only("copy $basedir/$cfile $SAVEDIR/$cfile\n");
      copy("$basedir/$cfile", "$SAVEDIR/$cfile");
    }
    log_only("copy $basedir/$cfile $SAVEDIR/$cfile.new\n");
    copy("$basedir/$cfile", "$SAVEDIR/$cfile.new");
  } else {
    create_newconfigfile($cfile);
  }

  # mv msg.conf.<timestamp>.*  to the save area
#  foreach $f (glob "$basedir/config/msg.conf.[0-9]*") {
#    log_only("move $f $dir\n");
#    move("$f", "$dir");
#  }

  # mv local.conf.<timestamp>.* to the save area
#  foreach $f (glob "$basedir/config/local.conf.[0-9]*") {
#    log_only("move $f $dir\n");
#    move("$f", "$dir");
#  }

  # mv local.conf to the save area
  # pathological case - should not happen
#  foreach $f (glob "$basedir/config/local.conf") {
#    log_only("move $f $dir\n");
#    move("$f", "$dir");
#  }

  # this function is now done in uninstall-newconfig
  # in $dir ($SAVEDIR/$cfile directory)
  # make the msg.conf.<timestamp> file the msg.conf file so that it
  #   will be the version restored on uninstall-newconfig
  # if msg.conf.<timestamp> exists
  #   rename msg.conf to msg.conf.old
  #   take the latest one
  #   rename it .old
  #   copy it to msg.conf
  # endif

  # now do the real work of merging

  my(%OldTemplate, %NewTemplate, %DevProps);
  %OldTemplate = ReadProp("$BACKOUTDIR/$tfile", 0);
  %NewTemplate = ReadProp("$basedir/$tfile", 0);
  %LiveConfig = ReadProp("$basedir/$cfile", 0);

  %DevProps = ReadProp("$basedir/lib/config-templates/Devsetup.properties", 1);
  $DevProps{"msg.RootPath"} = $LiveConfig{"local.installeddir"};
  $DevProps{"msg.RootPathUNIX"} = $LiveConfig{"local.installeddir"};
  $DevProps{"local.serveruid"} = $LiveConfig{"local.serveruid"};
  $DevProps{"local.servergid"} = $LiveConfig{"local.servergid"};
  $DevProps{"msg.ServerHostName"} = $LiveConfig{"local.hostname"};
  # new variables added storeEnable
  if ( ! exists($DevProps{"storeEnable"} ) ) {
    if ( $DevProps{"msmaEnable"} eq "1" ||
         $DevProps{"webmailEnable"} eq "1" ) {
      $DevProps{"storeEnable"} = "1";
    } else {
      $DevProps{"storeEnable"} = "0";
    }
  }

  # read more variables set in devtypes.txt. For now, just do it for known
  # required ones
  %DevTypes = ReadProp("$basedir/lib/config-templates/devtypes.txt", 1);
  if ( ! exists($DevProps{"msg.product.Name"} ) ) {
    if ( exists($DevTypes{"msg.product.Name"} ) ) {
      $DevProps{"msg.product.Name"} = $DevTypes{"msg.product.Name"};
    }
  }

  %DeletedVars = ReadDeletedConfig("$basedir/lib/config.listdeleted");

  open(MERGED, ">$SAVEDIR/$cfile.new") or die "Cannot open $SAVEDIR/$cfile.new";

  foreach $key (sort sortfn keys(%NewTemplate)) {
    log_only("DEBUG1 working on $key\n");

    $old = $OldTemplate{$key};
    $new = $NewTemplate{$key};
    $newsub = doSubs($new, %DevProps);
    $live = $LiveConfig{$key};

    # for debugging
    #  $o = defined($old);
    #  $n = defined($new);
    #  $l = defined($live);
    #  $o1 = exists($OldTemplate{$key});
    #  $n1 = exists($NewTemplate{$key});
    #  $l1 = exists($LiveConfig{$key});
    #  print ("QQQ $key => ;$old;$new;$live; defined => ;$o;$n;$l; exists => ;$o1;$n1;$l1;\n");

    if ( ! exists($OldTemplate{$key}) ) {
      if ( exists($LiveConfig{$key}) ) {
	log_only("QQQ1 new exists, old does not exist, take live for $key\n");
	print MERGED "$key = $live\n";
	goto ENDLOOP;
      } else {
	log_only("QQQ2 new exists, old and live does not exist, take new for $key\n");
	print MERGED "$key = $newsub\n";
	goto ENDLOOP;
      }
    }

    # old exists at this point...

    if ( ! exists($LiveConfig{$key}) ) {
      # pathological case, how can this occur??
      # it does occur in one case, when the entry is NULL
      log_only("QQQ3 pathological case, old exists, new exists, live does not exist, take new for $key\n");
      print MERGED "$key = $newsub\n";
      goto ENDLOOP;
    }

    # live exists at this point

    $stat = compareVals($old,$new);
    if ( $stat == 0 ) {
      log_only("QQQ4 old same as new, take live for $key\n");
      print MERGED "$key = $live\n";
      goto ENDLOOP;
    }

    $stat = compareVals($newsub,$live);
    if ( $stat == 0 ) {
      log_only("QQQ5 old != new,  newsub same as live, take live for $key\n");
      print MERGED "$key = $live\n";
      goto ENDLOOP;
    }

    # have to do merging
    $oldsub = doSubs($old, %DevProps);
    $stat = compareVals($oldsub,$live);
    if ( $stat == 0 ) {
      log_only("QQQ6 oldsub same as live, take newsub for $key\n");
      print MERGED "$key = $newsub\n";
      goto ENDLOOP;
    }

    # $old ne $new, $newsub ne $live, $oldsub ne $live, it was user customized
    # conflict
    log_only("QQQ7 conflict for $key\n");
    $conflict = 1;
    print MERGED "<<<<<<< user customized value\n";
    print MERGED "$key = $live\n";
    print MERGED "=======\n";
    print MERGED "$key = $newsub\n";
    print MERGED ">>>>>>> new factory value\n";
    goto ENDLOOP;

  ENDLOOP:
    delete($LiveConfig{$key});
    delete($OldTemplate{$key});
  }

  # iterate over what is leftover in OldTemplate
  foreach $key (sort sortfn keys(%OldTemplate)) {
    log_only("DEBUG2 working on $key\n");

    $old = $OldTemplate{$key};
    $live = $LiveConfig{$key};
    if ( exists($LiveConfig{$key}) ) {
      log_only("QQQ8 in old, not in new, in live, delete $key\n");
      # was not in NewTemplate, so assume it is a deleted variable
      delete($LiveConfig{$key});
    } else {
      # it was already deleted somehow
    }
  }

  # iterate over what is leftover in LiveConfig
  # must be undocumented or deleted variables
  foreach $key (sort sortfn keys(%LiveConfig)) {
    log_only("DEBUG3 working on $key\n");
    if ( exists($DeletedVars{$key}) ) {
      log_only("QQQ9 not in old, not in new, in live, in deleted list, delete $key\n");
      next;
    } else {
      $live = $LiveConfig{$key};
      log_only("QQQ10 undocumented $key\n");
      print MERGED "$key = $live\n";
    }
  }

  close(MERGED);

  # always say there is a new version (instead of actually testing...)
  # TODO: could determine if any actual change was made
  $NEWCONFIGS = $NEWCONFIGS + 1;
  print NEWCONFIG "$cfile\n";
  if ( $conflict == 1 ) {
    log_msg("-- New with Conflicts: $basedir/$cfile\n");
    $CONFLICTS = $CONFLICTS + 1;
  } else {
    log_msg("-- New: $basedir/$cfile\n");
  }
}

########################################
# patch hosted domain config files
# - like patch_config except for hosted domain files only
#
# uses global variable $PATCHDIR, @domains
# creates NEWCONFIG file descriptor - writes $PATCHDIR/newconfig.list
# creates CONFIG    file descriptor - reads $PATCHDIR/config.list
# sets $tfile - template file
# sets $cfile - config file
########################################
sub patch_hosted_config {
  my($lineno,$domain);
  my($tfile,$cfile,$tfile_old,$tfile_new);
  log_only("-- patch_hosted_config being run\n");
  foreach $domain (@domains) {
    log_msg("-- Checking hosted domain $domain\n");
    #print "QQQ d=$domain $PATCHDIR/${domain}_newconfig.list\n";
    open(NEWCONFIG, ">$PATCHDIR/${domain}_newconfig.list") or die("Cannot open $PATCHDIR/${domain}_newconfig.list for writing -");
    open(CONFIG, "$PATCHDIR/config.list") or die ("Cannot open $PATCHDIR/config.list for reading -");
    $lineno = 0;
    while (<CONFIG>) {
      $lineno = $lineno + 1;
      chomp;
      # $tfile - template file, a path relative to $basedir
      # $cfile - config file, a path relative to $basedir
      ($tfile, $cfile) = split(/ /,$_,2);

      # consider only config files that begin with config/html/
      if ($cfile !~ /^config\/html\//) {
        next;
      }

      # tear up $cfile to insert $domain between config/html and the
      # rest of it
      $cfile =~ s/^config\/html/config\/html\/$domain/;

      # if $cfile does not exist, skip it completely
      if ( ! -f "$basedir/$cfile" ) {
        next;
      }

      # old and new template files
      ($tfile_old, $tfile_new) = get_templates($tfile);
      if ($tfile =~ /.*\.(gif|jpg|ico|png|jar)$/) {
        patch_binfile($tfile, $cfile, $tfile_old, $tfile_new);
      } else {
        patch_configfile($tfile,$cfile, $tfile_old, $tfile_new);
      }
    }
    close (NEWCONFIG);
    close (CONFIG);
  }
}

########################################
# check_file
#   ARG: the location of the config file, a path relative to basedir
#   RETURN: 0 to skip patching this config file
# This is where I hack things.
# - if MTA is not configured, skip the MTA config files from consideration
#   this is a hack since the list of MTA config files are hard coded here.
########################################
# list of MTA files that is not configured if MTA component is not selected
# i.e. those whose package is imta in config.ins.template
@MTA_FILES = (
              "config/dispatcher.cnf",
              "config/imta.cnf",
              "config/mappings",
              "config/return_header.opt",
              "config/aliases",
              "config/mappings.locale",
              "config/maximum.dat",
              "config/maximum_command.dat",
              "config/maximum_charset.dat",
              "config/charnames.txt",
              "config/advanced/option_charset.dat",
              "config/charsubs.txt",
              "config/charsets.txt",
              "config/charsets_ps.txt",
              "config/advanced/name_content.dat",
              "config/countries.txt",
              "config/languages.txt",
              "config/magic.dat",
              "config/ksx1001.txt",
              "config/jis0208.txt",
              "config/jis0212.txt",
              "config/gb2312.txt",
              "config/big5.txt",
              "config/gb18030.txt",
              "config/cns11643.txt",
              "config/internet.rules"
             );

sub check_file {
  my($cfile) = @_;
  my($mtaEnable,$exists);
  $mtaEnable = `$basedir/sbin/configutil -o local.imta.enable`;
  chomp $mtaEnable;
  if ($mtaEnable == 0) {
    $exists = grep /$cfile/, @MTA_FILES;
    if ( $exists != 0) {
      log_only("-- $cfile - MTA not enabled, skipping it\n");
      return(0);
    }
  }
  return(1);
}

########################################
# main program
########################################
#
# PATH=/bin:/usr/bin:/usr/sbin:/sbin:/usr/etc:${PATH}
$ENV{"PATH"} = "/bin:/usr/bin:/usr/sbin:/sbin:/usr/etc:" . $ENV{"PATH"};
# NEWCONFIG - number of new files
# CONFLICTS - number of new files that have conflicts
# NEWBINS   - number of new files that are binary files
$NEWCONFIGS =0;
$NEWBINS = 0;
$CONFLICTS=0;
$UNAME = `/bin/uname`;
get_my_dir();
# assume that this script is located in $basedir/sbin/, 
# so set basedir accordingly
$basedir = dirname ("$scriptDir");

# prepend to search path
unshift (@INC, "$basedir/lib");
require "util.pl";

# get args
# whether to install the new config files or not
if (! GetOptions("help" => \$help, "domain=s" => \$opt_d) ) {
  print "ERROR parsing options: \n";
  usage;
}

if ( $help ) {
  print "Here is your Help: \n";
  usage;
}

if ( @ARGV != 1 ) {
  print "ERROR: Missing patch directory argument\n";
  usage;
}
$PATCHDIR = "$ARGV[0]";

#
# must be root
#
if ($< != 0) {
  print "\nUtility to generate new config files for the Messaging server\n";
  print "To use this utility you need to be the system's root user. \n\n";
  exit(1);
}
if ( ! -d "$PATCHDIR/save" ) {
  print "Invalid PATCHDIR, $PATCHDIR/save is not a directory\n";
  usage;
}
$SAVEDIR = "$PATCHDIR/save";
if ( ! -d "$PATCHDIR/backout" ) {
  print "Invalid PATCHDIR, $PATCHDIR/backout is not a directory\n";
  usage;
}
$BACKOUTDIR = "$PATCHDIR/backout";

# process -d switch
# @domains will be the list of hosted domains to operate on
@domains = get_hosted_domains();

if ( ! defined $opt_d ) {
  # default is to operate on all hosted domains, already set to do so
} elsif ( $opt_d eq "ALL") {
  # do nothing, already set to all domains
} else {
  # set to a specific domain
  # make sure it is a valid domain
  $exists = grep /^$opt_d$/, @domains;
  if ( $exists != 0 ) {
    @domains = ($opt_d);
  } else {
    print "ERROR: Invalid domain $opt_d\n";
    print "The list of valid domains are: @domains\n";
    die "  ";
  }
}

# initialize logging
$LOGFILE = getLogfile($PATCHDIR, '/', "patch-config");
print "-- LOGFILE: $LOGFILE\n";
log_init($LOGFILE);
log_only("basedir = $basedir\n");

# check to make sure config is accessible
check_configaccess();

# run configutil to trigger 6.3 local.conf -> msg.conf automigration
#   if necessary
$Cmd = "$basedir/sbin/configutil";
logonly_cmd($Cmd);

save_movedfiles();
run_devinstall_dryrun();
if ( ! defined $opt_d ) {
  patch_config();
}
patch_hosted_config();
special_hacks();

log_msg("--\n");
log_msg("-- Number of new config files: $NEWCONFIGS\n");
log_msg("-- Number of config files with conflicts: $CONFLICTS\n");
log_msg("-- Number of config files that are images (gif png ico or jpg): $NEWBINS\n");
log_msg("-- The list of new config files are in:\n");
if ( ! defined $opt_d ) {
  log_msg("    $PATCHDIR/newconfig.list\n");
}
foreach $domain (@domains) {
  log_msg("    $PATCHDIR/${domain}_newconfig.list\n");
}
log_msg("\n");
log_msg("--\n");
log_msg("-- Please review the changes in the new config files before installing them.\n");
log_msg("-- Note that you may have to run clbuild, chbuild and cnbuild commands.\n");
log_msg("-- You also need to apply the ldif files under $basedir/lib/patch.\n");

$Cmd = "$basedir/sbin/configutil -o service.http.enable";
logonly_cmd($Cmd);
$output = `$Cmd`;
chomp($output);
if ( $output eq "1") {
  log_msg("WARNING: Messenger Express has been deprecated\n");
  log_msg("         It will be removed in JES6\n");
}
