#!/etc/opt/openit/perl
#
# $Id: patch.pl,v 1.1.2.18 2009/06/15 11:43:05 bolsen Exp $
#
# (C) Copyright 2002 by Open iT Norge AS, All rights reserved.
#
# This software may be used or copied only with the written permission of
# Open Systems Computing AS or in accordance with the terms and conditions
# stipulated in the agreement under which this software has been supplied.
#
# This file is part of Open iT Resources.
#
#HEAD##########################################################################
#
# @NAME:
#   patch
# @AUTHOR:
#   Borgar Olsen
# @CREATED:
#   Wed Oct 16 3:47:01 MET 2002
# @DESCRIPTION:
#   Install a patch in a Open iT installation
#
#/HEAD#########################################################################

#MAIN##########################################################################
#
# @HEADING Patch installation
#
# This program, which must be inculded in with all patches, will install
# a patch. This basically consists of replacing files and restarting
# the appropriate daemons
#
# The patching is configured in the file 'patch.cfg' which is included in
# the tar file.
#
#/MAIN#########################################################################

use strict;

my $cfg = "patch.cfg";
my $status = 1;
my $reinstall = 0;
my $non_root = 0;
my $debug = 0;

my $patch_id;
my $readme_file;

umask(0022);

my $arg;

my $patch_pl_version = "2016-12-07";
print "patch.pl v$patch_pl_version\n";

foreach $arg (@ARGV) {
    if ($arg eq "-reinstall") {
        $reinstall = 1;
    } elsif( $arg eq "-nonroot" ) {
		$non_root = 1;
    } elsif ( $arg eq "-debug" ) {
		$debug = 1;
	}
	else {
        &std_out( "Usage: ./patch.pl [-reinstall]\n" );
        exit 0;
    }
}

if( $< != 0 ) {
	my $openit_uid = &get_openit_user();
	if( ! defined $openit_uid ) {
		die "Can't identify non-root openit user.\n";
	}
	if( $< == $openit_uid ) {
		print "Setting non_root install.\n";
		$non_root = 1;
	}
}

if ( ! $non_root && $< ne 0 ) {
	die "Error: Patching must be run as 'root' or openit user!\n";
}

my $log_dir = "/var/opt/openit/inst/patches";
if( ! -d $log_dir ) {
    mkdir $log_dir or die "Could not make log dir '$log_dir': $!\n";
}
open( LOG, ">>$log_dir/patch.log" ) or
	die "Could not open log file: $log_dir/patch.log: $!\n";

# Dump patch.pl version to log file
&std_out( "Using patch.pl from date $patch_pl_version.\n" );

# Verify that we can find the patch.cfg file.
if (! -e $cfg) {
    &err_out( "Error: Unable to find patch config file '$cfg'!\n" );
}

# Find version of Open iT installation
my $openit_version = &get_installed_version();

# Find architecture of installation
my $arch = &get_architecture();

# Check if we have installed an 32 bit Linux installation on an AMD64 and
# should try to install x86_64 binaries if any
my $force_x86_64 = &check_force_of_x86_64();
if ( $force_x86_64 ) {
    &verify_or_create_path64();
    &verify_or_patch_openitenv();
    &verify_or_patch_wrapper("/opt/openit/bin/wrapper");
    &verify_or_patch_wrapper("/opt/openit/bin/disabler");
    &move_binaries_to_x86_64();
}

# Verify that there is a link from aix4.2 dir to aix5.1 dir
&verify_aix_link();

# Find patch ID and what Open iT verson valid for
my ($patch, $revision, $requires, @versions) = &get_patch_info();

my $old = ".pre_$patch-$revision";
my %backup_files = ();

# Find list of previously installed patches
my %installed_patches = &get_installed_patches();

# Verify that patch isn't already installed
if ($installed_patches{$patch}) {
	
    # Automatically trigger reinstall if installing a newer revision
    if ( $installed_patches{$patch} < $revision ) {
		$reinstall = 1;
    }

    if ($reinstall) {
        &std_out( "Warning: '$patch' is already installed, replacing it.\n" );
    } else {
        &err_out( "Error: '$patch' is already installed!\n" );
    }
} 
else
{
	# Verify that patch can be used for current installation
	if(! match_version($openit_version, @versions)) {
		&err_out( "Error: Patch $patch is not valid for Open iT $openit_version!\n" );
	}
}



# Verify that required patches are installed
# &std_out( "Pre-required patches: $requires\n" );
my @requires = split(/,/, $requires);
foreach my $req (@requires) {
    $req =~ s/\s//g;
    if (! $installed_patches{$req}) {
		&err_out( "Error: Pre-required patch '$req' has not been installed!\n" );
    }
}

# Read file and loop through all lines
open (CFG, "patch.cfg");
binmode(CFG);
my $skip = 0;
while ($status && (my $line = <CFG>)) {
    next if ( $line =~ /^\s*\#.*|^\s*$/ );  # Skip comments and empty lines
    chomp($line);

    # Substitute environment variables
    while ( $line =~ m/%%([^%]+)%%/ ) {
        $line =~ s/%%([^%]+)%%/$ENV{$1}/;
    }

    # Lookup and replace openit.cfg values
    while ( $line =~ m/!!([^!]+)!!/ ) {
        my $value = get_openit_param($1);
        $line =~ s/!!([^!]+)!!/$value/;
    }

    my @elems = split(":", $line);

    if( $skip ) {
		if( $elems[ 0 ] eq "end_verify" ) {
			&std_out( "end_verify\n" );
			$skip = 0;
		} else {
			&std_out( "Skipping: $elems[ 0 ]\n" );
		}
		next;
    }

    # Handle directive specified in $elems[0]
    my $x86_64 = $elems[ 3 ] =~ m/x86_64-unknown-linux/;
    if( ! ( $x86_64 && $force_x86_64 ) ) {
		$status = &handle_operations( @elems );
		if( $status == 2 ) { # skip block
			&std_out( "Skipping block...\n" );
			$skip = 1;
			$status = 1;
			next;
		} elsif( $status == 3 ) { # enter block
			$skip = 0;
			$status = 1;
			next;
		}
    }

    # Extra handling of x86_64 binaries on i386/i686 installations
    if ( $status && $force_x86_64 && $elems[0] =~ /^(add|replace|forceadd)$/ &&
		 $elems[1] =~ /b/ ) {
		$elems[0] = "forceadd";
		$elems[3] .= ".amd64";
		$status = &handle_operations( @elems ) ;
    }
}
close(CFG);

# Updated patch info for installation
&update_patch_info($patch, $revision);
&update_version( $patch );

# Output patch status (i.e ok / failed etc).
if (! $status) {
    &err_out( "Error: Patching failed!\n" );
} else {
    &std_out( "Patch installed!\n" );
}

&sep_out();

&std_out( "" );

close( LOG );

exec "/bin/true"; # To avoid seg-fault when replacing perl

sub sep_out {
    my $str =
		"----------------------------------------------------------------------";
    &std_out( $str );
}

sub std_out {
    my $line = shift @_;
    chomp $line;
    $line = "$line\n";
    print LOG $line or warn "Could not log: $!\n";
    print "$line";
}

sub DEBUG {
    my $line = shift @_;
    chomp $line;
    $line = "$line\n";
    print LOG $line or warn "Could not log: $!\n";
	if ( $debug )
	{
		print "DEBUG: $line";
	}
    
}

sub notice_out {
    my $line = shift @_;
    chomp $line;
    $line = "$line\n";
    print LOG $line or warn "Could not log: $!\n";
    return warn $line;
}

sub err_out {
    my $line = shift @_;
    chomp $line;
    $line = "$line\n";
    print LOG $line or warn "Could not log: $!\n";
    die "$line";
}

sub get_openit_param {
    my ($key) = @_;

    my $val = `. /tmp/openit-cfg-shenv; echo \$$key`;
    chomp($val);

    return $val;
}


sub get_installed_version {
	
	my $file =  &get_openit_param( "CONFIG_DIR" ) . "/version";
   
    open(IN, $file) || &err_out( "Open iT not installed on client!\n" );
    binmode(IN);
    my $line = <IN>; # Only need first line
    close(IN);

    my $version = $line;

    # Add missing .0's
    my $dots = $version =~ tr/././;
    for (; $dots < 3 ; $dots++) {
        $version .= ".0";
    }
	chomp ( $version );
    &sep_out();
    &std_out( "Patching Open iT $version\n" );

    return $version;
}

sub get_patch_info {
	
	
	$readme_file = "../../readme.txt" if ( -e "../../readme.txt" );
	$readme_file = "./readme.txt" if ( -e "/readme.txt" );
	
    open(INFO, $readme_file ) || &err_out( "Unable to read readme.txt file!\n" );
    binmode(INFO);
    my @lines = <INFO>;
    close(INFO);

    # Get ID
    my ($id) = grep( s/\+ID:\s*(\S*)\s*/$1/, @lines);
	DEBUG ( "Got Patch id $id" );
	
    # Get REVISION
    my ($revision) = grep( s/\+REVISION:\s*(\d*)\s*/$1/, @lines);
    if (! defined($revision)) {
		$revision = 1;
    }
	DEBUG ( "Got Patch REVISION: $revision" );

    # Get versions
    my ($versions) = grep( s/\+VALID:\s*(.*)\s*/$1/, @lines);
    my @versions = split(",", $versions);
	
	DEBUG ( "Got Patch VALID FOR: $versions" );
	
    # Get architectures
    my ($architectures) = grep( s/\+REQUIRED ARCH:\s*(.*)\s*/$1/, @lines);
    my @architectures = split(",", $architectures);
    if (@architectures) {
        my $myarch = `uname -m`;
        chomp($myarch);
        my $a;
        my $match = 0;
        foreach $a (@architectures) {
            if ($a =~ m/$myarch/i) {
                $match = 1 
            }
        }
        if ($match == 0) {
            &std_out( "This patch is only valid for " . join(" ", @architectures) . "\n" );
            &std_out( "Your architecture is $myarch, aborting.\n" );
            exit(1);
        }
    }

    # Get requires
    my ($requires) = grep( s/\+REQUIRES:\s*(.*\S)\s*/$1/, @lines);
	DEBUG ( "Got required patches: $requires" );
	
    my @requires = ();
    # REQUIRES may contain a installation version which will
    # superseed the patch requirement. Test for this
    $requires =~ s/\s//g;
    foreach my $element( split( /,/, $requires ) ) {
		if ($element =~ /or/) {
			my ($patch, $req_version) = (split(/\s+or\s+/, $element));
			if ( compare_version( $req_version ) > 0 ) {
				push @requires, $patch;
			}
		} elsif ($element =~ /:/) {
			my ($range, $patch) = (split(/\s*:\s*/, $element));
			if ( &required_for_version($patch, $range) ) {
				push @requires, $patch;
			}
		} else {
			push @requires, $element;
		}
    }
    $requires = join ",", @requires;

    &std_out( "Attempting to install patch $id." );
    &sep_out();
    &std_out( "Time: " . time );
    &std_out( `date 2>&1` );

    return ($id, $revision, $requires, @versions);
}


sub get_architecture {
    my $file = "/var/opt/openit/inst/product/base.prod";

    open(IN, $file) || &err_out( "Open iT not installed on client! \n" );
    binmode(IN);
    my $line = <IN>; # Only need first line
    close(IN);

    return (split(" ", $line))[0];
}

sub check_force_of_x86_64 {
    return 0 if ($arch =~ /x86_64/); # We have AMD64 installation

    return 0 if ($arch =~ /^i[3456]86-.*-linux/ &&
				 ! required_for_version( undef, "4.9.0.7 - 5.5.1") );

    return 1 if ( `uname -m` =~ /x86_64/ ); #

    return 0;
}

# Create x86_64 paths
sub verify_or_create_path64 {
    my $path = "/opt/openit/libexec/x86_64-unknown-linux";
    my @paths = ( "$path", "$path/lib", "$path/bin" );
    foreach my $dir( @paths ) {
		if( ! -d $dir ) {
			make_dir( $dir );
		}
    }
}

# Add OPENIT_LD_LIBRARY_PATH_?? to openit.env
sub verify_or_patch_openitenv {
    my ($olduid, $oldgid);
    my $openitenv = "/etc/opt/openit/openit.env";

    # Read env
    open(IN, $openitenv) || &err_out( "Unable to read Open iT config openit.env" );
    binmode(IN);
    my @openitenv = <IN>;
    close(IN);

    # Already updated?
    return if ( grep( /OPENIT_LD_LIBRARY_PATH_64/, @openitenv ) );

    &std_out( "Preparing openit.env for installation for 64 bit binaries\n" );

    # Locate place to insert stuff
    my $ind = 0;
    while ($openitenv[$ind] !~ /^LD_LIBRARY_PATH=/) {$ind++;}
    $openitenv[$ind] =~ s#:(/opt/openit/libexec/[^/]+/lib)##;
    my $platform = $1;
    splice(@openitenv, $ind, 0,
		   "OPENIT_LD_LIBRARY_PATH_32=$platform\n" );
    my $arch64 = "x86_64-unknown-linux";
    splice(@openitenv, $ind, 0,
		   "OPENIT_LD_LIBRARY_PATH_64=/opt/openit/libexec/$arch64/lib\n" );
    while ($openitenv[$ind] !~ /export.*LD_LIBRARY_PATH/) { $ind++ };
    splice(@openitenv, $ind + 1, 0,
		   "export OPENIT_LD_LIBRARY_PATH_32 OPENIT_LD_LIBRARY_PATH_64\n" );
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat("$openitenv");
    if (!defined($gid)) {
        &err_out( "Failed to stat $openitenv: $!" );
    }

    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the file
    $> = $uid; # Change to the owner of the file

    rename $openitenv, "$openitenv$old" || &notice_out( "Failed to rename $openitenv to $openitenv$old: $!\n" );

    if (open(OUT, ">$openitenv.$$")) {
		binmode(OUT);
		print OUT @openitenv;
		close(OUT);

		chmod(0755, "$openitenv.$$");

		rename ("$openitenv.$$", $openitenv) || &notice_out( "Failed to rename $openitenv.$$ to $openitenv: $!\n" );
    } else {
		&notice_out("Failed to write $openitenv.$$: $!\n");
		rename("$openitenv$old", $openitenv);
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root
}

# Add 64 binary path to wrapper
sub verify_or_patch_wrapper {
    my( $wrapper ) = @_;
    my ($olduid, $oldgid);

    return if( ! -e $wrapper );

    # Read env
    open(IN, $wrapper) || &err_out( "Unable to read Open iT binary wrapper" );
    binmode(IN);
    my @wrapper = <IN>;
    close(IN);

    my $path32 = "OPENIT_LD_LIBRARY_PATH_32";
    my $path64 = "OPENIT_LD_LIBRARY_PATH_64";

    # Already updated?
    return if ( grep( /PROG64/, @wrapper ) &&
				grep( /$path32/, @wrapper ) &&
				grep( /$path64/, @wrapper) );

    &std_out( "Preparing wrapper for installation for 64 bit binaries\n" );

    # Locate place to insert stuff
    my $ind = 0;
    if( ! grep( /PROG64/, @wrapper ) ) {
		while ($wrapper[$ind++] !~ /PROG=/) {}
		splice(@wrapper, $ind++, 0, "PROG64=\$\{PROG\}.amd64\n");
		while ($wrapper[$ind] !~ /OPENIT_HOME\//) { $ind++ };
		my $exec = "";
		if( grep m#^\s+exec\s+\$OPENIT_HOME/libexec#, @wrapper ) {
			$exec = "exec ";
		}
		splice(@wrapper, $ind, 0,
			   'if [ -z "$IGNORE_64BIT" -a -x $OPENIT_HOME/libexec/$OPENIT_ARCH/bin/$PROG64 ]; then',
			   "\n",
			   '    ' . $exec . '$OPENIT_HOME/libexec/$OPENIT_ARCH/bin/$PROG64 "$@"', "\n" . 'el');
    }

    if( ! grep( /$path32/, @wrapper ) ) {
		$ind = 0;
		while($ind < scalar @wrapper) {
			if($wrapper[$ind] =~
			   m#^(\s*)(exec\s+)?\$OPENIT_HOME/libexec/(\$OPENIT_ARCH/)?bin/\$PROG # ){
				splice(@wrapper,$ind,0,
					   "${1}export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:\$$path32\n");
				$ind++;
			}
			$ind++;
		}
    }

    if( ! grep( /$path64/, @wrapper ) ) {
		$ind = 0;
		my $arch64 = "x86_64-unknown-linux";
		while($ind < scalar @wrapper) {
			if($wrapper[$ind] =~ m/^(\s*)(exec\s+)?.*PROG64 / ){
				splice(@wrapper,$ind,0,
					   "${1}export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:\$$path64\n" );
				$ind++;
			}
			$wrapper[$ind] =~ s#\$OPENIT_ARCH/bin/\$PROG64#$arch64/bin/\$PROG#;
			$ind++;
		}
    }

    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat("$wrapper");
    if (!defined($gid)) {
        &err_out( "Failed to stat $wrapper: $!" );
    }

    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the file
    $> = $uid; # Change to the owner of the file

    rename $wrapper, "$wrapper$old" || &notice_out( "Failed to rename $wrapper to $wrapper$old: $!\n" );

    if (open(OUT, ">$wrapper.$$")) {
		binmode(OUT);
		print OUT @wrapper;
		close(OUT);

		chmod(0755, "$wrapper.$$");

		rename ("$wrapper.$$", $wrapper) || &notice_out( "Failed to rename $wrapper.$$ to $wrapper: $!\n" );
    } else {
		&notice_out("Failed to write $wrapper.$$: $!\n");
		rename("$wrapper$old", $wrapper);
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root
}

sub move_binaries_to_x86_64 {
    my $path32 = "/opt/openit/libexec/$arch";
    my $path64 = "/opt/openit/libexec/x86_64-unknown-linux";
    my $files = `ls $path32/bin/*.amd64`;
    chomp $files;
    my @files = split "\n", $files;
    foreach my $file( @files ) {
		my $dest = $file;
		$dest =~ s/\.amd64$//;
		$dest =~ s/i.86-[^-]+-linux/x86_64-unknown-linux/;
		copy_file( "$file", "$dest", 0, 1 );
		my @to_delete = glob( "$file*" );
		foreach my $to_delete( @to_delete ) {
			unlink_file( $to_delete );
		}
    }
}

#
# Check if the patch is requiered for the installed version
#
sub required_for_version {
    my ($patch, $range) = @_;

    if( $range =~ m/^\s*(server|client)\s*(\S.*)$/i ) {
		$range = $2;
		my $type = lc( $1 );
		if( ! -e "/opt/openit/bin/openit-$type" ) { # affected type ?
			return 0;      # if not, ignore...
		}
    }

    my ($lower, $upper);
    if ($range =~ /-/) { 		# Proper range
    	($lower, $upper) = split("\s*-\s*", $range);
    } else {
		$lower = $upper = $range;	# Just one release
    }

    if ( compare_version( $lower ) <= 0 &&        # lower <= current ?
		 compare_version( $upper ) >= 0 ) {       # upper >= current ?
		return 1;
    }

    return 0;
}

#
# Compare versions
#
#    -1 if version < installed version
#     0 if version = installed version
#     1 if version > installed version
#
sub compare_version {
    my ($version) = @_;

    my @instparts = split('\.', $openit_version);
    my @testparts = split('\.', $version);

    # Make sure version number in 4 parts (e.g 4.9.0.0)
    while (scalar(@testparts) < 4) {
		push @testparts, 0;
    }

    my $level = 0;
    while ($level < 4) {
		my $ip = $instparts[$level];
		my $tp = $testparts[$level];

		if ( $tp < $ip ) { # Version less than installed version
			return -1;
		} elsif ( $tp > $ip ) { # Version higher than installed version
			return 1;
		}
		$level++;
    }

    # Version indetical to installed version
    return 0;
}


sub match_version {
    my ($rel, @vrels) = @_;
	
    foreach my $vrel (@vrels) {
		# Remove spaces
		$vrel =~ s/\s*(.*)\s*$/$1/;

		# Expand vrel to 4 digit version match
		my @vrelparts = split('\.', $vrel);

		# First scan for 'x' and identify glob match
		my $ind = 0;
		my $glob = 0;
		while ( $ind < scalar(@vrelparts) ) {
			if ($vrelparts[$ind] eq 'x') {
				$glob = 1;
			}
			$ind++;
		}
		# Fill out to 4 values
		while (scalar(@vrelparts) < 4) {
			if ($glob) {
				push @vrelparts, 'x';
			} else {
				push @vrelparts, 0;
			}
		}
		$vrel = join(".", @vrelparts);

		# Transform to match
		$vrel =~ s/\./\\./g;  # . -> \.
		$vrel =~ s/x/\\d+/g;  # x -> \d+
		DEBUG( "Comparing version $rel vs $vrel" );
		if ($rel =~ /^$vrel/) {
			return 1;
		}
    }

    return 0;
}

sub get_installed_patches {
    my $dir = "/var/opt/openit/inst/patches/";
    my %installed_patches;

    if (! -d $dir) { # If dir dosn't exist, no patche have been applied
		return ();
    }

    opendir(PATCHES, $dir) || "Unable to open patch info dir '$dir'\n";
    my @files = readdir PATCHES;

    closedir(PATCHES);
    @files = grep /^\d+\.\d+\.\d+(\.\d+)?-.*$/, @files;
	
    foreach $patch (@files) {
		my ($id, $rev) = split("-", $patch);

		if (! defined($rev)) {
			$rev = 1;
		}

		# Only keep highest revision
		my $oldrev = $installed_patches{$id};
		if (defined($oldrev) && $oldrev > $rev) {
			$rev = $oldrev
		}

		$installed_patches{$id} = $rev,
    }

    return %installed_patches;
}


sub handle_operations {
    my ($op, @params) = @_;

    if ($op eq "replace") {
		&add_or_replace_file(0, @params);
    } elsif($op eq "restart") {
		&restart(@params);
    } elsif($op eq "stop") {
		&stop(@params);
    } elsif($op eq "start") {
		&start(@params);
    } elsif($op eq "run") {
		&run_program(@params);
    } elsif($op eq "runas") {
		&runas_program(@params);
    } elsif($op eq "unpack") {
		&unpack_tar(@params);
    } elsif($op eq "add") {
		&add_or_replace_file(1, @params);
    } elsif($op eq "forceadd") {
		&add_or_replace_file(undef, @params);
    } elsif($op eq "mkdir") {
		&make_dir(@params);
    } elsif($op eq "link") {
		&make_symlink(@params);
    } elsif($op eq "symlink") {
		&make_symlink(@params);
    } elsif($op eq "mergeacc") {
		&merge_acc_types(@params);
    } elsif($op eq "add_conf") {
		&add_conf_object(@params);
    } elsif($op eq "del_conf") {
		&del_conf_object(@params);
    } elsif($op eq "subst_conf_string") {
		&subst_conf_string(@params);
    } elsif($op eq "verify") {
		if( ! &verify(@params) ) {
			return 2; # skip block
		} else {
			return 3; # go into block
		}
    } elsif($op eq "add_new_lines") {
		&add_new_lines(@params);
    } elsif($op eq "add_new_gen_lines") {
		&add_new_gen_lines(@params);
    } elsif($op eq "add_module_ids") {
		&add_module_ids(@params);
    } elsif($op eq "add_classvar_config") {
		&add_classvar_config(@params);
    } elsif($op eq "add_dist") {
		&add_dist(@params);
    } elsif($op eq "add_cfg") {
		&fix_openit_cfg(@params);
    } elsif($op eq "del_cfg") {
		&fix_openit_cfg(@params);
    } elsif($op eq "substitute_lines") {
		&substitute_lines(@params);
    } elsif($op eq "add_conf_to_xml") {
		&add_conf_to_xml(@params);
    } elsif($op eq "change_xml_value") {
		&change_xml_value(@params);
    } elsif($op eq "end_verify") {
		return 1; # ignore
    } elsif($op eq "remove") {
		&remove_file(@params);
    } elsif($op eq "info") {
		&print_info(@params);
    } elsif($op eq "update_version") {
		&update_version($patch);
    } else {
		&std_out( "Error: Unknown operation '$op' found!\n" );
		return 0;
    }

    return 1;
}


sub add_or_replace_file {
    my ($is_add, $type, $source, $dest) = @_;
    my $action;

    if (! defined $is_add) {
		$action = "Force-Adding";
    } elsif ($is_add) {
		$action = "Adding";
    } else {
		$action = "Replacing";
    }

    if ($dest =~ /64$/) {
		$action .= " (64bit)";
    }


    # Modify source and destination (for binaries)
    if ($type eq "b") {
		if ( ($dest =~ /64$/) && $force_x86_64 ) {  # Adding AMD64 binaries
			$source = "files/x86_64-unknown-linux/$source";
			$dest =~ s/\.amd64$//;
			$dest =~ s/\$ARCH/x86_64-unknown-linux/;
		} else {
			$source = "files/$arch/$source";
			$dest =~ s/\$ARCH/$arch/;  # Replace
		}
    } else {
		$source =~ s/\$ARCH/$arch/;  # Replace
		$source = "files/$source";
		$dest =~ s/\$ARCH/$arch/;  # Replace
    }

    if (&identical($source, $dest)) {
        $source =~ s/^files\///;
        &std_out( "$source doesn't need updating, skipping.\n" );
        return;
    }
    &std_out( "$action '$source'!\n" );

    # Verify that source file exists
    if (! -e $source) {
		&err_out( "Error:: File '$source' is missing from patch!\n" );
    }

    if ($is_add || ! defined $is_add) {
		# Verify that destination directory exists when adding file
		my $dir = $dest;
		if ( $dest !~ m#^(/|/.*/)[^/]+$# ) {
			&std_out( "Warning: Filepath '$dest' is invalid, skipping update!\n" );
			return;
		} else {
			$dir = $1;
		}
		if (! -d $dir ) {
			&std_out( "Warning: Didn't find expected directory '$dir'," .
					  " skipping update!\n" );
			return;
		}
    } else {
		# Verify that destination file exists when replacing file
		if ($dest !~ /64$/ && ! -e $dest) {
			&std_out( "Warning: File '$dest' is missing, skipping update!\n" );
			return;
		}
    }

    &copy_file($source, $dest, defined $is_add && !$is_add,
			   $reinstall || ! defined $is_add);
#    # Checking owner of file
#    my $SU_START = "";
#    my $SU_END = "";
#    my $owner_uid = (stat($dest))[4];
#    if ($owner_uid ) { # Not owned by root
#	my $owner = (getpwuid($owner_uid))[0];
#	$SU_START = "su $owner -c '";
#	$SU_END = "' >/dev/null 2>&1";
#    }
#
#    # Process old file and adding new
#    if( -e $dest ) { # Only rename old file if it already exists
#	rename $dest, "${dest}$old";
##	system("${SU_START}/bin/mv $dest $dest$old${SU_END}");
#    }
#    print("${SU_START}/bin/cp $source $dest${SU_END}\n");
#    system("${SU_START}/bin/cp $source $dest${SU_END}");
}

sub remove_file {
    my @params = @_;
    my $file = $params[ 0 ];
    &std_out( "Removing $file\n" );
    if( ! -e $file ) {
		&std_out( "Remove: $file does not exist. Skipping.\n" );
		return;
    }
    unlink $file or &std_out( "Warning: Could not remove $file: $!\n" );
    return 1;
}

sub print_info {
    my @params = @_;
    &std_out( "\n" );
    print join(" ",@params);
    &std_out( "\n\n" );
    sleep 4;
}


sub make_dir {
    my( $dir, $perm ) = @_;
    &std_out( "Creating directory '$dir'!\n" );

    # Get the components
    my @comps = split '/+', $dir;
    $dir = "/";
    $dir .= shift @comps;
    while( -d $dir && scalar @comps ) {
		$dir .= "/" . shift @comps;
    }
    if( scalar @comps == 0 && -d $dir ) {
		return;
    }
    if( $dir =~ m#/([^/]+)$# ) {
		unshift @comps, $1;
		$dir =~ s#/[^/]+$##;
    }

    while( scalar @comps ) {
		my ($olduid, $oldgid);
		my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($dir);
		$olduid = $>;
		$oldgid = $(;
		$) = $gid; # Change to the group of the parent directory
		$> = $uid; # Change to the owner of the parent directory
		$dir .= "/" . shift @comps;
		mkdir $dir or &notice_out( "Failed to mkdir $dir: $!\n" );
		if( defined $perm ) {
			my $r_perm = $perm;
			$r_perm = oct $r_perm if $r_perm =~ m/^0/;
			if( ! chmod $r_perm, $dir ) {
				&notice_out( "Failed to chmod( $perm, $dir ): $!\n" );
			}
		}
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
    }
}

# New method, using suid and link instead of system/su

sub make_symlink {
    my( $target, $name ) = @_;
    my ($olduid, $oldgid);

    $name =~ s#//#/#g;

    $target =~ s/\$ARCH/$arch/;  # Replace
    $name =~ s/\$ARCH/$arch/;  # Replace

    my $dir = $name;
    $dir =~ s#/([^/]+)$##;
    my $file = $1;

    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($dir);

    if (!defined($gid)) {
        &notice_out( "Failed to stat $dir: $!\n" );
    }

    my $pid = fork();
    if( ! defined $pid ) {
		&err_out( "Could not fork to make link. $!\n" );
    }
    if (! $pid ) { # to get back to current dir
		$) = $gid; # Change to the group of the parent directory
		$> = $uid; # Change to the owner of the parent directory
        if( ! chdir($dir) ) {
			&std_out( "Failed to chdir to $dir: $!" );
			exec "/bin/false"; # To avoid segfault when perl is replaced
		}
		if( -e $file ) {
			&std_out( "Link name '$name' exists. Deleting it!\n" );
		}
		unlink $file;
		&std_out( "Creating link: $name\n" );

        if( ! symlink($target, $file) ) {
			&std_out( "Failed to symlink $file -> $target: $!" );
			exec "/bin/false"; # To avoid segfault when perl is replaced
		}
        exec "/bin/true"; # To avoid segfault when perl is replaced
    };
    waitpid($pid,0);
    if( $? ) {
		&err_out( "$?, $!" );
    }
}


sub daemon_action {
    my ($action, $action_text, $type) = @_;

    if ($type eq "client") {
		&std_out( "$action_text Open iT client\n" );
		system("/opt/openit/bin/openit-client $action");
    } elsif ($type eq "server") {
		&std_out( "$action_text Open iT server\n" );
		system("/opt/openit/bin/openit-server $action");
    } elsif ($type eq "logger") {
		&std_out( "$action_text Open iT logger\n" );
		system("/opt/openit/bin/openit-logger $action");
    } elsif ($type eq "webserver") {
		&std_out( "$action_text Open iT web server\n" );
		system("/opt/openit/bin/openit-httpd $action");
    } elsif ($type eq "mc") {
		&std_out( "$action_text Open iT messagecentral\n" );
		system("/opt/openit/bin/openit-mc $action");
    } elsif ($type eq "lo") {
		&std_out( "$action_text Open iT licenseoptimizer\n" );
		system("/opt/openit/bin/openit-lo $action");
    } elsif ($type eq "dcc") {
		&std_out( "$action_text Open iT dcc scheduler\n" );
		system("/opt/openit/bin/openit-dcc $action");
    } elsif ($type eq "scheduler") {
		&std_out( "$action_text Open iT scheduler\n" );
		system("/opt/openit/bin/openit-scheduler $action");
    } elsif ($type eq "jobscheduler") {
		&std_out( "$action_text Open iT jobscheduler\n" );
		system("/opt/openit/bin/openit-jobscheduler $action");
    } elsif ($type eq "all") {
        &std_out( "$action_text Open iT all Open iT daemons\n" );
        system("/opt/openit/bin/openit-all $action");
		system("/opt/openit/bin/openit-dcc $action");
    } else {
		&std_out( "Error: $action_text of unknown daemon '$type'!\n" );
    }

}

sub restart {
    my ($type) = @_;

    &daemon_action( "stop", "Stopping", $type );
    &daemon_action( "start", "Starting", $type );
}

sub stop {
    my ($type) = @_;

    &daemon_action( "stop", "Stopping", $type );
}

sub start {
    my ($type) = @_;

    &daemon_action( "start", "Starting", $type );
}

sub run_program {
    my ($prog, @params) = @_;

    &std_out( "Running program '$prog'!\n" );

    my $command;

    if ($prog =~ /^\//) { # Full path
		$command = "$prog " . join(" ", @params);
    } else {                   # else
		$command = "./files/$prog " . join(" ", @params);
    }

    if( system("$command") ) {
		&err_out( "Error: program '$prog' failed!" );
    }
}

sub runas_program {
    my ($dir, $prog, @params) = @_;
    my ($olduid, $oldgid);

    &std_out( "Dir: $dir, params: ", join(":", @params), "\n" );

    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($dir);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $dir: $!" );
    }

    $olduid = $>;
    $oldgid = $(;
    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory
    $< = $uid;


    &std_out( "Running program '$prog' as user $uid.\n" );

    my $command;

    if ($prog =~ /^\//) { # Full path
		$command = "$prog " . join(" ", @params);
    } else {                   # else
		$command = "./files/$prog " . join(" ", @params);
    }

    if( system("$command") ) {
		&err_out( "Error: program '$prog' failed!" );
    }

    $> = $olduid; # Change uid back to root
    $< = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root
}

sub unpack_tar {
    my ($archive, $dir) = @_;

    &std_out( "Unpacking archive '$archive'\n" );

    # Sanity check (no remove of /);
    if ($dir =~ q'^/$' || $dir eq "") {
		&err_out( "Illegal to remove '$dir'" );
    }

    # Remove directory if it exists, then create
    if (-d $dir) {
		system("rm -rf $dir$old");
		system("mv $dir $dir$old");
    }
    mkdir $dir;

    my $command = "/opt/openit/bin/tar xf files/$archive -C $dir";
    if( system("$command") ) {
		&err_out( "Unpacking of '$archive' failed!" );
    }
    system("chown -R root $dir");
    system("chmod -R a+rX $dir");
}

sub update_patch_info {
    my ($id, $revision) = @_;
    my $dir = "/var/opt/openit/inst/patches/";

    my $base_id = "$dir/$id";
    my $full_id = "$dir/$id-$revision";

    if (! -d $dir) { # Create patch dir if missing
		mkdir $dir || &err_out( "Error: Unable to create patch dir '$dir'\n" );
    }

    # Copy README file to patch dir
    copy_file("$readme_file", $full_id, 0, 1);

    # Rename old non-revision id file if present to new revision
    # name (i.e where ID files do not contain revisions)
    if( $revision > 1 && -x $base_id && (! -l $base_id) ) {
		rename $base_id, "${base_id}-1";
    }

    # Update symlink
    unlink $base_id if( -x $base_id );
    &make_symlink( "$id-$revision", $base_id );
}

sub update_version {

	my $new_version = shift @_;
	my $version_file = get_openit_param( "CONFIG_DIR" ) . "/version";
	
	if ( ! -e $version_file )
	{
		&err_out( "Error: Unable to find patch version file '$version_file'!\n" );
	}
	else
	{
		#read current version file
		open(IN, $version_file) || &err_out( "Unable to open $version_file!\n" );
		binmode(IN);
		my $old_version = <IN>; # Only need first line
		close(IN);
		chomp ( $old_version );
		if( $old_version eq $new_version ) {
			return;
		}
		&std_out ( "Updating installation version from '$old_version' to '$new_version'" );
		
		# replace with new version
		my $new = "$version_file.new";
		my $old = "$version_file.old";

		# Create new tmp file
		open(OUT, ">$new") || die "Unable to create tmp file '$new'";
		print OUT $new_version;
		close(OUT);

		# Mv old file to .old
		if ( ! rename $version_file, $old ) {
		die "Unable to rename '$version_file'";
		}

		# Rename new 
		if ( ! rename $new, $version_file ) {
		die "Unable to rename '$version_file'";
		}
		

	}
	

}

sub get_openit_user {
	print "Checking openit user.\n";
	my $cfg = "/etc/opt/openit/openit.cfg";
	open( CONFIG, "<$cfg" ) or die "Cant open '$cfg': $!\n";
	my @lines = <CONFIG>;
	close( CONFIG );
	my $uid;
	foreach my $line( @lines ) {
		if( $line =~ m/^OPENIT_USER\s+(\S+)/ ) {
			my $user = $1;
			$uid = getpwnam( $user );
			print "User($uid): $user\n";
		}
	}
	return $uid;
}


# If replace is set to 1 it expects there to be an original from which
# to read original permissions.
# If force is set then ignore errors

sub copy_file {
    my ($from, $to, $replace, $force) = @_;
    my $buf;
    my ($dev, $ino, $mode, $nlink, $uid, $gid);
    my $statfrom;
    my ($olduid, $oldgid);
    
    if (!$force && !($to =~ m/64/) && ((!$replace) && -f "$to")) {
        &err_out( "$to is already present" );
    }

#   Note: We now always take uid/gid from directory and not from
#   previous file.  This is in case its a file owned by openit
#   user in a directory owned by root.
#   if ($replace) {
#       $statfrom = $to;
#   } else {
	my @p = split(/\//, $to);
	pop(@p); # Remove the filename after full path
	$statfrom = join("/", @p);
#   }
    ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);

    if (!defined($gid)) {
        &err_out( "Failed to stat $statfrom: $!" );
    }

    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the file
    $> = $uid; # Change to the owner of the file

    if (!open(FROM, $from)) {
        &err_out( "Can't read $from: $!" );
    }
    binmode(FROM);
    if (!open(TO, ">$to.$$")) {
        &err_out( "Can't write $to: $!" );
    }
    binmode(TO);

    if (!$replace) {
        $mode = 0755; # A new file so we don't copy permissions of directory
    }

    while (read(FROM, $buf, 65536)) {
        print TO $buf;
    }

    close (FROM);
    close(TO);

    if ($replace) { # Store the original
        if (!rename($to, "$to$old")) {
            &notice_out( "Can't rename $to to $to$old: $!" );
        }
    }
    if (!rename("$to.$$", $to)) {
        &err_out( "Can't rename $to.$$ to $to: $!" );
    }

    # Have to set mode
    if(!chmod ($mode, $to)) {
        rename($to, "$to.failed");
        rename("$to$old", $to);
		&err_out( "Can't set mode on $to: $!, aborting patch" );
    }

#   Old code, should be redundant by $> and $) but still here due to gid bug
    if (!chown ($uid, $gid, "$to")) {
        system("chgrp $gid $to");
    }
    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}

sub backup_file {
	my( $file ) = @_;
	if( ! defined $backup_files{ $file } ) {
		copy_file( $file, "$file$old", 0, 1 );
		$backup_files{ $file } = 1;
	}
}

sub unlink_file {
    my( $path ) = @_;

    my ($olduid, $oldgid);

    my @p = split(/\//, $path);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);

    if (!defined($gid)) {
        &err_out( "Failed to stat $statfrom: $!" );
    }

    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the file
    $> = $uid; # Change to the owner of the file

    unlink $path;

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}

# identical: returns 1 if the two files are identical

sub identical {
    my ($from, $to) = @_;
    my ($buf_from, $buf_to, $len_from, $len_to);
    
    if (!open(TO, $to)) {
        return 0;
    }
    binmode(TO);
    if (!open(FROM, $from)) {
        &notice_out( "Can't read $from: $!" );
        return 0;
    }
    binmode(FROM);
    while( ($len_from = read(FROM, $buf_from, 65536))) {
        $len_to = read(TO, $buf_to, 65536);
        if ($len_to != $len_from) {
            close(TO);
            close(FROM);
            return 0;
        }
        if ($buf_from ne $buf_to) {
            close(TO);
            close(FROM);
            return 0;
        }
    }
    close(TO);
    close(FROM);
    return 1;
}


sub count_acc_types {
    my( $acc ) = @_;
    my $number_of_data_types = 0;
    my $level = 0;
    foreach my $line( @$acc ) {
		for( my $i = 0; $i < length $line; $i++ ) {
			my $char = substr( $line, $i, 1 );
			if( $char eq "(" ) {
				$level++;
				if( $level == 2 ) {
					$number_of_data_types++;
				}
			} elsif( $char eq ")" ) {
				$level--;
			}
		}
    }
    return $number_of_data_types;
}

sub merge_acc_types {
    my( $new_acc ) = @_;
    my $acc = "/var/opt/openit/etc/acc_types";
    &std_out( "Updating '$acc'\n" );
    open( ACC, "<$acc" ) or &err_out( "Could not open $acc: $!\n" );
    $! = undef;
    my @acc = <ACC>;
    if( $! ) {
		&err_out( "Could not read '$acc': $!\n" );
    }
    close( ACC );

    $new_acc = "files/$new_acc";
    open( NEWACC, "<$new_acc" ) or &err_out( "Could not open $new_acc: $!\n" );
    $! = undef;
    my @new_acc = <NEWACC>;
    if( $! ) {
		&err_out( "Could not read $new_acc: $!\n" );
    }
    close( NEWACC );

    my $acc_num = count_acc_types( \@acc );
    my $new_acc_num = count_acc_types( \@new_acc );
    if( $new_acc_num <= $acc_num ) {
		&std_out( "'$acc' already updated...\n" );
		return;
    }

    my @acc_out = @acc;
    while( scalar @acc_out ) {
		my $str_ptr = \$acc_out[ $#acc_out ];
		if( length $$str_ptr > 0 ) {
			if( substr( $$str_ptr, -1, 1, "" ) eq ")" ) {
				last;
			}
		} else {
			pop @acc_out;
		}
    }
    while( scalar @acc_out ) {
		my $str_ptr = \$acc_out[ $#acc_out ];
		if( length $$str_ptr > 0 ) {
			my $char = substr( $$str_ptr, -1, 1, "" );
			if( $char eq "," ) {
				$$str_ptr .= ",\n";
				last;
			} elsif( $char eq ")" ) {
				$$str_ptr .= "),\n";
				last;
			}
		} else {
			pop @acc_out;
		}
    }
    my $level = 0;
    my $num = 0;
    my $copy = 0;
    foreach my $line( @new_acc ) {
		if( ! $copy ) {
			for( my $i = 0; $i < length $line; $i++ ) {
				my $char = substr( $line, $i, 1 );
				if( $copy && $char eq "," ) {
					my $right = substr( $line, $i + 1 );
					if( $right !~ m/^\s*$/ ) {
						push @acc_out, $right;
					}
					last;
				}
				if( $char eq "(" ) {
					$level++;
				} elsif( $char eq ")" ) {
					$level--;
					if( $level == 1 ) {
						$num++;
						if( $num == $acc_num ) {
							$copy = 1;
						}
					}
				}
			}
		} else {
			push @acc_out, $line;
		}
    }
    &backup_file( $acc );

    my ($olduid, $oldgid);
    my @p = split(/\//, $acc);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;
    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory


    open( OUT, ">$acc.tmp" ) or &err_out( "Could not open '$acc.tmp'" );
    foreach my $line( @acc_out ) {
		if( ! print OUT $line ) {
			close( OUT );
			unlink "$acc.tmp";
			&err_out( "Could not write to '$acc.tmp'" );
		}
    }
    close( OUT );

    rename( "$acc.tmp", "$acc") || &notice_out( "Failed to rename $acc.tmp to $acc: $!" );

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

    &std_out( "Appended " . ($new_acc_num - $acc_num) . " data types.\n" );
}

sub add_conf_object {
    my( $conf_file, $path_spec, @obj_spec ) = @_;
    my ($olduid, $oldgid);

    &std_out( "Updating '$conf_file'.\n" );

    if( ! defined $conf_file ) {
		&err_out( "Missing config file name" );
    }

    &backup_file( $conf_file );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory


    my $obj_spec = join ":", @obj_spec;
    if( ! defined $obj_spec || $obj_spec =~ m/^\s*$/ ) {
		&err_out( "ERROR: Missing object specification.\n" );
    }

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );
    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		close( CONF_IN );
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    my $root = [];
    my $pos = [ $root ];
    my $line;
    my $state = "XNAME";
    while( $line = <CONF_IN> ) {
		&parse_string( $line, $root, $pos, \$state );
    }
    close( CONF_IN );
    if( $state ne "XNAME" || scalar @$pos > 1 ) {
		my $stack = &get_stack( $pos );
		&err_out( "ERROR: Error in config file at \"$stack\". State: $state\n" );
    }


    my $path = [];
    my $to_merge = [];
    $pos = [ $to_merge ];

    @$path = split( '\.', $path_spec );
    $state = "XNAME";
    &parse_string( $obj_spec, $to_merge, $pos, \$state );
    if( $state ne "XNAME" || scalar @$pos > 1 ) {
		my $stack = &get_stack( $pos );
		&err_out( "ERROR: Error in specification at \"$stack\". State: $state\n" );
    }

    my $seek_obj = $root;
    foreach my $key( @$path ) {
		my $found = 0;
		foreach my $obj( @$seek_obj ) {
			if( $$obj[ 0 ] eq $key ) {
				$found = 1;
				$seek_obj = $$obj[ 3 ];
				last;
			}
		}
		if( ! $found ) {
			push @$seek_obj, [ $key, "", "", [] ];
			$seek_obj = $$seek_obj[ $#$seek_obj ][ 3 ];
		}
    }

    foreach my $new_obj( @$to_merge ) {
		foreach my $obj( @$seek_obj ) {
			if( $$obj[ 0 ] eq $$new_obj[ 0 ] ) {
				&std_out( "Warning: $path_spec.$$obj[ 0 ] exists. Skipping update.\n" );
				close( CONF_OUT );
				unlink "$conf_file.tmp";
                $> = $olduid; # Change uid back to root
                $) = $oldgid; # Change gid back to root
				return;
			}
		}
		push @$seek_obj, $new_obj;
    }

    write_output( $root, \*CONF_OUT, "" );
    close( CONF_OUT );

    rename( "$conf_file.tmp", "$conf_file") || &notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

    &std_out( "'$conf_file' updated.\n" );

}

sub write_output {
    my( $object_list, $handle, $indent ) = @_;
    if( ! defined $indent ) {
		$indent = "";
    }
    foreach my $object( @$object_list ) {
		print $handle sprintf( "%s%s(%s)=\"%s\"",
							   $indent, @$object[ 0, 1, 2 ] );
		if( scalar @{$$object[ 3 ]} ) {
			print $handle "\n${indent}\{\n";
			write_output( $$object[ 3 ], $handle, "\t$indent" );
			print $handle "${indent}\}\n";
		} else {
			print $handle "\{\}\n";
		}
    }
}

sub del_conf_object {
    my( $conf_file, $path_spec ) = @_;
    my ($olduid, $oldgid);

    &std_out( "Updating '$conf_file'. Removing entry.\n" );

    if( ! defined $conf_file ) {
		&err_out( "Missing config file name" );
    }

    &backup_file( $conf_file );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory


    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );
    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		close( CONF_IN );
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    my $root = [];
    my $pos = [ $root ];
    my $line;
    my $state = "XNAME";
    while( $line = <CONF_IN> ) {
		&parse_string( $line, $root, $pos, \$state );
    }
    close( CONF_IN );
    if( $state ne "XNAME" || scalar @$pos > 1 ) {
		my $stack = &get_stack( $pos );
		&err_out( "ERROR: Error in config file at \"$stack\". State: $state\n" );
    }


    my $path = [];
    @$path = split( '\.', $path_spec );

    my $seek_obj = $root;
    my @seek_idx = ();
    foreach my $key( @$path ) {
		my $found = 0;
		my $idx = 0;
		foreach my $obj( @$seek_obj ) {
			if( $$obj[ 0 ] eq $key ) {
				$found = 1;
				$seek_obj = $$obj[ 3 ];
				push @seek_idx, $idx;
				last;
			}
			$idx++;
		}
		if( ! $found ) {
			&std_out( "'$path_spec' does not exist. Nothing to do.\n" );
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			$> = $olduid; # Change uid back to root
			$) = $oldgid; # Change gid back to root
			return;
		}
    }

    my $root_idx = shift @seek_idx;
    while( scalar @seek_idx ) {
		my $last_idx = pop @seek_idx;
		$seek_obj = $$root[ $root_idx ];
		foreach my $seek_idx( @seek_idx ) {
			$seek_obj = $$seek_obj[ 3 ][ $seek_idx ];
		}
		splice @{$$seek_obj[ 3 ]}, $last_idx, 1;
		if( scalar @{$$seek_obj[ 3 ]} > 0 ) {
			last;
		}
    }
    &std_out( "Removed '$path_spec'\n" );

    write_output( $root, \*CONF_OUT, "" );
    close( CONF_OUT );

    rename( "$conf_file.tmp", "$conf_file") || &notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

    &std_out( "'$conf_file' updated.\n" );

}

sub subst_conf_string {
    my( $conf_file, $path_spec, $regexp, $substitution ) = @_;
    my ($olduid, $oldgid);

    &std_out( "Updating '$conf_file'. Substituting entry.\n" );

    if( ! defined $conf_file ) {
		&err_out( "Missing config file name" );
    }

    &backup_file( $conf_file );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory


    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );
    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		close( CONF_IN );
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    my $root = [];
    my $pos = [ $root ];
    my $line;
    my $state = "XNAME";
    while( $line = <CONF_IN> ) {
		&parse_string( $line, $root, $pos, \$state );
    }
    close( CONF_IN );
    if( $state ne "XNAME" || scalar @$pos > 1 ) {
		my $stack = &get_stack( $pos );
		&err_out( "ERROR: Error in config file at \"$stack\". State: $state\n" );
    }


    my $path = [];
    @$path = split( '\.', $path_spec );

    my $seek_obj = $root;
    my @seek_idx = ();
    foreach my $key( @$path ) {
		my $found = 0;
		my $idx = 0;
		foreach my $obj( @$seek_obj ) {
			if( $$obj[ 0 ] eq $key ) {
				$found = 1;
				$seek_obj = $$obj[ 3 ];
				push @seek_idx, $idx;
				last;
			}
			$idx++;
		}
		if( ! $found ) {
			&std_out( "'$path_spec' does not exist. Nothing to do.\n" );
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			$> = $olduid; # Change uid back to root
			$) = $oldgid; # Change gid back to root
			return;
		}
    }

    my $root_idx = shift @seek_idx;
    if( scalar @seek_idx ) {
		$seek_obj = $$root[ $root_idx ];
		foreach my $seek_idx( @seek_idx ) {
			$seek_obj = $$seek_obj[ 3 ][ $seek_idx ];
		}
		$$seek_obj[ 2 ] =~ s/$regexp/$substitution/;
    }
    &std_out( "Substituted '$path_spec'\n" );

    write_output( $root, \*CONF_OUT, "" );
    close( CONF_OUT );

    rename( "$conf_file.tmp", "$conf_file") || &notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

    &std_out( "'$conf_file' updated.\n" );

}

sub parse_string {
    my( $line, $root, $pos, $state_ptr ) = @_;

    chomp $line;

    while( length $line > 0 ) {
		my $char = substr( $line, 0, 1, "" );
		if( substr( $$state_ptr, 0, 1 ) eq "X" && $char =~ m/\s/ ) {
			next;
		}

		if( $$state_ptr eq "XNAME" ) {
			if( $char eq "}" ) {
				pop @$pos;
				pop @$pos;
				next;
			}
			push @{$$pos[ $#$pos ]}, [ $char, "", "", [] ];
			push @$pos, $$pos[ $#$pos ][ $#{$$pos[ $#$pos ]} ];
			$$state_ptr = "INNAME";
			next;
		}

		if( $$state_ptr eq "INNAME" ) {
			if( $char =~ m/\s/ ) {
				$$state_ptr = "XLP";
				next;
			}
			if( $char eq "(" ) {
				$$state_ptr = "XTYPE";
				next;
			}
			$$pos[ $#$pos ][ 0 ] .= $char;
			next;
		}

		if( $$state_ptr eq "XLP" ) {
			if( $char eq "(" ) {
				$$state_ptr = "XTYPE";
				next;
			}
			close( CONF_IN );
			close( CONF_OUT );
			&err_out( "ERROR: Space not allowed in object name." );
		}

		if( $$state_ptr eq "XTYPE" ) {
			if( $char eq ")" ) {
				$$state_ptr = "XEQ";
				next;
			}
			$$pos[ $#$pos ][ 1 ] .= $char;
			$$state_ptr = "INTYPE";
			next;
		}

		if( $$state_ptr eq "INTYPE" ) {
			if( $char eq ")" ) {
				$$state_ptr = "XEQ";
				next;
			}
			$$pos[ $#$pos ][ 1 ] .= $char;
			next;
		}

		if( $$state_ptr eq "XEQ" ) {
			if( $char eq "=" ) {
				$$state_ptr = "XQUOTE";
				next;
			}
			close( CONF_IN );
			close( CONF_OUT );
			&err_out( "ERROR: Missing equal sign." );
		}

		if( $$state_ptr eq "XQUOTE" ) {
			if( $char eq "\"" ) {
				$$state_ptr = "INVALUE";
				next;
			}
			close( CONF_IN );
			close( CONF_OUT );
			&err_out( "ERROR: Missing quotes." );
		}

		if( $$state_ptr eq "INVALUE" ) {
			if( $char eq "\"" ) {
				$$state_ptr = "XLC";
				next;
			}
			$$pos[ $#$pos ][ 2 ] .= $char;
			next;
		}

		if( $$state_ptr eq "XLC" ) {
			if( $char eq "{" ) {
				push @$pos, $$pos[ $#$pos ][ $#{$$pos[ $#$pos ]} ];
				$$state_ptr = "XNAME";
				next;
			}
			close( CONF_IN );
			close( CONF_OUT );
			&err_out( "ERROR: Missing left curly brackets." );
		}
    }
}

sub get_stack {
    my( $pos ) = @_;
    my $stack = "";
    for( my $i = 1; $i < scalar @$pos; $i += 2 ) {
		$stack .= $$pos[ $i ][ 0 ];
		if( $i + 2 < scalar @$pos ) {
			$stack .= ".";
		}
    }
    return $stack;
}

sub verify {
    my( $type, @params ) = @_;
    if( $type eq "exit_status" ) {
		my $command = $params[ 0 ];
		if( $command !~ m#^/# ) {
			$command = "files/$command";
		}
		my $stat = $params[ 1 ];
		my $found_stat = system( $command );
		my $low = $found_stat & 0xff;
		$found_stat >>= 8;
		if( $stat == $found_stat ) {
			&std_out( "Verified exit status of '$command': $stat.\n" );
			return 1;
		}
		&std_out( "Exit status of '$command': $found_stat != $stat. Skipping.\n" );
		return 0;
    }
    if( $type eq "output_line" ) {
		my $command = $params[ 0 ];
		if( $command !~ m#^/# ) {
			$command = "files/$command";
		}
		my $output = $params[ 1 ];
		my @found_output = `$command 2>&1`;
		if( scalar @found_output != 1 ) {
			&std_out( "Number of lined in output of '$command': " .
					  (scalar @found_output) . ". 1 expected. Skipping.\n" );
			return 0;
		}
		my $line = $found_output[ 0 ];
		chomp $line;
		if( $output eq $line ) {
			&std_out( "Verified output from '$command': $output.\n" );
			return 1;
		}
		&std_out( "Output of '$command': '$line' ne '$output'. Skipping.\n" );
		return 0;
    }
    &err_out( "Unknown verify type: $type\n" );
}

sub add_new_lines {
    my( $conf_file, $exclude, $new_file ) = @_;
    my ($olduid, $oldgid);

    &std_out( "Updating '$conf_file'.\n" );

    if( ! defined $conf_file ) {
		&err_out( "Missing config file name" );
    }

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    $new_file = "files/$new_file";
    if( ! -f $new_file || -z $new_file ) {
		&err_out( "ERROR: Missing new_file specification.\n" );
    }

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );

    my $found;
    my @file;
    my $line;
    while( $line = <CONF_IN> ) {
		push @file, $line;
		if( $line =~ m/$exclude/ ) {
			$found = 1;
		}
    }
    close( CONF_IN );

    if( $found ) {
		&std_out( "'$conf_file' does not need update. Skipping.\n" );
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }

    &backup_file( $conf_file );

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    if( ! open( CONF_DIFF, "<$new_file" ) ) {
		my $err = $!;
		close( CONF_OUT );
		unlink "$conf_file.tmp";
		&err_out( "ERROR: Could not open '$new_file': $err\n" );
    }

    push @file, "\n";

    while( $line = <CONF_DIFF> ) {
		push @file, $line;
    }
    close( CONF_DIFF );

    foreach $line( @file ) {
		if( ! print CONF_OUT $line ) {
			my $err = $!;
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
		}
    }
    close( CONF_OUT );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}

sub add_new_gen_lines {
    my( $conf_file, $diff_file ) = @_;
    my ($olduid, $oldgid);

    &std_out( "Updating '$conf_file'.\n" );

    if( ! defined $conf_file ) {
		&err_out( "Missing config file name" );
    }

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    $diff_file = "files/$diff_file";
    if( ! -f $diff_file || -z $diff_file ) {
		&err_out( "ERROR: Missing diff_file specification.\n" );
    }

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );
    my @file = <CONF_IN>;
    close( CONF_IN );

    &backup_file( $conf_file );

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    if( ! open( CONF_DIFF, "<$diff_file" ) ) {
		my $err = $!;
		close( CONF_OUT );
		unlink "$conf_file.tmp";
		&err_out( "ERROR: Could not open '$diff_file': $err\n" );
    }

    my @diff = <CONF_DIFF>;
    close( CONF_DIFF );

    my $header = shift @diff;
    my $header_match;
    if( $header =~ m/^(TYPE:[^:]+):/ ) {
		$header_match = $1;
    } else {
		close( CONF_OUT );
		&err_out( "ERROR: Header line does not match header standard.\n" );
    }

    my $i;
    my $found = undef;
    my @dt_match = ();
    foreach my $diff( @diff ) {
		push @dt_match, ( split( ":", $diff ) )[ 0 ];
    }
    for( $i = 0; $i < scalar @file; $i++ ) {
		if( ! $found && $file[ $i ] =~ /$header_match/ ) {
			$found = 1;
		} elsif( $found && $file[ $i ] =~ /^TYPE:/ ) {
			last;
		} elsif( $found ) {
			for( my $j = 0; $j < scalar @diff; $j++ ) {
				my $match = $dt_match[ $j ];
				if( $file[ $i ] =~ m/^$match:/ ) {
					splice @diff, $j, 1, ();
					splice @dt_match, $j, 1, ();
					last;
				}
			}
		}
    }
    if( $found ) {
		$i--;
		while( $file[ $i ] =~ m/^\s*$/ ) {
			$i--;
		}
		chomp $file[ $i ]; $file[ $i ] .= "\n";
		$i++;
		splice @file, $i, 0, @diff;
    } else {
		push @file, "\n";
		push @file, $header;
		push @file, @diff;
    }

    foreach my $line( @file ) {
		if( ! print CONF_OUT $line ) {
			my $err = $!;
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
		}
    }
    close( CONF_OUT );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}





sub add_module_ids {
    my( $module, @data_types ) = @_;
    my ($olduid, $oldgid);

    my $conf_file = "/var/opt/openit/etc/modules";
    &std_out( "Updating '$conf_file'.\n" );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );

    my $found;
    my @file;
    my $line;
    my $reg_module = $module;
    $reg_module =~ s/\^/\\^/g;
    while( $line = <CONF_IN> ) {
		push @file, $line;
		if( $line =~ m/$reg_module/ ) {
			$found = 1;
		}
    }
    close( CONF_IN );

    if( ! $found ) {
		&notice_out( "Warning: '$conf_file' does not contain module '$module'. Skipping.\n" );
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }

    &backup_file( $conf_file );

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    for( my $i = 0; $i < scalar @file; $i++ ) {
		if( $file[ $i ] !~ m/$reg_module/ ) {
			next;
		}
		if( $file[ $i ] !~ m/$reg_module\s*,\s*\(([^\)]+)\)/ ) {
			&notice_out( "Warning: Could not recognize format of '$conf_file'. Skipping.\n" );
			$> = $olduid; # Change uid back to root
			$) = $oldgid; # Change gid back to root
			return;
		}
		my $list = $1;
		$list =~ s/\s+//g;
		my @list = split ",", $list;
		my %list = ();
		my $dt;
		foreach $dt( @list ) {
			$list{ $dt } = 1;
		}
		foreach $dt( @data_types ) {
			$list{ $dt } = $dt;
		}
		$list = join ", ", sort { $a <=> $b } keys %list;
		$list = "($list)";
		$file[ $i ] =~ s/$reg_module\s*,\s*\([^\)]+\)/$module, $list/;
		last;
    }

    foreach $line( @file ) {
		if( ! print CONF_OUT $line ) {
			my $err = $!;
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
		}
    }
    close( CONF_OUT );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}


sub add_classvar_config {
    my( $exclude, $new_file ) = @_;
    my ($olduid, $oldgid);

    my $conf_file = "/var/opt/openit/etc/classvar_mapping_config";
    &std_out( "Updating '$conf_file'.\n" );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    $new_file = "files/$new_file";
    if( ! -f $new_file || -z $new_file ) {
		&err_out( "ERROR: Missing new_file specification.\n" );
    }

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );

    my $found;
    my @file;
    my $line;
    $exclude =~ s/\^/\\^/g;
    while( $line = <CONF_IN> ) {
		push @file, $line;
		if( $line =~ m/$exclude/ ) {
			$found = 1;
		}
    }
    close( CONF_IN );

    if( $found ) {
		&std_out( "'$conf_file' already updated. Skipping.\n" );
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }

    &backup_file( $conf_file );

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    my $remove_parenthesis = 1;
    my $add_comma = 1;
    while( $remove_parenthesis || $add_comma ) {
		my $last = pop @file;
		if( $remove_parenthesis && $last =~ m/\)\s*$/ ) {
			$last =~ s/\s*\)\s*$//;
			$remove_parenthesis = 0;
		} elsif( ! $remove_parenthesis && $last =~ m/\S/ ) {
			$last =~ s/,+\s+$//;
			push @file, "$last,\n\n";
			$add_comma = 0;
		}
    }

    if( ! open( CONF_DIFF, "<$new_file" ) ) {
		my $err = $!;
		close( CONF_OUT );
		unlink "$conf_file.tmp";
		&err_out( "ERROR: Could not open '$new_file': $err\n" );
    }

    push @file, "\n";

    while( $line = <CONF_DIFF> ) {
		push @file, $line;
    }
    close( CONF_DIFF );

    push @file, "\n)\n";

    foreach $line( @file ) {
		if( ! print CONF_OUT $line ) {
			my $err = $!;
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
		}
    }
    close( CONF_OUT );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}



sub add_dist {
    my( $exclude, $target ) = @_;
    my ($olduid, $oldgid);

    my $conf_file = "/var/opt/openit/etc/dist_replace";
    &std_out( "Updating '$conf_file'.\n" );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );

    my $found;
    my @file;
    my $line;
    $exclude =~ s/\^/\\^/g;
    while( $line = <CONF_IN> ) {
		push @file, $line;
		if( $line =~ m/$exclude/ ) {
			$found = 1;
		}
    }
    close( CONF_IN );

    if( $found ) {
		&std_out( "'$conf_file' already updated. Checking order.\n" );
    }

    # extract license
    my $license = undef;
    my $license_idx = undef;
    my $last_idx = undef;
    my $i;
    for( $i = 0; $i < scalar @file; $i++ ) {
		# Assume that the last comma indicates it is not the last element
		if( $file[ $i ] =~
			m/^\s*\([^\(]+\/license\s*,\s*license\s*\)\s*,?\s*$/ ) {
			$license = $file[ $i ];
			$license_idx = $i;
		}
		my $recognized = 0;
		if( $file[ $i ] =~
			m/^\s*\([^\(\),]+,[^\(\),]+\)\s*,?\s*$/ ) {
			$last_idx = $i;
			$recognized = 1;
		}
		if( ! $recognized && $file[ $i ] !~ m/^\s*(\(|\))?\s*$/ ) {
			&notice_out( "Warning: Could not recognize format of '$conf_file'. Skipping.\n" );
			$> = $olduid; # Change uid back to root
			$) = $oldgid; # Change gid back to root
			return;
		}
    }

    if( ! defined $license ) {
		&err_out( "No license line found in '$conf_file'.\n" );
    }

    $file[ $last_idx ] =~ s/(\))(\s*)$/$1,$2/;
    splice( @file, $license_idx, 1 );
    $license =~ s/,\s*$//;
    $license = "$license\n";
    if( ! $found ) {
		$target =~ s/\s*$//;
		$target = "$target\n";
		splice( @file, $last_idx, 0, $target, $license );
    } else {
		splice( @file, $last_idx, 0, $license );
    }

    &backup_file( $conf_file );

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    foreach $line( @file ) {
		if( ! print CONF_OUT $line ) {
			my $err = $!;
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
		}
    }
    close( CONF_OUT );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}

sub fix_openit_cfg {
    my( $directive, @value ) = @_;
    my ($olduid, $oldgid);

    my $value .= join ":", @value;

    my $conf_file = "/etc/opt/openit/openit.cfg";
    &std_out( "Updating '$conf_file'.\n" );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }
    if( ! open( CONF_IN, "<$conf_file" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file: $err" );
    }

    # Copy top definitions from old config
    my $replaced = 0;
    while(my $line = <CONF_IN>) {
		if($line =~ /^$directive\s/) {       # Replace existing
			if( length $value ) {
				print CONF_OUT "$directive $value\n";
			}
			$replaced = 1;
		} else {
			print CONF_OUT $line;
		}
    }
    close(CONF_IN);

    if( ! $replaced && length $value ) {       # Add if not seen before
		print CONF_OUT "$directive $value\n";
    }

    close(CONF_OUT);

    &backup_file( $conf_file );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}

sub substitute_lines {
    my( $conf_file, $pattern, $substitution ) = @_;
    my ($olduid, $oldgid);

    &std_out( "Substituting line in '$conf_file'.\n" );

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );

    my $found = 0;
    my @file;
    my $line;
    while( $line = <CONF_IN> ) {
		if( $line =~ m/$pattern/ ) {
			$line =~ s/$pattern/$substitution/;
			$found++;
		}
		push @file, $line;
    }
    close( CONF_IN );

    if( $found == 0 ) {
		&std_out( "'$conf_file' already updated? No substitutions.\n" );
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }

    &backup_file( $conf_file );

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    foreach $line( @file ) {
		if( ! print CONF_OUT $line ) {
			my $err = $!;
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
		}
    }
    close( CONF_OUT );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated: $found substitutions.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}

sub verify_aix_link {
    my ($olduid, $oldgid);
    $olduid = $>; # Save uid
    $oldgid = $(; # Save gid

    my $statfrom = "files/";
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory
    my $output = `uname -s`;
    chomp $output;
    if( $output !~ m/AIX/ ) {
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }
    $output = `cat /var/opt/openit/inst/product/base.prod`;
    chomp $output;
    if( $output !~ m/aix4\.2\.0\.0/ ) {
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }
    if( -e "files/powerpc-ibm-aix4.2.0.0" ) {
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }
    if( ! symlink( "powerpc-ibm-aix5.1.0.0",
				   "files/powerpc-ibm-aix4.2.0.0" ) ) {
		&std_out( "Warning: Could not make symlink from aix4.2 to aix5.1 dir\n" );
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }
    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root
    &std_out( "Created symlink from aix4.2 to aix5.1 dir.\n" );
}

sub change_xml_value {
    my( $conf_file, $path, $new_value, $regexp_in ) = @_;
    my ($olduid, $oldgid);

    if( ! defined $regexp_in ) {
		&std_out( "Substituting '$path' in '$conf_file' to '$new_value'.\n" );
    } else {
		&std_out( "Regex subst '$path' in '$conf_file': $regexp_in->$new_value.\n" );
    }

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory

    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );

    my @path = split /\./, $path;

    my $found = 0;
    my $level = 0;
    my $path_level = 0;
    my @file;
    my $line;

    while( $line = <CONF_IN> ) {
		if( ! $found ) {
			if( $line =~ m/^\s*\<SubObjects\>\s*$/ ) {
				$level += 1;
			} elsif( $line =~ m/^\s*\<\/SubObjects\>\s*$/ ) {
				$level -= 1;
			} elsif( $line =~ m/^\s*\<Name\>/ && $level == $path_level ) {
				$line =~ m/^\s*<Name>(.*)<\/Name>\s*$/;
				if( $1 eq $path[ $path_level ] ) {
					$path_level += 1;
				}
				if( ! defined $path[ $path_level ] ) {
					push @file, $line;
					while( $line = <CONF_IN> ) {
						if( $line !~ m/^\s*<Value type.*>.*<\/Value>\s*$/ ) {
							push @file, $line;
							next;
						}
						chomp $line;
						$line =~ m/^(.*\>)(.*)(<.*)/;
						my( $start, $value, $end ) = ( $1, $2, $3 );
						if( ! defined $regexp_in ) {
							$value = $new_value;
						} else {
							$value =~ s/$regexp_in/$new_value/;
						}
						$line = "$start$value$end\n";
						$found = 1;
						last;
					}
				}
			}
		}
		push @file, $line;
    }
    close( CONF_IN );

    if( ! $found ) {
		&std_out( "Did not find '$path' in '$conf_file'. No change.\n" );
		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
		return;
    }

    &backup_file( $conf_file );

    if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
		my $err = $!;
		&err_out( "Could not open $conf_file.tmp: $err" );
    }

    foreach $line( @file ) {
		if( ! print CONF_OUT $line ) {
			my $err = $!;
			close( CONF_OUT );
			unlink "$conf_file.tmp";
			&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
		}
    }
    close( CONF_OUT );

    if( ! rename( "$conf_file.tmp", "$conf_file") ) {
		&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
    } else {
		&std_out( "'$conf_file' updated.\n" );
    }

    $> = $olduid; # Change uid back to root
    $) = $oldgid; # Change gid back to root

}

sub add_conf_to_xml {
    my( $conf_file, $path, $diff_file, $regexp_in ) = @_;
    my ($olduid, $oldgid);

    if( ! -e $conf_file ) {
        &std_out( "$conf_file does not exist. Skipping update.\n" );
        return;
    }

    $diff_file = "files/$diff_file";	
    my @diff = split(/\//, $diff_file);
    pop(@diff); # Remove the filename after full path	

    my @p = split(/\//, $conf_file);
    pop(@p); # Remove the filename after full path
    my $statfrom = join("/", @p);
    my ($dev, $ino, $mode, $nlink, $uid, $gid) = stat($statfrom);
    if (!defined($gid)) {
        &notice_out( "Failed to stat $statfrom: $!" );
    }
    $olduid = $>;
    $oldgid = $(;

    $) = $gid; # Change to the group of the parent directory
    $> = $uid; # Change to the owner of the parent directory
	
    open( CONF_IN, "<$conf_file" ) or &err_out( "Could not open $conf_file: $!" );
    open( CONF_READ, "<$diff_file" ) or &err_out( "Could not open $diff_file: $!" );
    my @path = split /\./, $path;

    my $matched = 0;
    $regexp_in =~ s/^\s+//;
    $regexp_in =~ s/\s+$//;
    while ( <CONF_IN> ) {
		if( /$regexp_in/ ) {
			$matched = 1;
			&notice_out( "Pattern \"$regexp_in\" matched in line $." );
			&notice_out( "Not adding configuration, assuming the configuration object is already present." );
			last;
		}
    }
    
    if( ! $matched ) {
		seek( CONF_IN, 0, 0 );
		my $found = 0;
		my $level = 0;
		my $path_level = 0;
		my @file;
		my $line;
		my $insert;

		while( $line = <CONF_IN> ) {
			if( ! $found ) {
				if( $line =~ m/^\s*\<SubObjects\>\s*$/ ) {
					$level += 1;
				} elsif( $line =~ m/^\s*\<\/SubObjects\>\s*$/ ) {
					$level -= 1;
				} elsif( $line =~ m/^\s*\<Name\>/ && $level == $path_level ) {
					$line =~ m/^\s*<Name>(.*)<\/Name>\s*$/;
					if( $1 eq $path[ $path_level ] ) {
						$path_level += 1;
					}
					if( ! defined $path[ $path_level ] ) {
						push @file, $line;
						while( $line = <CONF_IN> ) {
							push @file, $line;
							if( $line !~ m/^\s*\<SubObjects\>\s*$/ ) {
								next;
							}
							while( $insert = <CONF_READ> ) {
								push @file, $insert;
								next;
							}
							$found = 1;
							last;
						}
						next;
					}
				}
			}
			push @file, $line;
		}
		close( CONF_IN );
		close( CONF_READ );

		if( ! $found ) {
			&std_out( "Did not find '$path' in '$conf_file'. No change.\n" );
			$> = $olduid; # Change uid back to root
			$) = $oldgid; # Change gid back to root
			return;
		}

		&backup_file( $conf_file );

		if( ! open( CONF_OUT, ">$conf_file.tmp" ) ) {
			my $err = $!;
			&err_out( "Could not open $conf_file.tmp: $err" );
		}

		foreach $line( @file ) {
			if( ! print CONF_OUT $line ) {
				my $err = $!;
				close( CONF_OUT );
				unlink "$conf_file.tmp";
				&err_out( "ERROR: Could not write to '$conf_file.tmp': $err\n" );
			}
		}
		close( CONF_OUT );

		if( ! rename( "$conf_file.tmp", "$conf_file") ) {
			&notice_out( "Failed to rename $conf_file.tmp to $conf_file: $!" );
		} else {
			&std_out( "'$conf_file' updated.\n" );
		}

		$> = $olduid; # Change uid back to root
		$) = $oldgid; # Change gid back to root
    }
}

