package Portage;

# Copyright 1999-2014 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
# $Header: $

use strict;
use warnings;

BEGIN {
    use Exporter ();
    
    our @ISA         = qw(Exporter);
    our %EXPORT_TAGS = ();
    our @EXPORT_OK   = ();
}


# --- public members ---

# Set this to 1 to get debugging output
use constant { DEBUG => 0 };
## Note: Although use constant is deprecated as of page 55 of PBP
# [ValuesAndExpressions::ProhibitConstantPragma] and should be replaced by
# ReadOnly, I do not see any gain in adding a dependency to dev-perl/Readonly
# just for one value that is used when debugging only. - sed


# $use_flags - hashref that represents the combined and
# consolidated data about all valid use flags
# Layout of $use_flags->{flag_name}:
# {count}  = number of different description lines
#  Note: +1 for the list of affected packages, and +1 for each descriptionless
#        package with settings differing from global.
# {global} = hashref for the global paramters if the flag has a description in
#            use.desc, otherwise undefined
#   ->{conf}      = The flag is disabled (-1), enabled (1) or not set (0) in make.conf
#   ->{default}   = The flag is disabled (-1), enabled (1) or not set (0) by default
#   ->{descr}     = Global description
#   ->{descr_alt} = Alternative description with stripped words like "Enables support for"
#   ->{forced}    = The flag is globally force enabled (and masked) (0,1)
#   ->{installed} = At least one affected package is installed (0,1)
#   ->{masked}    = The flag is globally masked (0,1)
#     Note: When a flag is forced, {masked} is set to one, but can be reset to
#           0 by any later use.mask file.
# {"local"}->{package} = hashref for per package settings
#   ->{descr}     = Description from use.local.desc or empty if there is no
#                   individual description.
#     Note: Packages without description are only listed here if their settings
#           differ from the global.
#   ->{forced}    = The flag is explicitly unforced (-1), default (0) or
#                   explicitly force enabled (1) for this package
#   ->{installed} = This package is installed
#   ->{masked}    = The flag is explicitly unmasked (-1), default (0) or
#                   masked (1) for this package
#   ->{package}   = The flag is explicitly disabled (-1), default (0) or
#                   enabled (1) for this package by (profiles)/package.use
#   ->{pkguse}    = The flag is explicitly disabled (-1), default (0) or
#                   enabled(1) for this package by /etc/portage/package.use
#     Note: This is a combination of the ebuilds IUSE and the installation
#           PKGUSE and only set for installed packages.
our $use_flags;

# $used_make_conf - path of the used make.conf
# If PREFIX/etc/[portage/]make.conf is a directory, $used_make_conf
# points to the file the last USE settings are found in.
our $used_make_conf = "";

# If the found make.conf can not be changed by the effective
# uid/gid of the caller, this value is set to 1
our $ro_mode = 0;

# --- private members ---
my %_environment     = ();
my $_EPREFIX         = "";
my $_PORTDIR         = "";
my $_PORTDIR_OVERLAY = "";
my @_profiles        = ();
my %_use_eh_safe     = (); ## USE_EXPAND_HIDDEN safe hash. See _read_make_defaults()
my %_use_order       = ();

# $_use_temp - hashref that represents the current state of all known flags.
# This is for data gathering, the public $use_flags is generated out of this
# by _gen_use_flags().
# Layout of $_use_temp->{flag_name}:
# {global}  = conf hash for global settings
# {"local"}-> {package} = conf hash for per package settings
# global and per package settings:
# ->{conf}      Is either disabled, left alone or enabled by make.conf (-1, 0, 1)
# ->{default}   Is either disabled, left alone or enabled by make.defaults
#               (-1, 0, 1) (global) or installed packages IUSE (local)
# ->{descr}     Description from use.desc ({global}) or use.local.desc {cat/pkg}
# ->{forced}    Is force enabled (implies {masked}=1) in any *use.force
#               For packages this is only set to -1 (explicitly unforced) or
#               +1 (explicitly forced). 0 means "left alone".
# ->{installed} Has one installed package ({global}) or is installed {cat/pkg}
# ->{masked}    Is masked by any *use.mask (0,1)
#               For packages this is only set to -1 (explicitly unmasked) or +1
#               (explicitly masked). 0 means "left alone".
# ->{package}   Is either disabled, left alone or enabled by the profiles
#               package.use files
# ->{pkguse}    Is either disabled, left alone or enabled by the users
#               package.use file

my $_use_temp = undef;
my $_use_template = {
	conf      => 0,
	"default" => 0,
	descr     => "",
	forced    => 0,
	installed => 0,
	masked    => 0,
	"package" => 0,
	pkguse    => 0
};
my $_has_eix = 0; # Set to 1 by INIT if eix can be found.
my $_eix_cmd = "";

# --- public methods ---
sub debugMsg;

# --- private methods ---
sub _add_flag;
sub _add_temp;
sub _determine_eprefix_portdir;
sub _determine_make_conf;
sub _determine_profiles;
sub _final_cleaning;
sub _fix_flags;
sub _gen_use_flags;
sub _get_files_from_dir;
sub _merge;
sub _merge_env;
sub _noncomments;
sub _norm_path;
sub _read_archs;
sub _read_descriptions;
sub _read_make_conf;
sub _read_make_defaults;
sub _read_make_globals;
sub _read_package_use;
sub _read_packages;
sub _read_sh;
sub _read_use_force;
sub _read_use_mask;
sub _remove_expands;

# --- Package initialization ---
INIT {
	$_environment{$_} = {} for qw{USE USE_EXPAND USE_EXPAND_HIDDEN};
	
	# See if eix is available
	$_eix_cmd = qx{which eix 2>/dev/null};
	defined($_eix_cmd)
        and chomp $_eix_cmd
        and length($_eix_cmd)
        and -x $_eix_cmd
        and $_has_eix = 1
        and debugMsg("Found eix in \"$_eix_cmd\"")
         or $_has_eix = 0;
	
	# Initialize basics
	_determine_eprefix_portdir;
	_determine_make_conf;
	_determine_profiles;
	_read_make_globals;

	# make.conf is loaded first to parse for the set overlays
	# directories, if any. USE flags from make.conf get a
	# dedicated area {conf}, so there is no harm in loading
	# it first.
	_read_make_conf;

	# USE_ORDER must not only be defined, it sets the order in which settings
	# are loaded overriding each other.
	defined($_environment{USE_ORDER})
		or die("Unable to determine USE_ORDER!\nSomething is seriously broken!\n");
	my $ordNr  = 0;
	my @use_order = reverse split /:/, $_environment{USE_ORDER};
	for my $order(@use_order) {
		"env"         eq $order and next; ## Not used by ufed
		"pkg"         eq $order and _read_package_use;
		# "conf" is already loaded
		"defaults"    eq $order and _read_make_defaults;
		"pkginternal" eq $order and _read_packages;
		"repo"        eq $order and next; ## Done in "defaults" and "pkginternal" in the right order
		"env.d"       eq $order and next; ## Not used by ufed
		$_use_order{$order} = ++$ordNr;
	}
	if ( !defined($_use_order{"defaults"})
	  || !defined($_use_order{"conf"})
	  || ($_use_order{"defaults"} > $_use_order{"conf"})) {
		die("Sorry, USE_ORDER without make.conf overriding global"
		  . " USE flags are not currently supported by ufed.\n");
	}

	# Now the rest can be read	
	_read_use_force; ## Must be before _read_use_mask to not
	_read_use_mask;  ## unintentionally unmask explicitly masked flags.
	_read_archs;
	_read_descriptions;
	_remove_expands;
	_fix_flags;
	_final_cleaning;
	_gen_use_flags;
}

# --- public methods implementations ---

# Write a given message to STDERR adding a newline at the end
# This function does nothing unless DEBUG is set to something
# different than zero
# Parameter 1: The message
sub debugMsg
{
	my ($msg) = @_;
	DEBUG or return 1;
	print STDERR "$msg\n";
	return 1;
}

# --- private methods implementations ---

# Add a flag to $use_flags and intialize it with the given
# description hash key.
# Parameter 1: flag
# Parameter 2: Keyword "global" or the package name
# Parameter 3: description hash key
sub _add_flag
{
	my ($flag, $pkg, $descKey) = @_;
	
	if ($descKey =~ /^\[(.*)\](-?\d+):(-?\d+):(-?\d+):(-?\d+):(-?\d+):(-?\d+):(-?\d+)$/ ) {
		my ($descr, $conf, $default, $forced, $installed, $masked, $package, $pkguse)
			= ($1, $2, $3, $4, $5, $6, $7, $8);
		my %data = ();

		# An alternate form of the description is a version
		# where various words are stripped, as they do not
		# bear much information.
		my $descr_alt = $descr;
		
		if (length($descr)) {
			my $VERB   = "(?:build|include|enable|add|support|use|be|install)(?:s)?";
			my $BIND   = "(?:and|for|in|the|a)?";
			my $WHAT   = "(?:install|support|build|include|able)?(?:s|ing|ed)?";
			my $POST   = "(?:in|for|the|on|with|a|to)?";
			$descr_alt =~ s/^$VERB\s*$BIND\s*$WHAT\s*$POST\s*$POST\s+//mig;
			
			debugMsg("$flag : [$pkg]\n   \"$descr\"\n-> \"$descr_alt\"");
		}

		$data{descr}     = $descr;
		$data{descr_alt} = $descr_alt;
		$data{forced}    = $forced;
		$data{installed} = $installed;
		$data{masked}    = $masked;
		$data{"package"} = $package;
		$data{"default"} = $default;
		if ("global" eq "$pkg") {
			$data{conf}      = $conf;
			%{$use_flags->{$flag}{global}} = %data;
		} else {
			$data{pkguse}    = $pkguse;
			%{$use_flags->{$flag}{"local"}{$pkg}} = %data;
		}
		++$use_flags->{$flag}{count} if (length($descr));


	} else {
		die ("\n\"$flag\" ($pkg) Description Hash Key\n  \"$descKey\"\nis illegal!\n");
	}

	return 1;	
}


# Add a flag to $_use_temp if it does not exist
# Parameter 1: flag
# Parameter 2: Keyword "global" or "category/package"
sub _add_temp
{
	my ($flag, $pkg) = @_;
	my $isAdded = 0;
	
	(defined($flag) && length($flag))
		or return;

	if ("global" eq $pkg) {
		defined ($_use_temp->{$flag}{global})
			or %{$_use_temp->{$flag}{global}} = %$_use_template;
	} else {
		defined ($_use_temp->{$flag}{"local"}{$pkg})
			or %{$_use_temp->{$flag}{"local"}{$pkg}} = %$_use_template;
	}

	return;
}


# Determine the values for EPREFIX, PORTDIR and PORTDIR_OVERLAY. These are
# saved in $_EPREFIX, $_PORTDIR and $_PORTDIR_OVERLAY.
# This is done using 'eix' with 'portageq' as a fallback.
# Other output from portageq is printed on STDERR.
# No parameters accepted.
sub _determine_eprefix_portdir {
	my $tmp = "/tmp/ufed_$$.tmp";

	$_EPREFIX = qx{portageq envvar EPREFIX 2>$tmp};
	defined($_EPREFIX) and chomp $_EPREFIX or $_EPREFIX="";
	
	# Prefer eix over portageq if it is available
	if ($_has_eix) {
		debugMsg("Using eix...");
		
		local $ENV{PRINT_APPEND}='';
		$_PORTDIR         = qx{$_eix_cmd --print PORTDIR 2>>$tmp};
		$_PORTDIR_OVERLAY = qx{$_eix_cmd --print PORTDIR_OVERLAY 2>>$tmp};
		
		# eix ends PORTDIR with a slash that must be removed
		$_PORTDIR =~ s,/+$,,;
	} else {
		debugMsg("Using portageq fallback...");

		my $eroot = qx{portageq envvar EROOT 2>>$tmp};
		defined($eroot) and chomp $eroot or $eroot="/";
		
		# Remove 'gentoo', this is PORTDIR, the others are PORTDIR_OVERLAY.
		my $repos = join(' ', map {
			my $x = $_;
			$x =~ s/^gentoo$//;
			$x
		} split(' ', qx{portageq get_repos $eroot 2>>$tmp}) );
		chomp $repos;
		
		# Now the paths can be determined:
		$_PORTDIR         = qx{portageq get_repo_path $eroot gentoo 2>>$tmp};
		$_PORTDIR_OVERLAY = join(' ', map {
			my $x = $_;
			$x =~ s/^\s*(\S+)\s*$/$1/mg;
			$x
		} split('\n', qx{portageq get_repo_path $eroot $repos 2>>$tmp} ));
		defined($_PORTDIR) and chomp $_PORTDIR;
		defined($_PORTDIR_OVERLAY) and chomp $_PORTDIR_OVERLAY;
	}
	
	debugMsg("EPREFIX='${_EPREFIX}'");
	debugMsg("PORTDIR='${_PORTDIR}'");
	debugMsg("PORTDIR_OVERLAY='${_PORTDIR_OVERLAY}'");

	# Print error messages if any:
	if ( -s $tmp ) {
		if (open (my $fTmp, "<", $tmp)) {
			print STDERR "$_" while (<$fTmp>);
			close $fTmp;
		}
	}
	-e $tmp and unlink $tmp;

	# Die unless this is sane
	defined($_EPREFIX)
		and defined($_PORTDIR)
		and length($_PORTDIR)
		and defined($_PORTDIR_OVERLAY)
		 or die "\nCouldn't determine EPREFIX and PORTDIR from Portage\n";

	return;
}


# determine which make.conf to use
# and save the result in $used_make_conf
sub _determine_make_conf
{
	$used_make_conf = "${_EPREFIX}/etc/portage/make.conf";
	(-r $used_make_conf)
		or $used_make_conf = "${_EPREFIX}/etc/make.conf";
	
	# If $used_make_conf points to a directory now,
	# or if it is simply not present, it is emptied so
	# _read_make_conf will determine the later used
	# value
	
	if ( (! -e $used_make_conf) || (-d $used_make_conf) ) {
		$used_make_conf = "";
	}
		
	return; 
}


# read /etc/make.profile and /etc/portage/make.profile and analyze the complete
# profile tree using the found parent files. Add all found paths to @profiles.

# No parameters accepted.
sub _determine_profiles
{
	my $mp_path = "${_EPREFIX}/etc/portage/make.profile";
	-e $mp_path or $mp_path = "${_EPREFIX}/etc/make.profile";
	
	-e $mp_path
		or die("make.profile can not be found");

	my $mp = undef;
	   (-l $mp_path and $mp = readlink $mp_path)
	or (-d $mp_path and $mp = $mp_path)
		# Be sure it is not a file either:
	or (-f $mp_path and die(
		  "\n$mp_path is a file.\n"
		. " This is an odd setup.\n"
		. " Please report this incident!\n"));
	
	# make.profile is mandatory and must be a link or directory
	defined($mp)
		or die("\n$mp_path is neither symlink nor directory\n");

	# Start with the found path, it is the deepest profile child.
	@_profiles = -l $mp_path ? _norm_path('/etc', $mp) : $mp;
	for (my $i = -1; $i >= -@_profiles; $i--) {
		for(_noncomments("${_profiles[$i]}/parent")) {
			length($_)
				and splice(@_profiles, $i, 0, _norm_path(${_profiles[$i]}, $_));
		}
	}
	return;
}


# This method does a final cleanup of $_use_temp.
# Everything that is to be done _after_ all configs are parsed goes in here.
# No parameters accepted
sub _final_cleaning
{
	# The "disable all" flag is truncated to '*' by the parsing, but
	# it has to read '-*'.
	_add_temp("-*", "global");

	$_use_temp->{'-*'}{global}{descr} = "{Never enable any flags other than those specified in make.conf}";
	$_use_temp->{'-*'}{global}{conf} = 0; ## Can never be -1

	# Set it from the truncated config:
	if (defined($_use_temp->{'*'}{global})) {
		$_use_temp->{'*'}{global}{conf} > -1
			and $_use_temp->{'-*'}{global}{conf} = 1;
	}

	# The following use flags are dangerous or internal only
	# and must no be available using ufed:
	defined($_use_temp->{"*"})         and delete($_use_temp->{"*"});
	defined($_use_temp->{"bootstrap"}) and delete($_use_temp->{"bootstrap"});
	defined($_use_temp->{"build"})     and delete($_use_temp->{"build"});
	defined($_use_temp->{"livecd"})    and delete($_use_temp->{"livecd"});
	defined($_use_temp->{"selinux"})   and delete($_use_temp->{"selinux"});

	return;
}


# This function fixes two aspects of the temporary flag hash:
# A) The {"default"} flag settings of packages might have to be overridden by
#    the {"global"} ones.
#    (see USE_ORDER in man make.conf)
# B) All flags that are specific to explicit versioning have no descriptions
#    yet. This must be enriched from the versionless package setting.
# C) Further flags that have no proper description get the string "(Unknown)"
#    as a description
sub _fix_flags
{
	for my $flag (keys %{$_use_temp}) {
		my $flagRef  = $_use_temp->{$flag}; ## Shortcut
		my $globRef  = $flagRef->{global}  || undef;
		my $locaRef  = $flagRef->{"local"} || undef;
		my $gDesc    = "(Unknown)";
		my $gDefault = 0;
		my $hasLocal = 0;

		# check global part first
		if (defined($globRef)) {
			if (length($globRef->{descr})) {
				$gDesc    = $globRef->{descr};
				$gDefault = $globRef->{"default"};
			} elsif ( $globRef->{conf}
				   || $globRef->{"default"}
				   || $globRef->{forced}
				   || $globRef->{masked} ) {
			    ## The flag is definitely set somewhere
			    $globRef->{descr} = $gDesc;
			}
		}

		# Now check local part
		for my $pkg (sort keys %$locaRef) {
			$hasLocal = 1;
			
			# fix {default} settings if a global one is set
			# This is make.defaults overriding IUSE settings.
			if ( $gDefault
			  && ( !defined($_use_order{"pkginternal"})
			    || ($_use_order{"defaults"} > $_use_order{"pkginternal"})) ) {
				$locaRef->{$pkg}{"default"} = $gDefault;
			}

			# No further action required if a description is present
			next if (length($locaRef->{$pkg}{descr}));
			
			# Otherwise check wether this is worth to be added
			if ( $locaRef->{$pkg}{installed}
			  || $locaRef->{$pkg}{forced}
			  || $locaRef->{$pkg}{masked}
			  || $locaRef->{$pkg}{"package"}
			  || $locaRef->{$pkg}{pkguse}) {
			    # it is set and/or used by an ebuild
				if ($pkg =~ /^[<>=~]+([^<>=~].+)-\d+(?:\.\d+)*\w?(?:_(?:alpha|beta|pre|rc|p)\d*)*(?:-r\d+)?$/) {
					defined($locaRef->{$1})
						and $locaRef->{$pkg}{descr} = $locaRef->{$1}{descr};
				}
				length($locaRef->{$pkg}{descr})
					or $locaRef->{$pkg}{descr} = $gDesc; ## (Unknown) unless set
			}
		} ## End of looping packages

		# Finally remove the global description if it is (Unknown) with at
		# least one local representation present.
		if ($hasLocal && ("(Unknown)" eq $gDesc)) {
			$globRef->{descr} = "";
		}
	} ## End of looping flags

	return;
}


# Once $_use_temp  is ready, this method builds the final $use_flags hashref.
# No parameters accepted
sub _gen_use_flags
{
	for my $flag (keys %$_use_temp) {
		my %descCons = ();
		my $flagRef  = $_use_temp->{$flag}; ## Shortcut
		my $hasGlobal= defined($flagRef->{global}) ? 1 : 0;
		my $lCount   = ($hasGlobal && length($flagRef->{global}{descr})) ? 1 : 0;
		my $gDesc    = "";
		my $gKey     = "";
		my $gRef     = $flagRef->{global};
		my $gdLen    = 0;
		my $pDesc    = "";
		my $pKey     = "";
		my $pRef     = undef;
		my $pdLen    = 0;
		
		# Build the description consolidation hash
		if ($hasGlobal) {
			$gDesc = $gRef->{descr};
			$gdLen = length($gDesc);
			$gKey  = sprintf("[%s]%d:%d:%d:%d:%d:%d:%d", $gDesc, $gRef->{conf}, $gRef->{"default"},
							$gRef->{forced}, $gRef->{installed}, $gRef->{masked},
							$gRef->{"package"}, $gRef->{pkguse});
		}
		for my $pkg (sort keys %{$flagRef->{"local"}}) {
			$pRef  = $flagRef->{"local"}{$pkg};
			$pdLen = length($pRef->{descr});
			
			# only accept entries with a non-empty description:
			$pdLen or next;

			$pDesc = $pRef->{descr};
			
			# Now the Key can be assembled...
			$pKey  = sprintf("[%s]%d:%d:%d:%d:%d:%d:%d", $pDesc, $pRef->{conf}, $pRef->{"default"},
							$pRef->{forced}, $pRef->{installed}, $pRef->{masked},
							$pRef->{"package"}, $pRef->{pkguse});
							
			# ...and safed, if it has an own description or differs in its settings from global
			if ( ($pdLen && ($gDesc ne $pDesc)) ## has an own description
			  || $pRef->{"default"} ## explicitly set default from IUSE
			  || $pRef->{forced}    ## explicitly (un)forced from package.use.force
			  || $pRef->{masked}    ## explicitly (un)masked from package.use.mask
			  || $pRef->{pkguse}    ## explicitly (un)set from users package.use
			   ) {
				$descCons{$pKey}{$pkg} = 1;
				++$lCount;
			}
		} ## End of walking through a flags package list
		
		# Skip if there was nothing consolidated
		next unless($lCount);
		
		# Add the content of $descCons to $use_flags:
		$use_flags->{$flag}{count} = 0;
		
		# The global data has to be added first:
		length($gKey) and _add_flag($flag, "global", $gKey);
		
		# Then the "local" flag descriptions
		for my $key (sort keys %descCons) {

			# Generate the package list with same description and flags,
			# but not for more than 5 packages
			my $packages = "";
			my $pkgCount = 0;
			for my $pkg (sort keys %{$descCons{$key}}) {
				length($packages) and $packages .= ", ";
				$packages .= $pkg;
				if (++$pkgCount == 5) {
					_add_flag($flag, $packages, $key);
					$packages = "";
					$pkgCount = 0;
				}
			}
			
			# Add the last result if there is something left
			$pkgCount and _add_flag($flag, $packages, $key);
		}

		delete $_use_temp->{$flag};
	} ## End of walking through $_use_temp flags

	return;
}


# Get an alphabetical case insensitive list of files
# from a path (_not_ depth first!) and return it
# Param 1: the path to search in
# Param 2: recursive calls should set this to 1
sub _get_files_from_dir {
	my ($search_path, $isRecursive) = @_;
	my @result        = ();
	
	# Nothing to do if the search path is empty
	(	defined($search_path)
	 &&	length($search_path) )
	 or return @result; 
	 
	defined($isRecursive) or $isRecursive = 0;

	for my $confFile (glob("$search_path/*")) {
		
		# Skip hidden and backup files
		if ( (-f $confFile )
		  && ( ($confFile =~ /~$/)
		    || ($confFile =~ /^\./) ) ) {
			debugMsg("Skipping file $confFile");
			next;
		}
		
		# Skip special directories CVS, RCS and SCCS
		if ( (-d $confFile)
		  && ($confFile =~ /\/(?:CVS|RCS|SCCS)$/ ) ) {
			debugMsg("Skipping directory $confFile");
			next;
		}
		
		# recurse if this is a directory:
		if (-d $confFile) {
			push @result, _get_files_from_dir($confFile, 1);
			next;
		}
		
		# Otherwise just add to result list
		push @result, $confFile;
	}

	# return plain list if this is a recursive call
	$isRecursive and return @result;

	# return an alphabetically sorted list:
	my @sorted = sort {lc($a) cmp lc($b)} @result;
	return @sorted;
}


# merges two hashes into the first.
# Parameter 1: reference of the destination hash
# Parameter 2: reference of the source hash
sub _merge {
	my ($dst, $src) = @_;
	%{$dst} = () if(exists $src->{'*'});
	$dst->{$_} = $src->{$_} for(keys %$src);
	return;
}


# Splits content of the source hash at spaces and
# merges its contents into %_environment.
# Parameter 1: reference of the hash to merge
sub _merge_env {
	my ($src) = @_;
	for(keys %_environment) {
		if(ref $_environment{$_} eq 'HASH') {
			if(exists $src->{$_}) {
				my %split;
				for(split ' ', $src->{$_}) {
					my $off = s/^-//;
					%split = () if($_ eq '*');
					$split{$_} = !$off;
				}
				$src->{$_} = { %split };
				_merge(\%{$_environment{$_}}, \%{$src->{$_}});
			}
		}
	}
	for(keys %$src) {
		if(ref $_environment{$_} ne 'HASH') {
			$_environment{$_} = $src->{$_};
		}
	}
	return;
}


# returns a list of all lines of a given file/dir
# that are no pure comments
# Parameter 1: filename/dirname
sub _noncomments {
	my ($fname) = @_;
	my @result  = ();

	if(-d $fname) {
		for my $i (_get_files_from_dir($fname)) {
			(-f $i) && push @result, _noncomments($i);
		}
	} else {
		local $/;
		if(open my $file, '<', $fname) {
			binmode( $file, ":encoding(UTF-8)" );
			my $content = <$file> || "";
			close $file;
			@result = split /(?:[^\S\n]*(?:#.*)?\n)+/, "$content\n";
			shift @result while ( (@result > 0) && !length($result[0]) );
		}
	}

	return @result;
}


# normalizes a given path behind a base
# Parameter 1: base path 
# Parameter 2: sub path
# return: normalized base path / normalized sub path
sub _norm_path {
	my ($base, $path) = @_;
	my @pathcomp = ($path !~ m!^/! && split(m!/!, $base), split(m!/!, $path));
	for(my $i=0;;$i++) {
		last if $i == @pathcomp; # don't want to skip this with redo
		if($pathcomp[$i] eq '' || $pathcomp[$i] eq '.') {
			splice @pathcomp, $i, 1;
			redo;
		}
		if($pathcomp[$i] eq '..') {
			if($i==0) {
				splice @pathcomp, 0, 1;
			} else {
				splice @pathcomp, --$i, 2;
			}
			redo;
		}
	}
	return '/'.join '/', @pathcomp;
}


# reads all found arch.list and erase all found archs from $_use_temp. Archs
# are not setable.
# No parameters accepted
sub _read_archs {
	for my $dir(@_profiles) {
		next unless (-r "$dir/arch.list");
		for my $arch (_noncomments("$dir/arch.list")) {
			length($arch)
				and defined($_use_temp->{$arch})
				and delete($_use_temp->{$arch});
		}
	}
	return;
}


# reads all use.desc and use.local.desc and updates $_use_temp accordingly.
# No parameters accepted
sub _read_descriptions
{
	for my $dir(@_profiles) {
		if (-r "$dir/use.desc") {
			for(_noncomments("$dir/use.desc")) {
				my ($flag, $desc) = /^(.*?)\s+-\s+(.*)$/ or next;
				
				_add_temp($flag, "global");

				$_use_temp->{$flag}{global}{descr} = $desc;
			}
		} ## End of having a use.desc file

		if (-r "$dir/use.local.desc") {
			for(_noncomments("$dir/use.local.desc")) {
				my ($pkg, $flag, $desc) = /^(.*?):(.*?)\s+-\s+(.*)$/ or next;

				# Here we do not explicitly add a {global} part,
				# some flags are local only.
				_add_temp($flag, $pkg);
				
				$_use_temp->{$flag}{"local"}{$pkg}{descr} = $desc;
			}
		} ## End of having a use.local.desc file
	} ## End of looping the profiles
	return;
}


# read make.conf and record the state of all set use flags.
# Additionally add all set portage directories (plus overlays) to @_profiles.
# The last added profile directory, if it exists, is /etc/portage/profile to
# allow recognition of user overrides.
# If either of the make.conf paths is a directory, all files are read in
# alphanumerical order. The file changes are written to will be the last file
# that contains a USE assignement.
# No parameters accepted.
sub _read_make_conf {
	my ($stOldPath, $stNewPath) = (	"${_EPREFIX}/etc/make.conf",
									"${_EPREFIX}/etc/portage/make.conf" );
	my (%oldEnv, %newEnv)		= ([], []);
	my $lastUSEFile				= "";
	
	for my $confPath ($stOldPath, $stNewPath) {
		if ( -d $confPath) {
			my @confFileList = _get_files_from_dir($confPath);
			for my $confFile (@confFileList) {
				debugMsg("Reading $confFile");
				%newEnv = _read_sh($confFile);
				_merge (\%oldEnv, \%newEnv);
				defined($newEnv{USE})
					and $lastUSEFile = $confFile
					and debugMsg(" -> USE flags found");
			}
		} else {
			%newEnv = _read_sh($confPath);
			_merge (\%oldEnv, \%newEnv);
			$lastUSEFile = $confPath;
		}
	}
	
	# If there is no used make.conf found, yet, save it:
	if ( 0 == length($used_make_conf) ) {
		$used_make_conf = $lastUSEFile;
		$used_make_conf =~ /\/make\.conf$/
			or print "Using $used_make_conf as USE flags file\n";
	}
	debugMsg("$used_make_conf will be used to store changes");

	# If the make.conf is not writable, enable read-only-mode
	if (! -w $used_make_conf ) {
		my $egid = $);
		$egid =~ s/\s+.*$//; 
		$ro_mode = 1;
		print "WARNING: $used_make_conf not writable by uid/gid $>/$egid\n";
		print "WARNING: ufed will run in read-only-mode!\n";
	}

	# Note the conf state of the read flags:
	for my $flag ( keys %{$oldEnv{USE}}) {
		_add_temp($flag, "global");

		$oldEnv{USE}{$flag}
			and $_use_temp->{$flag}{global}{conf} = 1
			 or $flag eq '*'
			and $_use_temp->{$flag}{global}{conf} = 1
			 or $_use_temp->{$flag}{global}{conf} = -1;
	}
	
	# Add PORTDIR and overlays to @_profiles
	length ($_PORTDIR)
		and push @_profiles, "${_PORTDIR}/profiles"
		or  die("Unable to determine PORTDIR!\nSomething is seriously broken here!\n");
	length ($_PORTDIR_OVERLAY)
		and push @_profiles, split(' ', $_PORTDIR_OVERLAY);
	-e "${_EPREFIX}/etc/portage/profile"
		and push @_profiles, "${_EPREFIX}/etc/portage/profile";
	return;
}


# read all found make.defaults merge their values into env,
# adding flag parameters to $_use_tmp.
# TODO : use USE_EXPAND to add Expansion parsing. The most
#        important of these are set with sane defaults here,
#        too.
# No parameters accepted.
sub _read_make_defaults {

	# make.defaults are parsed first by portage:
	for my $dir(@_profiles) {
		if (-r "$dir/make.defaults") {
			my %env = _read_sh("$dir/make.defaults");
	
			# Note the conf state of the read flags:
			for my $flag ( keys %{$env{USE}}) {
				_add_temp($flag, "global");

				$env{USE}{$flag}
					and $_use_temp->{$flag}{global}{"default"} = 1
					or  $_use_temp->{$flag}{global}{"default"} = -1;
			}
			
			# Safe USE_EXPAND_HIDDEN if set. This is done because a user might
			# set it to "-*" in make.conf, which does not disable flags but only
			# the hidden status making them visible.
			_merge(\%_use_eh_safe, $env{USE_EXPAND_HIDDEN})
				if (defined($env{USE_EXPAND_HIDDEN}));
		}
	} ## End of reading make.defaults

	return;
}


# read all found make.globals and merge their settings into %environment. This
# is done to get the final "PORTDIR" and "USE_ORDER"
# No parameters accepted
sub _read_make_globals {
	for my $dir(@_profiles, "${_EPREFIX}/usr/share/portage/config") {
		_read_sh("$dir/make.globals");
	}
	return;
}


# read all found package.use files and merge their values into env, adding flag
# parameters to $_use_tmp.
# No parameters accepted.
sub _read_package_use
{
	for my $dir(@_profiles, "${_EPREFIX}/etc/portage") {
		my $tgt = $dir eq "${_EPREFIX}/etc/portage" ? "pkguse" : "package";
		for(_noncomments("$dir/package.use") ) {
			my($pkg, @flags) = split;
			
			for my $flag (@flags) {
				my $state = $flag =~ s/^-// || 0;

				_add_temp($flag, "local");
				_add_temp($flag, $pkg);

				if ($state) {
					$_use_temp->{$flag}{"local"}{$pkg}{$tgt} = -1; ## explicitly disabled
				} else {
					$_use_temp->{$flag}{"local"}{$pkg}{$tgt} =  1; ## explicitly enabled
				}
			}
		}
	} ## End of reading package.use

	return;
}


# Analyze EPREFIX/var/db/pkg and analyze all installed packages. The contents
# of the file IUSE are used to enrich the information of the {default} part and
# to determine which packages are installed.
sub _read_packages {
	my $pkgdir = undef;
	opendir($pkgdir, "${_EPREFIX}/var/db/pkg")
		or die "Couldn't read ${_EPREFIX}/var/db/pkg\n";
		
	# loop through all categories in pkgdir
	while(my $cat = readdir $pkgdir) {
		next if $cat eq '.' or $cat eq '..';
		my $catdir = undef;
		opendir($catdir, "${_EPREFIX}/var/db/pkg/$cat")
			or next;

		# loop through all openable directories in cat
		while(my $pkg = readdir $catdir) {
			next if $pkg eq '.' or $pkg eq '..';
			my @iuse = ();
			
			# Load IUSE to learn which use flags the package in this version knows
			my $fiuse = "${_EPREFIX}/var/db/pkg/$cat/$pkg/IUSE";
			if(open my $use, '<', $fiuse) {
				local $/;
				@iuse = split ' ', <$use>;
				close $use;
			}

			# could be shortened, but make sure not to strip off part of the name
			$pkg =~ s/-\d+(?:\.\d+)*\w?(?:_(?:alpha|beta|pre|rc|p)\d*)*(?:-r\d+)?$//;
			$pkg = $cat . "/" . $pkg;

			# Now save the knowledge gained (if any) in $_use_temp:
			for my $flag (@iuse) {
				my $eState = $flag =~ s/^\+// || 0; 
				my $dState = $flag =~ s/^-// || 0; 

				_add_temp($flag, "global");
				_add_temp($flag, $pkg);

				$_use_temp->{$flag}{"local"}{$pkg}{"default"} = $eState ? 1 : $dState ? -1 : 0;
				$_use_temp->{$flag}{"local"}{$pkg}{installed} = 1;
				$_use_temp->{$flag}{global}{installed} = 1;
			} ## End of looping IUSE
			
		}
		closedir $catdir;
	}
	closedir $pkgdir;
	return;
}


# reads the given file and parses it for key=value pairs.
# "source" entries are added to the file and parsed as well. The results of the
# parsing are merged into %environment.
# Parameter 1: The path of the file to parse.
# In a non-scalar context the function returns the found values.
sub _read_sh {
	my ($fname) = @_;
	my $BLANK = qr{(?:[ \n\t]+|#.*)+};         # whitespace and comments
	my $IDENT = qr{([^ \\\n\t'"{}=#]+)};       # identifiers
	my $ASSIG = qr{=};                         # assignment operator
	my $UQVAL = qr{((?:          # guard to extend the character limit
		[^ \\\n\t'"#]{1,32766} | # max 32766 of regular characters or
		\\.                      # one escaped character
		){1,32766}               # extend by 32766 sub matches
	)}sx;                                      # unquoted value
	my $SQVAL = qr{'((?:         # guard to extend the character limit
		[^']{0,32766}            # 0 to 32766 characters ...
		){0,32766}               # ... up to 32766 times
	)'}x;                                      # singlequoted value
	my $DQVAL = qr{"((?:         # guard to extend the character limit
		[^\\"]{0,32766}        | # max 32766 non-escaped characters or
		\\.                      # one escaped character
		){0,32766}               # extend by up to 32766 sub matches
	)"}sx;                                     # doublequoted value

	my %env;
	if(open my $file, '<', $fname) {
		{ local $/; $_ = <$file> }
		close $file;
		eval {
			for(;;) {
				/\G$BLANK/gc;
				last if ((pos || 0) == (length || 0));
				/\G$IDENT/gc or die "Empty file detected, no identifier found.";
				my $name = $1;
				/\G$BLANK/gc;
				if($name ne 'source') {
				/\G$ASSIG/gc or die "Bare keyword $name (pos " . pos . ") detected.";
				/\G$BLANK/gc;
				}
				pos == length and die "Bumped into unexpected EOF after $name.";
				my $value = '';
				for(;;) {
					if(/\G$UQVAL/gc || /\G$DQVAL/gc) {
						my $addvalue = $1;
						$addvalue =~ s[
							\\\n       | # backslash-newline
							\\(.)      | # other escaped characters
							\$(\{)?      # $
							$IDENT       # followed by an identifier
							(?(2)})      # optionally enclosed in braces
						][
							defined $3 ? $env{$3} || '' : # replace envvars
							defined $1 ? $1             : # unescape escaped characters
							             ''               # delete backslash-newlines
						]gex;
						$value .= $addvalue
					} elsif(/\G$SQVAL/gc) {
						$value .= $1
					} else {
						last
					}
				}
				if($name eq 'source') {
					open my $f, '<', $value or die "Unable to open $value\n$!\n";
					my $pos = pos;
					substr($_, pos, 0) = do {
						local $/;
						my $text = <$f>;
						defined $text or die "Error parsing $value";
						$text;
					};
					pos = $pos;
					close $f or die "Unable to close $value\n$!\n";
				} else {
				$env{$name} = $value;
				}
			}
		};
		defined($@) and length($@) and chomp $@
			and die "Parse error in $fname\n - Error: \"$@\"\n";
	}
	_merge_env(\%env);
	return %env if wantarray;
	return;
}


# read all enforced flags from all found use.force and package.use.force files.
# Save the found masks in %use_flags.
# No parameters accepted.
sub _read_use_force {
	for my $dir(@_profiles) {
		if (-r "$dir/use.force") {
			# use.force can enforce and mask specific flags
			for my $flag (_noncomments("$dir/use.force") ) {
				my $state = $flag =~ s/^-// || 0;
				
				_add_temp($flag, "global");
	
				$_use_temp->{$flag}{global}{masked} = !$state;
				$_use_temp->{$flag}{global}{forced} = !$state;
			}
		} ## End of having a use.force file
		
		if (-r "$dir/package.use.force") {
			# package.use.force can enforce or unforce flags per package
			for(_noncomments("$dir/package.use.force") ) {
				my($pkg, @flags) = split;
				for my $flag (@flags) {
					my $state = $flag =~ s/^-// || 0;
	
					_add_temp($flag, "global");
					_add_temp($flag, $pkg);
	
					if ($state) {
						$_use_temp->{$flag}{"local"}{$pkg}{masked} = -1; ## explicitly unmasked and
						$_use_temp->{$flag}{"local"}{$pkg}{forced} = -1; ## explicitly unforced
					} else {
						$_use_temp->{$flag}{"local"}{$pkg}{masked} =  1; ## explicitly masked and
						$_use_temp->{$flag}{"local"}{$pkg}{forced} =  1; ## explicitly enforced
					}
				}
			}
		} ## End of having a package.use.force file
	} ## End of looping through the profiles
	return;
}


# read all masked flags from all found use.mask and package.use.mask files.
# Save the found masks in %use_flags.
# No parameters accepted.
sub _read_use_mask {
	for my $dir(@_profiles) {
		if (-r "$dir/use.mask") {
			# use.mask can enable or disable masks
			for my $flag (_noncomments("$dir/use.mask") ) {
				my $state = $flag =~ s/^-// || 0;

				_add_temp($flag, "global");
	
				$_use_temp->{$flag}{global}{masked} = !$state;
			}
		} ## End of having a use.mask file
		
		if (-r "$dir/package.use.mask") {
		# package.use.mask can enable or disable masks per package
			for(_noncomments("$dir/package.use.mask") ) {
				my($pkg, @flags) = split;
				for my $flag (@flags) {
					my $state = $flag =~ s/^-// || 0;
	
					_add_temp($flag, "global");
					_add_temp($flag, $pkg);
	
					$state and $_use_temp->{$flag}{"local"}{$pkg}{masked} = -1; ## explicitly unmasked
					$state  or $_use_temp->{$flag}{"local"}{$pkg}{masked} =  1; ## explicitly masked
				}
			}
		} ## End of having a package.use.mask file
	} ## End of looping through the profiles
	return;
}


# TODO : Remove this function once the usage of the USE_EXPAND
#        values is implemented.
# For now all use flags that are expanded are removed. They are not
# set in USE="foo" but in their respective values like APACHE2_MODULES="foo"
#
# Note: the values from base/make.defaults are: (but there might be more)
# USE_EXPAND="APACHE2_MODULES APACHE2_MPMS CALLIGRA_FEATURES ENLIGHTENMENT_MODULES 
#  FOO2ZJS_DEVICES MISDN_CARDS FRITZCAPI_CARDS FCDSL_CARDS VIDEO_CARDS DVB_CARDS 
#  LIRC_DEVICES INPUT_DEVICES LINGUAS USERLAND KERNEL ELIBC CROSSCOMPILE_OPTS 
#  ALSA_CARDS ALSA_PCM_PLUGINS LCD_DEVICES CAMERAS NETBEANS_MODULES QEMU_SOFTMMU_TARGETS 
#  QEMU_USER_TARGETS SANE_BACKENDS RUBY_TARGETS PHP_TARGETS NGINX_MODULES_HTTP 
#  NGINX_MODULES_MAIL XFCE_PLUGINS XTABLES_ADDONS GPSD_PROTOCOLS COLLECTD_PLUGINS 
#  DRACUT_MODULES OFED_DRIVERS GRUB_PLATFORMS FFTOOLS PYTHON_TARGETS CURL_SSL 
#  OPENMPI_FABRICS OPENMPI_RM OPENMPI_OFED_FEATURES LIBREOFFICE_EXTENSIONS
#  VOICEMAIL_STORAGE PYTHON_SINGLE_TARGET ABI_X86"
#
# And the USE_EXPAND variables whose contents are not shown in package manager output.
# USE_EXPAND_HIDDEN="USERLAND KERNEL ELIBC CROSSCOMPILE_OPTS ABI_X86"
#
# Note2: It might be a good idea to leave this function and just reduce it to kill
#        USE_EXPAND_HIDDEN flags, as they must not be seen anyway.
#
# Note3: It can happen, that a user sets USE_EXPAND_HIDDEN to "-*" - which then moves
#        all entries to MOVE_EXPAND making them visible.
sub _remove_expands {

	my $expands = $_environment{USE_EXPAND} || {};
	my $hidden  = $_environment{USE_EXPAND_HIDDEN} || {};

	# If USE_EXPAND_HIDDEN is set to "-*", the safed flags have to be merged
	# into USE_EXPAND
	if (defined($hidden->{"*"})) {
		_merge($expands, \%_use_eh_safe);
		$hidden = {};
	}

	for my $key (map {my $x=lc($_)."_"; $x } keys %$expands) {
		for my $flag (keys %$_use_temp) {
			if ($flag =~ /^$key/ ) {
				delete($_use_temp->{$flag});
			} 
		}
	} ## Done looping USE_EXPAND

	for my $key (map {my $x=lc($_)."_"; $x } keys %$hidden) {
		for my $flag (keys %$_use_temp) {
			if ($flag =~ /^$key/ ) {
				delete($_use_temp->{$flag});
			} 
		}
	} ## Done looping USE_EXPAND_HIDDEN

	return;
}

1;
