Merge remote-tracking branch 'origin/master' into prefix

Signed-off-by: Fabian Groffen <grobian@gentoo.org>
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..62b0edc
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,8 @@
+In addition to the the long list of regular portage contributors, these
+folks (in no particular order) have put their hard work into this.
+
+Kito Dietrich           <kito at ???>
+Emanuele Giaquinta      <emanuele.giaquinta at gmail.com>
+Fabian Groffen          <grobian at gentoo.org>
+Brian Harring           <ferringb at gmail.com>
+Michael Haubenwallner   <haubi at gentoo.org>
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..9bcfa00
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,8 @@
+SHELL = @PORTAGE_BASH@
+
+SUBDIRS = man bin lib cnf
+
+AUTOMAKE_OPTIONS = dist-bzip2 no-dist-gzip
+
+MAINTAINERCLEANFILES = \
+	Makefile.in config.guess config.sub configure ltmain.sh aclocal.m4
diff --git a/README.md b/README.md
index 481d12b..5699b1b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,3 @@
-[![CI](https://github.com/gentoo/portage/actions/workflows/ci.yml/badge.svg)](https://github.com/gentoo/portage/actions/workflows/ci.yml)
-
 About Portage
 =============
 
@@ -8,6 +6,13 @@
 the behaviour of Portage so that ebuild repositories can be used by
 other package managers.
 
+This is the prefix branch of portage, a branch that deals with portage
+setup as packagemanager for a given offset in the filesystem running
+with user privileges.
+
+If you are not looking for something Gentoo Prefix like, then this
+is not the right place.
+
 Contributing
 ============
 
diff --git a/acinclude.m4 b/acinclude.m4
new file mode 100644
index 0000000..1d9ccf5
--- /dev/null
+++ b/acinclude.m4
@@ -0,0 +1,83 @@
+dnl acinclude.m4 generated automatically by ac-archive's acinclude 0.5.63
+
+dnl Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl This program is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+dnl PARTICULAR PURPOSE.
+
+dnl ______  ______
+
+dnl GENTOO_PATH_PYTHON([minimum-version], [path])
+dnl author: Fabian Groffen <grobian a gentoo.org>
+AC_DEFUN([GENTOO_PATH_PYTHON],
+[
+  AC_PATH_PROG([PREFIX_PORTAGE_PYTHON], [python], no, $2)
+
+  dnl is is there at all?
+  if test "$PREFIX_PORTAGE_PYTHON" = "no" ; then
+    AC_MSG_ERROR([no python found in your path])
+  fi
+
+  dnl is it the version we want?
+  ver=`$PREFIX_PORTAGE_PYTHON -c 'import sys; print(sys.version.split(" ")[[0]])'`
+  AC_MSG_CHECKING([whether $PREFIX_PORTAGE_PYTHON $ver >= $1])
+  cmp=`$PREFIX_PORTAGE_PYTHON -c 'import sys; print(sys.version.split(" ")[[0]] >= "$1")'`
+  if test "$cmp" = "True" ; then
+    AC_MSG_RESULT([yes])
+  else
+    AC_MSG_ERROR([need at least version $1 of python])
+  fi
+])
+
+dnl GENTOO_PATH_XCU_ID([path])
+dnl author: Fabian Groffen <grobian a gentoo.org>
+dnl         based on the original work by
+dnl         Michael Haubenwallner <mhaubi at users dot sourceforge dot net>
+AC_DEFUN([GENTOO_PATH_XCU_ID],
+[
+  AC_PATH_PROG([XCU_ID], [id], no, $1)
+
+  dnl does it support all the bells and whistles we need?
+  AC_MSG_CHECKING([whether $XCU_ID is good enough])
+  for a in '' '-u' '-g' ; do
+    if ! "$XCU_ID" $a >/dev/null 2>&1 ; then
+      XCU_ID=no
+      break
+    fi
+  done
+  if test "$XCU_ID" != "no" ; then
+    AC_MSG_RESULT([yes])
+  else
+    AC_MSG_ERROR([$XCU_ID doesn't understand $a])
+  fi
+])dnl
+
+dnl GENTOO_PATH_GNUPROG([variable], [prog-to-check-for], [path])
+dnl author: Fabian Groffen <grobian a gentoo.org>
+AC_DEFUN([GENTOO_PATH_GNUPROG],
+[
+  AC_PATH_PROG([$1], [$2], no, $3)
+
+  dnl is is there at all?
+  tool="`eval echo \$$1`"
+  if test "$tool" = "no" ; then
+    AC_MSG_ERROR([$1 was not found in your path])
+  fi
+
+  dnl is it a GNU version?
+  AC_MSG_CHECKING([whether $tool is GNU $2])
+  ver=`$tool --version 2>/dev/null | head -n 1`
+  case $ver in
+    *GNU*)
+      AC_MSG_RESULT([yes])
+    ;;
+    *)
+      AC_MSG_ERROR([no])
+    ;;
+  esac
+])
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..463753c
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env sh
+
+die() {
+	echo "!!! $*" > /dev/stderr
+	exit -1
+}
+
+#autoheader || { echo "failed autoheader"; exit 1; };
+aclocal || die "failed aclocal"
+[ "`type -t glibtoolize`" = "file" ] && alias libtoolize=glibtoolize
+libtoolize --automake -c -f || die "failed libtoolize"
+autoconf || die "failed autoconf"
+touch ChangeLog 
+automake -a -c || die "failed automake"
+
+echo "finished"
diff --git a/bin/Makefile.in b/bin/Makefile.in
new file mode 100644
index 0000000..e6dc672
--- /dev/null
+++ b/bin/Makefile.in
@@ -0,0 +1,84 @@
+SHELL = @PORTAGE_BASH@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+sysconfdir = @sysconfdir@
+libdir = @libdir@
+
+srcdir = @srcdir@
+top_builddir = @top_builddir@
+
+portageuser = @portageuser@
+portagegroup = @portagegroup@
+
+PORTAGE_BIN = @PORTAGE_BASE@/bin
+LN_S = @LN_S@
+INSTALL = @INSTALL@
+INSTALL_subst = $(top_builddir)/subst-install
+
+usr_binprogs = \
+	ebuild \
+	egencache \
+	emerge \
+	emerge-webrsync \
+	emirrordist \
+	portageq \
+	quickpkg
+
+usr_sbinprogs = \
+	archive-conf \
+	dispatch-conf \
+	emaint \
+	env-update \
+	etc-update \
+	fixpackages \
+	regenworld
+
+hprefixify_progs = \
+	etc-update
+
+all:
+
+install:
+	$(INSTALL) -d -m 755 -o "$(portageuser)" -g "$(portagegroup)" $(DESTDIR)$(PORTAGE_BIN)
+	( cd "$(srcdir)" && find . -type d ) | while read f ; do \
+		files=( ) ; \
+		shopt -s nullglob ; \
+		for t in "$(srcdir)/$${f}"/* ; do \
+			[[ -d $${t} ]] && continue ; \
+			[[ $${t} == */Makefile* ]] && continue ; \
+			files=( "$${files[@]}" "$${t}" ) ; \
+		done ; \
+		$(INSTALL) -d -m 755 \
+			-o "$(portageuser)" -g "$(portagegroup)" \
+			"$(DESTDIR)$(PORTAGE_BIN)/$${f}" && \
+		[[ $${files[0]} ]] || continue ; \
+		$(INSTALL_subst) -m 755 \
+			-o "$(portageuser)" -g "$(portagegroup)" \
+			-t "$(DESTDIR)$(PORTAGE_BIN)/$${f}" \
+			"$${files[@]}" ; \
+	done ; \
+	for f in $(hprefixify_progs) ; do \
+		$(INSTALL_subst) --hprefixify -m 755 \
+			-o "$(portageuser)" -g "$(portagegroup)" \
+			-t "$(DESTDIR)$(PORTAGE_BIN)" \
+			"$(srcdir)/$${f}" ; \
+	done
+	$(INSTALL) -d -m 755 -o "$(portageuser)" -g "$(portagegroup)" $(DESTDIR)$(prefix)/bin
+	cd $(DESTDIR)$(prefix)/bin \
+	; for p in $(usr_binprogs) \
+	; do test -f $(DESTDIR)$(PORTAGE_BIN)/$${p} \
+		 || { echo "$(DESTDIR)$(PORTAGE_BIN)/$${p} does not exist" ; exit 1 ; } \
+	   ; rm -f $(DESTDIR)$(prefix)/bin/$${p} \
+	   ; $(LN_S) ../lib/portage/bin/$${p} $${p} || exit 1 \
+	; done
+	$(INSTALL) -d -m 755 -o "$(portageuser)" -g "$(portagegroup)" $(DESTDIR)$(prefix)/sbin
+	cd $(DESTDIR)$(prefix)/sbin \
+	; for p in $(usr_sbinprogs) \
+	; do test -f $(DESTDIR)$(PORTAGE_BIN)/$${p} \
+		 || { echo "$(DESTDIR)$(PORTAGE_BIN)/$${p} does not exist" ; exit 1 ; } \
+	   ; rm -f $(DESTDIR)$(prefix)/sbin/$${p} \
+	   ; $(LN_S) ../lib/portage/bin/$${p} $${p} || exit 1 \
+	; done
+
+.PHONY: all install
diff --git a/bin/ebuild-helpers/dohtml b/bin/ebuild-helpers/dohtml
index 4d4efd4..b1636d6 100755
--- a/bin/ebuild-helpers/dohtml
+++ b/bin/ebuild-helpers/dohtml
@@ -16,8 +16,10 @@
 # Use safe cwd, avoiding unsafe import for bug #469338.
 export __PORTAGE_HELPER_CWD=${PWD}
 cd "${PORTAGE_PYM_PATH}" || die
+# BEGIN PREFIX LOCAL: use Prefix Python fallback
 PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \
-	"${PORTAGE_PYTHON:-/usr/bin/python}" "${PORTAGE_BIN_PATH}/dohtml.py" "$@"
+	"${PORTAGE_PYTHON:-@PREFIX_PORTAGE_PYTHON@}" "${PORTAGE_BIN_PATH}/dohtml.py" "$@"
+# END PREFIX LOCAL
 
 ret=$?
 # Restore cwd for display by __helpers_die
diff --git a/bin/ebuild-helpers/emake b/bin/ebuild-helpers/emake
index 3d8b31e..21da858 100755
--- a/bin/ebuild-helpers/emake
+++ b/bin/ebuild-helpers/emake
@@ -12,7 +12,8 @@
 source "${PORTAGE_BIN_PATH}"/isolated-functions.sh || exit 1
 
 cmd=(
-	${MAKE:-make} ${MAKEOPTS} "$@" ${EXTRA_EMAKE}
+    # PREFIX LOCAL: force SHELL to be set (don't use possibly ancient /bin/sh)
+	${MAKE:-make} SHELL="${BASH:-/bin/bash}" ${MAKEOPTS} "$@" ${EXTRA_EMAKE}
 )
 
 if [[ ${PORTAGE_QUIET} != 1 ]] ; then
diff --git a/bin/ebuild-pyhelper b/bin/ebuild-pyhelper
index 901277c..afed4d4 100755
--- a/bin/ebuild-pyhelper
+++ b/bin/ebuild-pyhelper
@@ -13,8 +13,10 @@
 cd "${PORTAGE_PYM_PATH}" || exit 1
 for path in "${PORTAGE_BIN_PATH}/${0##*/}"{.py,}; do
 	if [[ -x "${path}" ]]; then
+	    # BEGIN PREFIX LOCAL: use Prefix Python fallback
 		PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \
-			exec "${PORTAGE_PYTHON:-/usr/bin/python}" "${path}" "$@"
+			exec "${PORTAGE_PYTHON:-@PREFIX_PORTAGE_PYTHON@}" "${path}" "$@"
+		# END PREFIX LOCAL
 	fi
 done
 echo "File not found: ${path}" >&2
diff --git a/bin/emerge-webrsync b/bin/emerge-webrsync
index 99da055..9959b6a 100755
--- a/bin/emerge-webrsync
+++ b/bin/emerge-webrsync
@@ -78,14 +78,22 @@
 portageq=$(PATH="${BASH_SOURCE[0]%/*}:${PATH}" type -P portageq)
 [[ -n ${portageq} ]] || die "could not find 'portageq'; aborting"
 
+# PREFIX LOCAL: retrieve PORTAGE_USER/PORTAGE_GROUP
 eval "$("${portageq}" envvar -v DISTDIR EPREFIX FEATURES \
 	FETCHCOMMAND GENTOO_MIRRORS \
 	PORTAGE_BIN_PATH PORTAGE_CONFIGROOT PORTAGE_GPG_DIR \
 	PORTAGE_NICENESS PORTAGE_REPOSITORIES PORTAGE_RSYNC_EXTRA_OPTS \
 	PORTAGE_RSYNC_OPTS PORTAGE_TEMP_GPG_DIR PORTAGE_TMPDIR \
-	USERLAND http_proxy https_proxy ftp_proxy)"
+	USERLAND http_proxy ftp_proxy \
+	USERLAND http_proxy https_proxy ftp_proxy
+	PORTAGE_USER PORTAGE_GROUP)"
 export http_proxy https_proxy ftp_proxy
 
+# PREFIX LOCAL: use Prefix servers, just because we want this and infra
+# can't support us yet
+GENTOO_MIRRORS="http://rsync.prefix.bitzolder.nl"
+# END PREFIX LOCAL
+
 source "${PORTAGE_BIN_PATH}"/isolated-functions.sh || exit 1
 
 repo_name=gentoo
@@ -410,7 +418,9 @@
 
 	[[ ${PORTAGE_QUIET} -eq 1 ]] || einfo "Syncing local repository ..."
 
-	local ownership="portage:portage"
+	# PREFIX LOCAL: use PORTAGE_USER and PORTAGE_GROUP
+	local ownership="${PORTAGE_USER:-portage}:${PORTAGE_GROUP:-portage}"
+	# END PREFIX LOCAL
 	if has usersync ${FEATURES} ; then
 		case "${USERLAND}" in
 			BSD)
diff --git a/bin/install-qa-check.d/05prefix b/bin/install-qa-check.d/05prefix
index edbd6fa..229daff 100644
--- a/bin/install-qa-check.d/05prefix
+++ b/bin/install-qa-check.d/05prefix
@@ -34,9 +34,15 @@
 	# check shebangs, bug #282539
 	rm -f "${T}"/non-prefix-shebangs-errs
 	local WHITELIST=" /usr/bin/env "
+	# BEGIN PREFIX LOCAL: pull canonicalize call out of loop
+	# shebang can be an absolutised path, bug #342929
+	local eprefix=$(canonicalize ${EPREFIX})
+	# END PREFIX LOCAL
 	# this is hell expensive, but how else?
+	# PREFIX LOCAL: use grep -I to skip binary files, allow POSIX space
+	#               between # and !
 	find "${ED}" -executable \! -type d -print0 \
-			| xargs -0 grep -H -n -m1 "^#!" \
+			| xargs -0 grep -H -n -m1 -I '^# \?!' \
 			| while read f ;
 	do
 		local fn=${f%%:*}
@@ -49,9 +55,7 @@
 		line=( ${line#"#!"} )
 		IFS=${oldIFS}
 		[[ ${WHITELIST} == *" ${line[0]} "* ]] && continue
-		local fp=${fn#${D}} ; fp=/${fp%/*}
-		# line[0] can be an absolutised path, bug #342929
-		local eprefix=$(canonicalize ${EPREFIX})
+		local fp=${fn#${D}/} ; fp=/${fp%/*}
 		local rf=${fn}
 		# in case we deal with a symlink, make sure we don't replace it
 		# with a real file (sed -i does that)
diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh
index 06be030..47e18cc 100644
--- a/bin/isolated-functions.sh
+++ b/bin/isolated-functions.sh
@@ -450,16 +450,24 @@
 fi
 
 
-if [[ -z ${USERLAND} ]] ; then
-	case $(uname -s) in
-	*BSD|DragonFly)
-		export USERLAND="BSD"
-		;;
-	*)
-		export USERLAND="GNU"
-		;;
-	esac
-fi
+# BEGIN PREFIX LOCAL
+# In Prefix every platform has USERLAND=GNU, even FreeBSD.  Since I
+# don't know how to reliably "figure out" we are in a Prefix instance of
+# portage here, I for now disable this check, and hardcode it to GNU.
+# Somehow it appears stange to me that this code is in this file,
+# non-ebuilds/eclasses should never rely on USERLAND and XARGS, don't they?
+#if [[ -z ${USERLAND} ]] ; then
+#	case $(uname -s) in
+#	*BSD|DragonFly)
+#		export USERLAND="BSD"
+#		;;
+#	*)
+#		export USERLAND="GNU"
+#		;;
+#	esac
+#fi
+[[ -z ${USERLAND} ]] && USERLAND="GNU"
+# END PREFIX LOCAL
 
 if [[ -z ${XARGS} ]] ; then
 	case ${USERLAND} in
@@ -658,7 +666,8 @@
 		printf '%s\n' "${@}" >> "${T}/eclass-debug.log"
 
 		# Let the portage user own/write to this file
-		chgrp "${PORTAGE_GRPNAME:-portage}" "${T}/eclass-debug.log"
+		# PREFIX LOCAL: fallback to configured group
+		chgrp "${PORTAGE_GRPNAME:-${PORTAGE_GROUP}}" "${T}/eclass-debug.log"
 		chmod g+w "${T}/eclass-debug.log"
 	fi
 }
diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh
index 4ce3acb..47ee729 100755
--- a/bin/misc-functions.sh
+++ b/bin/misc-functions.sh
@@ -162,6 +162,31 @@
 		mtree -U -e -p "${ED}" -k flags < "${T}/bsdflags.mtree" &> /dev/null
 	fi
 
+	# PREFIX LOCAL:
+	# anything outside the prefix should be caught by the Prefix QA
+	# check, so if there's nothing in ED, we skip searching for QA
+	# checks there, the specific QA funcs can hence rely on ED existing
+	if [[ -d ${ED} ]] ; then
+		case ${CHOST} in
+			*-darwin*)
+				# Mach-O platforms (NeXT, Darwin, OSX/macOS)
+				install_qa_check_macho
+			;;
+			*)
+				# because this is the majority: ELF platforms (Linux,
+				# Solaris, *BSD, IRIX, etc.)
+				install_qa_check_elf
+			;;
+		esac
+	fi
+
+	# this is basically here such that the diff with trunk remains just
+	# offsetted and not out of order
+	install_qa_check_misc
+	# END PREFIX LOCAL
+}
+
+install_qa_check_elf() {
 	# Create NEEDED.ELF.2 regardless of RESTRICT=binchecks, since this info is
 	# too useful not to have (it's required for things like preserve-libs), and
 	# it's tempting for ebuild authors to set RESTRICT=binchecks for packages
@@ -240,7 +265,9 @@
 			fi
 		fi
 	fi
+}
 
+install_qa_check_misc() {
 	# If binpkg-dostrip is enabled, apply stripping before creating
 	# the binary package.
 	# Note: disabling it won't help with packages calling prepstrip directly.
@@ -257,6 +284,152 @@
 	fi
 }
 
+install_qa_check_macho() {
+	if ! has binchecks ${RESTRICT} ; then
+		# on Darwin, dynamic libraries are called .dylibs instead of
+		# .sos.  In addition the version component is before the
+		# extension, not after it.  Check for this, and *only* warn
+		# about it.  Some packages do ship .so files on Darwin and make
+		# it work (ugly!).
+		rm -f "${T}/mach-o.check"
+		find ${ED%/} -name "*.so" -or -name "*.so.*" | \
+		while read i ; do
+			[[ $(file $i) == *"Mach-O"* ]] && \
+				echo "${i#${D}}" >> "${T}/mach-o.check"
+		done
+		if [[ -f ${T}/mach-o.check ]] ; then
+			f=$(< "${T}/mach-o.check")
+			__vecho -ne '\a\n'
+			eqawarn "QA Notice: Found .so dynamic libraries on Darwin:"
+			eqawarn "    ${f//$'\n'/\n    }"
+		fi
+		rm -f "${T}/mach-o.check"
+
+		# The naming for dynamic libraries is different on Darwin; the
+		# version component is before the extention, instead of after
+		# it, as with .sos.  Again, make this a warning only.
+		rm -f "${T}/mach-o.check"
+		find ${ED%/} -name "*.dylib.*" | \
+		while read i ; do
+			echo "${i#${D}}" >> "${T}/mach-o.check"
+		done
+		if [[ -f "${T}/mach-o.check" ]] ; then
+			f=$(< "${T}/mach-o.check")
+			__vecho -ne '\a\n'
+			eqawarn "QA Notice: Found wrongly named dynamic libraries on Darwin:"
+			eqawarn "    ${f// /\n    }"
+		fi
+		rm -f "${T}/mach-o.check"
+	fi
+
+	install_name_is_relative() {
+		case $1 in
+			"@executable_path/"*)  return 0  ;;
+			"@loader_path"/*)      return 0  ;;
+			"@rpath/"*)            return 0  ;;
+			*)                     return 1  ;;
+		esac
+	}
+
+	# While we generate the NEEDED files, check that we don't get kernel
+	# traps at runtime because of broken install_names on Darwin.
+	rm -f "${T}"/.install_name_check_failed
+	scanmacho -qyRF '%a;%p;%S;%n' "${D}" | { while IFS= read l ; do
+		arch=${l%%;*}; l=${l#*;}
+		obj="/${l%%;*}"; l=${l#*;}
+		install_name=${l%%;*}; l=${l#*;}
+		needed=${l%%;*}; l=${l#*;}
+
+		ignore=
+		qa_var="QA_IGNORE_INSTALL_NAME_FILES_${ARCH/-/_}"
+		eval "[[ -n \${!qa_var} ]] &&
+			QA_IGNORE_INSTALL_NAME_FILES=(\"\${${qa_var}[@]}\")"
+
+		if [[ ${#QA_IGNORE_INSTALL_NAME_FILES[@]} -gt 1 ]] ; then
+			for x in "${QA_IGNORE_INSTALL_NAME_FILES[@]}" ; do
+				[[ ${obj##*/} == ${x} ]] && \
+					ignore=true
+			done
+		else
+			local shopts=$-
+			set -o noglob
+			for x in ${QA_IGNORE_INSTALL_NAME_FILES} ; do
+				[[ ${obj##*/} == ${x} ]] && \
+					ignore=true
+			done
+			set +o noglob
+			set -${shopts}
+		fi
+
+		# See if the self-reference install_name points to an existing
+		# and to be installed file.  This usually is a symlink for the
+		# major version.
+		if install_name_is_relative ${install_name} ; then
+			# try to locate the library in the installed image
+			local inpath=${install_name#@*/}
+			local libl
+			for libl in $(find "${ED}" -name "${inpath##*/}") ; do
+				if [[ ${libl} == */${inpath} ]] ; then
+					install_name=/${libl#${D}}
+					break
+				fi
+			done
+		fi
+		if [[ ! -e ${D}${install_name} ]] ; then
+			eqawarn "QA Notice: invalid self-reference install_name ${install_name} in ${obj}"
+			# remember we are in an implicit subshell, that's
+			# why we touch a file here ... ideally we should be
+			# able to die correctly/nicely here
+			[[ -z ${ignore} ]] && touch "${T}"/.install_name_check_failed
+		fi
+
+		# this is ugly, paths with spaces won't work
+		for lib in ${needed//,/ } ; do
+			if [[ ${lib} == ${D}* ]] ; then
+				eqawarn "QA Notice: install_name references \${D}: ${lib} in ${obj}"
+				[[ -z ${ignore} ]] && touch "${T}"/.install_name_check_failed
+			elif [[ ${lib} == ${S}* ]] ; then
+				eqawarn "QA Notice: install_name references \${S}: ${lib} in ${obj}"
+				[[ -z ${ignore} ]] && touch "${T}"/.install_name_check_failed
+			elif ! install_name_is_relative ${lib} ; then
+				local isok=no
+				if [[ -e ${lib} || -e ${D}${lib} ]] ; then
+					isok=yes  # yay, we're ok
+				elif [[ -e "${EROOT}"/MacOSX.sdk ]] ; then
+					# trigger SDK mode, at least since Big Sur (11.0)
+					# there are no libraries in /usr/lib any more, but
+					# there are references too it (some library cache is
+					# in place), yet we can validate it sort of is sane
+					# by looking at the SDK metacaches, TAPI-files, .tbd
+					# text versions of libraries, so just look there
+					local tbd=${lib%.*}.tbd
+					if [[ -e ${EROOT}/MacOSX.sdk/${lib%.*}.tbd ]] ; then
+						isok=yes  # it's in the SDK, so ok
+					elif [[ -e ${EROOT}/MacOSX.sdk/${lib}.tbd ]] ; then
+						isok=yes  # this happens in case of Framework refs
+					fi
+				fi
+				if [[ ${isok} == no ]] ; then
+					eqawarn "QA Notice: invalid reference to ${lib} in ${obj}"
+					[[ -z ${ignore} ]] && \
+						touch "${T}"/.install_name_check_failed
+				fi
+			fi
+		done
+
+		# backwards compatibility
+		echo "${obj} ${needed}" >> "${PORTAGE_BUILDDIR}"/build-info/NEEDED
+		# what we use
+		echo "${arch};${obj};${install_name};${needed}" >> "${PORTAGE_BUILDDIR}"/build-info/NEEDED.MACHO.3
+	done }
+	if [[ -f ${T}/.install_name_check_failed ]] ; then
+		# secret switch "allow_broken_install_names" to get
+		# around this and install broken crap (not a good idea)
+		has allow_broken_install_names ${FEATURES} || \
+			die "invalid install_name found, your application or library will crash at runtime"
+	fi
+}
+
 __dyn_instprep() {
 	if [[ -e ${PORTAGE_BUILDDIR}/.instprepped ]] ; then
 		__vecho ">>> It appears that '${PF}' is already instprepped; skipping."
@@ -529,9 +702,11 @@
 			${PORTAGE_COMPRESSION_COMMAND} > "${PORTAGE_BINPKG_TMPFILE}"
 		assert "failed to pack binary package: '${PORTAGE_BINPKG_TMPFILE}'"
 
+		# BEGIN PREFIX LOCAL: use PREFIX_PORTAGE_PYTHON fallback
 		PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \
-			"${PORTAGE_PYTHON:-/usr/bin/python}" "${PORTAGE_BIN_PATH}"/xpak-helper.py recompose \
+			"${PORTAGE_PYTHON:-@PREFIX_PORTAGE_PYTHON@}" "${PORTAGE_BIN_PATH}"/xpak-helper.py recompose \
 			"${PORTAGE_BINPKG_TMPFILE}" "${PORTAGE_BUILDDIR}/build-info"
+		# END PREFIX LOCAL
 		if [[ $? -ne 0 ]]; then
 			rm -f "${PORTAGE_BINPKG_TMPFILE}"
 			die "Failed to append metadata to the tbz2 file"
@@ -550,9 +725,11 @@
 		__vecho ">>> Done."
 
 	elif [[ "${BINPKG_FORMAT}" == "gpkg" ]]; then
+		# BEGIN PREFIX LOCAL: use PREFIX_PORTAGE_PYTHON fallback
 		PYTHONPATH=${PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}} \
-			"${PORTAGE_PYTHON:-/usr/bin/python}" "${PORTAGE_BIN_PATH}"/gpkg-helper.py compress \
+			"${PORTAGE_PYTHON:-@PREFIX_PORTAGE_PYTHON@}" "${PORTAGE_BIN_PATH}"/gpkg-helper.py compress \
 			"${PF}${BUILD_ID:+-${BUILD_ID}}" "${PORTAGE_BINPKG_TMPFILE}" "${PORTAGE_BUILDDIR}/build-info" "${D}"
+		# END PREFIX LOCAL
 		if [[ $? -ne 0 ]]; then
 			rm -f "${PORTAGE_BINPKG_TMPFILE}"
 			die "Failed to create binpkg file"
diff --git a/bin/phase-functions.sh b/bin/phase-functions.sh
index ebcf5f2..31b02ce 100644
--- a/bin/phase-functions.sh
+++ b/bin/phase-functions.sh
@@ -146,7 +146,8 @@
 		fi
 	fi
 
-	"${PORTAGE_PYTHON:-/usr/bin/python}" "${PORTAGE_BIN_PATH}"/filter-bash-environment.py "${filtered_vars}" || die "filter-bash-environment.py failed"
+    # PREFIX LOCAL: use Prefix Python fallback
+	"${PORTAGE_PYTHON:-@PREFIX_PORTAGE_PYTHON@}" "${PORTAGE_BIN_PATH}"/filter-bash-environment.py "${filtered_vars}" || die "filter-bash-environment.py failed"
 }
 
 # @FUNCTION: __preprocess_ebuild_env
@@ -1110,8 +1111,8 @@
 		__save_ebuild_env | __filter_readonly_variables \
 			--filter-features > "${T}/environment"
 		assert "__save_ebuild_env failed"
-
-		chgrp "${PORTAGE_GRPNAME:-portage}" "${T}/environment"
+		# PREFIX LOCAL: use configured group
+		chgrp "${PORTAGE_GRPNAME:-${PORTAGE_GROUP}}" "${T}/environment"
 		chmod g+w "${T}/environment"
 	fi
 
diff --git a/bin/save-ebuild-env.sh b/bin/save-ebuild-env.sh
old mode 100644
new mode 100755
index 3a2560a..c3c83c9
--- a/bin/save-ebuild-env.sh
+++ b/bin/save-ebuild-env.sh
@@ -119,6 +119,10 @@
 	# user config variables
 	unset DOC_SYMLINKS_DIR INSTALL_MASK PKG_INSTALL_MASK
 
+	# PREFIX LOCAL: Prefix additions
+	unset EXTRA_PATH PORTAGE_GROUP PORTAGE_USER
+	# END PREFIX LOCAL
+
 	declare -p
 	declare -fp
 	if [[ ${BASH_VERSINFO[0]} == 3 ]]; then
diff --git a/cnf/Makefile.in b/cnf/Makefile.in
new file mode 100644
index 0000000..64d2fa7
--- /dev/null
+++ b/cnf/Makefile.in
@@ -0,0 +1,42 @@
+SHELL = @PORTAGE_BASH@
+
+prefix = @prefix@
+sysconfdir = @sysconfdir@
+datadir = @datadir@
+
+srcdir = @srcdir@
+top_builddir = @top_builddir@
+PORTAGE_CONF = $(datadir)/portage/config
+
+portageuser = @portageuser@
+portagegroup = @portagegroup@
+
+INSTALL = @INSTALL@
+INSTALL_subst = ${top_builddir}/subst-install --hprefixify
+LN_S = @LN_S@
+
+all:
+
+install:
+	$(INSTALL) -d -m 755 -o "$(portageuser)" -g "$(portagegroup)" $(DESTDIR)$(PORTAGE_CONF)
+	$(INSTALL_subst) \
+		-o "$(portageuser)" -g "$(portagegroup)" \
+		-t "$(DESTDIR)$(PORTAGE_CONF)" \
+		"$(srcdir)"/make.globals "$(srcdir)"/repos.conf
+	$(INSTALL) -d -m 755 -o "$(portageuser)" -g "$(portagegroup)" $(DESTDIR)$(PORTAGE_CONF)/sets
+	$(INSTALL_subst) \
+		-o "$(portageuser)" -g "$(portagegroup)" \
+		-t "$(DESTDIR)$(PORTAGE_CONF)/sets" \
+		"$(srcdir)"/sets/portage.conf
+	$(INSTALL_subst) \
+		-o "$(portageuser)" -g "$(portagegroup)" \
+		"$(srcdir)"/make.conf.example "$(DESTDIR)$(PORTAGE_CONF)"/make.conf.example
+	$(INSTALL) -d -m 755 -o "$(portageuser)" -g "$(portagegroup)" $(DESTDIR)$(sysconfdir)
+	$(INSTALL_subst) \
+		-o "$(portageuser)" -g "$(portagegroup)" \
+		-t "$(DESTDIR)$(sysconfdir)" \
+		"$(srcdir)"/dispatch-conf.conf \
+		"$(srcdir)"/etc-update.conf
+	( cd $(DESTDIR)$(sysconfdir) && rm -f make.globals && $(LN_S) $(DESTDIR)$(PORTAGE_CONF)/make.globals )
+
+.PHONY:	all install
diff --git a/cnf/make.conf.example b/cnf/make.conf.example
index 4375665..502f064 100644
--- a/cnf/make.conf.example
+++ b/cnf/make.conf.example
@@ -157,8 +157,8 @@
 #RESUMECOMMAND="wget -c -t 3 -T 60 --passive-ftp --limit-rate=200k -O \"\${DISTDIR}/\${FILE}\" \"\${URI}\""
 #
 # Lukemftp (BSD ftp):
-#FETCHCOMMAND="/usr/bin/lukemftp -s -a -o \"\${DISTDIR}/\${FILE}\" \"\${URI}\""
-#RESUMECOMMAND="/usr/bin/lukemftp -s -a -R -o \"\${DISTDIR}/\${FILE}\" \"\${URI}\""
+#FETCHCOMMAND="lukemftp -s -a -o \"\${DISTDIR}/\${FILE}\" \"\${URI}\""
+#RESUMECOMMAND="lukemftp -s -a -R -o \"\${DISTDIR}/\${FILE}\" \"\${URI}\""
 #
 # Portage uses GENTOO_MIRRORS to specify mirrors to use for source retrieval.
 # The list is a space separated list which is read left to right. If you use
@@ -246,6 +246,10 @@
 #     Instructions for setting up a local rsync server are available here:
 #     https://wiki.gentoo.org/wiki/Local_Mirror
 #
+# For Gentoo Prefix, use the following URL:
+#
+#   Default:       "rsync://rsync.prefix.bitzolder.nl/gentoo-portage-prefix
+#
 #SYNC="rsync://rsync.gentoo.org/gentoo-portage"
 #
 # PORTAGE_RSYNC_RETRIES sets the number of times portage will attempt to retrieve
diff --git a/cnf/make.globals b/cnf/make.globals
index 2bb7a65..a5931a1 100644
--- a/cnf/make.globals
+++ b/cnf/make.globals
@@ -130,12 +130,24 @@
 PORTAGE_LOGDIR_CLEAN="find \"\${PORTAGE_LOGDIR}\" -type f ! -name \"summary.log*\" -mtime +7 -delete"
 
 # Minimal CONFIG_PROTECT
+# NOTE: in Prefix, these are NOT prefixed on purpose, because the
+# profiles define them too
 CONFIG_PROTECT="/etc"
 CONFIG_PROTECT_MASK="/etc/env.d"
 
 # Disable auto-use
 USE_ORDER="env:pkg:conf:defaults:pkginternal:features:repo:env.d"
 
+# PREFIX LOCAL: additional vars set during install
+# Default portage user/group
+PORTAGE_USER='@portageuser@'
+PORTAGE_GROUP='@portagegroup@'
+PORTAGE_ROOT_USER='@rootuser@'
+
+# Default ownership of installed files.
+PORTAGE_INST_UID="@rootuid@"
+PORTAGE_INST_GID="@rootgid@"
+
 # Mode bits for ${WORKDIR} (see ebuild.5).
 PORTAGE_WORKDIR_MODE="0700"
 
@@ -143,9 +155,9 @@
 PORTAGE_ELOG_CLASSES="log warn error"
 PORTAGE_ELOG_SYSTEM="save_summary:log,warn,error,qa echo"
 
-PORTAGE_ELOG_MAILURI="root"
+PORTAGE_ELOG_MAILURI="@rootuser@"
 PORTAGE_ELOG_MAILSUBJECT="[portage] ebuild log for \${PACKAGE} on \${HOST}"
-PORTAGE_ELOG_MAILFROM="portage@localhost"
+PORTAGE_ELOG_MAILFROM="@portageuser@@localhost"
 
 # Signing command used by egencache
 PORTAGE_GPG_SIGNING_COMMAND="gpg --sign --digest-algo SHA256 --clearsign --yes --default-key \"\${PORTAGE_GPG_KEY}\" --homedir \"\${PORTAGE_GPG_DIR}\" \"\${FILE}\""
@@ -163,6 +175,20 @@
 	security.selinux system.nfs4_acl user.apache_handler
 	user.Beagle.* user.dublincore.* user.mime_encoding user.xdg.*"
 
+# Writeable paths for Mac OS X seatbelt sandbox
+#
+# If path ends in a slash (/), access will recursively be allowed to directory
+# contents (using a regex), not the directory itself. Without a slash, access
+# to the directory or file itself will be allowed (using a literal), so it can
+# be created, removed and changed. If both is needed, the directory needs to be
+# given twice, once with and once without the slash. Obviously this only makes
+# sense for directories, not files.
+#
+# An empty value for either variable will disable all restrictions on the
+# corresponding operation.
+MACOSSANDBOX_PATHS="/dev/fd/ /private/tmp/ /private/var/tmp/ @@PORTAGE_BUILDDIR@@/ @@PORTAGE_ACTUAL_DISTDIR@@/"
+MACOSSANDBOX_PATHS_CONTENT_ONLY="/dev/null /dev/dtracehelper /dev/tty /private/var/run/syslog"
+
 #            *****************************
 #            **  DO NOT EDIT THIS FILE  **
 # ***************************************************
diff --git a/cnf/repos.conf b/cnf/repos.conf
index f16fd35..3924461 100644
--- a/cnf/repos.conf
+++ b/cnf/repos.conf
@@ -1,10 +1,10 @@
 [DEFAULT]
-main-repo = gentoo
+main-repo = gentoo_prefix
 
-[gentoo]
+[gentoo_prefix]
 location = /var/db/repos/gentoo
 sync-type = rsync
-sync-uri = rsync://rsync.gentoo.org/gentoo-portage
+sync-uri = rsync://rsync.prefix.bitzolder.nl/gentoo-portage-prefix
 auto-sync = yes
 sync-rsync-verify-jobs = 1
 sync-rsync-verify-metamanifest = yes
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..12b669e
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,128 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_INIT([portage-prefix],[@version@],[prefix@gentoo.org])
+
+AC_PREREQ([2.71])
+
+case "${prefix}" in
+	'') 	AC_MSG_ERROR([bad value ${prefix} for --prefix, must not be empty]) ;;
+	*/)		AC_MSG_ERROR([bad value ${prefix} for --prefix, must not end with '/']) ;;
+	/*|NONE) ;;
+	*) 		AC_MSG_ERROR([bad value ${prefix} for --prefix, must start with /]) ;;
+esac
+
+AC_CANONICAL_BUILD
+AC_CANONICAL_HOST
+AC_CANONICAL_TARGET
+
+AM_INIT_AUTOMAKE
+
+dnl Checks for programs.
+dnl store cflags prior, otherwise it's not propagated.
+if test "x$CFLAGS" != "x"
+then
+	CFLAGS=$CFLAGS
+fi
+
+AC_PREFIX_DEFAULT([/usr])
+
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_EGREP
+
+GENTOO_PATH_XCU_ID()
+GENTOO_PATH_PYTHON([2.7])
+
+AC_PATH_PROG(PORTAGE_RM, [rm], no)
+AC_PATH_PROG(PORTAGE_MV, [mv], no)
+AC_PATH_PROG(PORTAGE_BASENAME, [basename], no)
+AC_PATH_PROG(PORTAGE_DIRNAME, [dirname], no)
+dnl avoid bash internal variable messing up things here
+GENTOO_PATH_GNUPROG(PORTAGE_BASH, [bash])
+GENTOO_PATH_GNUPROG(PORTAGE_SED, [sed])
+GENTOO_PATH_GNUPROG(PORTAGE_WGET, [wget])
+GENTOO_PATH_GNUPROG(PORTAGE_FIND, [find])
+GENTOO_PATH_GNUPROG(PORTAGE_XARGS, [xargs])
+GENTOO_PATH_GNUPROG(PORTAGE_GREP, [grep])
+
+AC_ARG_WITH(portage-user,
+AS_HELP_STRING([--with-portage-user=myuser],[use user 'myuser' as portage owner (default portage)]),
+[case "${withval}" in
+  ""|yes) AC_MSG_ERROR(bad value ${withval} for --with-portage-user);;
+  *) portageuser="${withval}";;
+esac],
+[portageuser="portage"])
+
+AC_ARG_WITH(portage-group,
+AS_HELP_STRING([--with-portage-group=mygroup],[use group 'mygroup' as portage users group (default portage)]),
+[case "${withval}" in
+  ""|yes) AC_MSG_ERROR(bad value ${withval} for --with-portage-group);;
+  *) portagegroup="${withval}";;
+esac],
+[portagegroup="portage"])
+
+AC_ARG_WITH(root-user,
+AS_HELP_STRING([--with-root-user=myuser],[uses 'myuser' as owner of installed files (default is portage-user)]),
+[case "${withval}" in
+  ""|yes) AC_MSG_ERROR(bad value ${withval} for --with-root-user);;
+  *) rootuser="${withval}";;
+esac],
+[rootuser="${portageuser}"])
+
+AC_MSG_CHECKING([for user id of ${rootuser}])
+dnl grab uid of rootuser
+rootuid=`${XCU_ID} -u "${rootuser}"`
+if test "x`echo ${rootuid} | ${EGREP} '^[[0-9]]+$'`" != "x"
+then
+	AC_MSG_RESULT([${rootuid}])
+else
+	AC_MSG_ERROR([error finding the user id of ${rootuser}])
+fi
+AC_MSG_CHECKING([for group id of ${rootuser}])
+rootgid=`${XCU_ID} -g "${rootuser}"`
+if test "x`echo ${rootgid} | ${EGREP} '^[[0-9]]+$'`" != "x"
+then
+	AC_MSG_RESULT([${rootgid}])
+else
+	AC_MSG_ERROR([error finding the group id of ${rootuser}])
+fi
+
+AC_ARG_WITH(offset-prefix, 
+AS_HELP_STRING([--with-offset-prefix],[specify the installation prefix for all packages, defaults to an empty string]),
+			   [PORTAGE_EPREFIX=$withval],
+			   [PORTAGE_EPREFIX=''])
+
+if test "x$PORTAGE_EPREFIX" != "x"
+then
+	PORTAGE_EPREFIX=`${PREFIX_PORTAGE_PYTHON} -c "import os; print(os.path.normpath('$PORTAGE_EPREFIX'))"`
+fi
+
+AC_SUBST(portageuser)
+AC_SUBST(portagegroup)
+AC_SUBST(rootuser)
+AC_SUBST(rootuid)
+AC_SUBST(rootgid)
+AC_SUBST(PORTAGE_EPREFIX)
+AC_SUBST(PORTAGE_BASE,['${exec_prefix}/lib/portage'])
+
+AC_SUBST(PORTAGE_RM)
+AC_SUBST(PORTAGE_MV)
+AC_SUBST(PORTAGE_BASENAME)
+AC_SUBST(PORTAGE_DIRNAME)
+AC_SUBST(PORTAGE_BASH)
+AC_SUBST(PORTAGE_SED)
+AC_SUBST(PORTAGE_WGET)
+AC_SUBST(PORTAGE_FIND)
+AC_SUBST(PORTAGE_XARGS)
+AC_SUBST(PORTAGE_GREP)
+
+AC_CONFIG_FILES([subst-install], [chmod +x subst-install])
+AC_CONFIG_FILES([
+				 Makefile
+				 man/Makefile
+				 bin/Makefile
+				 lib/Makefile
+				 cnf/Makefile
+])
+
+AC_OUTPUT
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 0000000..7d4193c
--- /dev/null
+++ b/lib/Makefile.in
@@ -0,0 +1,41 @@
+SHELL = @PORTAGE_BASH@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+sysconfdir = @sysconfdir@
+libdir = @libdir@
+PYTHON = @PREFIX_PORTAGE_PYTHON@
+
+srcdir=@srcdir@
+top_builddir=@top_builddir@
+
+portageuser = @portageuser@
+portagegroup = @portagegroup@
+
+PORTAGE_PYM = @PORTAGE_BASE@/lib
+INSTALL = @INSTALL@
+INSTALL_subst = ${top_builddir}/subst-install
+
+all:
+
+install:
+	$(INSTALL) -d -m 755 -o "$(portageuser)" -g "$(portagegroup)" $(DESTDIR)$(PORTAGE_PYM)
+	( cd "$(srcdir)" && find * -type d ) | while read f ; do \
+		files=( ) ; \
+		shopt -s nullglob ; \
+		for t in "$(srcdir)/$${f}"/* ; do \
+			[[ -d $${t} ]] && continue ; \
+			[[ $${t} == */Makefile* ]] && continue ; \
+			files=( "$${files[@]}" "$${t}" ) ; \
+		done ; \
+		$(INSTALL) -d -m 755 \
+			-o "$(portageuser)" -g "$(portagegroup)" \
+			"$(DESTDIR)$(PORTAGE_PYM)/$${f}" && \
+		[[ $${files[0]} ]] || continue ; \
+		$(INSTALL_subst) \
+			-o "$(portageuser)" -g "$(portagegroup)" \
+			-t "$(DESTDIR)$(PORTAGE_PYM)/$${f}" \
+			"$${files[@]}" \
+	; done
+
+.PHONY:	all install
diff --git a/lib/_emerge/EbuildPhase.py b/lib/_emerge/EbuildPhase.py
index b472803..9dbd79f 100644
--- a/lib/_emerge/EbuildPhase.py
+++ b/lib/_emerge/EbuildPhase.py
@@ -32,6 +32,8 @@
 from portage.util.futures.executor.fork import ForkExecutor
 from portage.exception import InvalidBinaryPackageFormat
 from portage.const import SUPPORTED_GENTOO_BINPKG_FORMATS
+# PREFIX LOCAL
+from portage.const import EPREFIX
 
 try:
     from portage.xml.metadata import MetaDataXML
@@ -636,6 +638,23 @@
             os.path.join(self.settings["PORTAGE_BUILDDIR"], "build-info"), all_provides
         )
 
+        # BEGIN PREFIX LOCAL
+        if EPREFIX != "" and unresolved:
+            # in prefix, consider the host libs for any unresolved libs,
+            # so we kill warnings about missing libc.so.1, etc.
+            for obj, libs in list(unresolved):
+                unresolved.remove((obj, libs))
+                libs=list(libs)
+                for lib in list(libs):
+                    for path in ['/lib64', '/lib/64', '/lib', \
+                            '/usr/lib64', '/usr/lib/64', '/usr/lib']:
+                        if os.path.exists(os.path.join(path, lib)):
+                            libs.remove(lib)
+                            break
+                if len(libs) > 0:
+                    unresolved.append((obj, tuple(libs)))
+        # END PREFIX LOCAL
+
         if unresolved:
             unresolved.sort()
             qa_msg = ["QA Notice: Unresolved soname dependencies:"]
diff --git a/lib/_emerge/Package.py b/lib/_emerge/Package.py
index 7901138..7d13897 100644
--- a/lib/_emerge/Package.py
+++ b/lib/_emerge/Package.py
@@ -86,6 +86,8 @@
         "SLOT",
         "USE",
         "_mtime_",
+        # PREFIX LOCAL
+        "EPREFIX",
     ]
 
     _dep_keys = ("BDEPEND", "DEPEND", "IDEPEND", "PDEPEND", "RDEPEND")
diff --git a/lib/_emerge/depgraph.py b/lib/_emerge/depgraph.py
index 6853ec4..63e7ad1 100644
--- a/lib/_emerge/depgraph.py
+++ b/lib/_emerge/depgraph.py
@@ -12207,6 +12207,18 @@
         if not pkgsettings._accept_chost(pkg.cpv, pkg._metadata):
             mreasons.append(_MaskReason("CHOST", f"CHOST: {pkg._metadata['CHOST']}"))
 
+    # BEGIN PREFIX LOCAL: check EPREFIX
+    eprefix = pkgsettings["EPREFIX"]
+    if len(eprefix.rstrip('/')) > 0 and pkg.built and not pkg.installed:
+        if not "EPREFIX" in pkg._metadata:
+            mreasons.append(_MaskReason("EPREFIX",
+                "missing EPREFIX"))
+        elif len(pkg._metadata["EPREFIX"].strip()) < len(eprefix):
+            mreasons.append(_MaskReason("EPREFIX",
+                "EPREFIX: '%s' too small" % \
+                    pkg._metadata["EPREFIX"]))
+    # END PREFIX LOCAL
+
     if pkg.invalid:
         for msgs in pkg.invalid.values():
             for msg in msgs:
diff --git a/lib/_emerge/emergelog.py b/lib/_emerge/emergelog.py
index c3c42d8..3d4452e 100644
--- a/lib/_emerge/emergelog.py
+++ b/lib/_emerge/emergelog.py
@@ -9,12 +9,15 @@
 from portage import _unicode_encode
 from portage.data import secpass
 from portage.output import xtermTitle
+# PREFIX LOCAL
+from portage.const import EPREFIX
 
 # We disable emergelog by default, since it's called from
 # dblink.merge() and we don't want that to trigger log writes
 # unless it's really called via emerge.
 _disable = True
-_emerge_log_dir = "/var/log"
+# PREFIX LOCAL: log inside Prefix
+_emerge_log_dir = EPREFIX + '/var/log'
 
 
 def emergelog(xterm_titles, mystr, short_msg=None):
diff --git a/lib/portage/__init__.py b/lib/portage/__init__.py
index aa81bdb..f097b65 100644
--- a/lib/portage/__init__.py
+++ b/lib/portage/__init__.py
@@ -163,6 +163,10 @@
         MISC_SH_BINARY,
         REPO_NAME_LOC,
         REPO_NAME_FILE,
+        # BEGIN PREFIX LOCAL
+        EPREFIX,
+        rootuid,
+        # END PREFIX LOCAL
     )
 
 except ImportError as e:
diff --git a/lib/portage/const.py b/lib/portage/const.py
index 2154213..1909199 100644
--- a/lib/portage/const.py
+++ b/lib/portage/const.py
@@ -2,6 +2,13 @@
 # Copyright 1998-2024 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2
 
+# BEGIN PREFIX LOCAL
+# ===========================================================================
+# autotool supplied constants.
+# ===========================================================================
+from portage.const_autotool import *
+# END PREFIX LOCAL
+
 import os
 
 from portage import installation
@@ -104,6 +111,20 @@
 MOVE_BINARY = f"{BINARY_PREFIX}/bin/mv"
 PRELINK_BINARY = f"{BINARY_PREFIX}/usr/sbin/prelink"
 
+# BEGIN PREFIX LOCAL: macOS sandbox
+MACOSSANDBOX_BINARY      = "/usr/bin/sandbox-exec"
+MACOSSANDBOX_PROFILE     = '''(version 1)
+(allow default)
+(deny file-write*)
+(allow file-write* file-write-setugid
+@@MACOSSANDBOX_PATHS@@)
+(allow file-write-data
+@@MACOSSANDBOX_PATHS_CONTENT_ONLY@@)'''
+
+PORTAGE_GROUPNAME        = portagegroup
+PORTAGE_USERNAME         = portageuser
+# END PREFIX LOCAL
+
 INVALID_ENV_FILE = "/etc/spork/is/not/valid/profile.env"
 MERGING_IDENTIFIER = "-MERGING-"
 REPO_NAME_FILE = "repo_name"
diff --git a/lib/portage/const_autotool.py b/lib/portage/const_autotool.py
new file mode 100644
index 0000000..ee54031
--- /dev/null
+++ b/lib/portage/const_autotool.py
@@ -0,0 +1,21 @@
+# Copyright: 2005-2009 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+# all vars that are to wind up in portage_const must have their name listed in __all__
+
+__all__ = ["EPREFIX", "SYSCONFDIR", "PORTAGE_BASE",
+		"portageuser", "portagegroup", "rootuser", "rootuid", "rootgid",
+		"PORTAGE_BASH", "PORTAGE_MV"]
+
+EPREFIX      = "@PORTAGE_EPREFIX@"
+SYSCONFDIR   = "@sysconfdir@"
+PORTAGE_BASE = "@PORTAGE_BASE@"
+
+portagegroup = "@portagegroup@"
+portageuser  = "@portageuser@"
+rootuser     = "@rootuser@"
+rootuid      = @rootuid@
+rootgid      = @rootgid@
+
+PORTAGE_BASH = "@PORTAGE_BASH@"
+PORTAGE_MV   = "@PORTAGE_MV@"
diff --git a/lib/portage/data.py b/lib/portage/data.py
index 4b9b74c..e57e672 100644
--- a/lib/portage/data.py
+++ b/lib/portage/data.py
@@ -6,6 +6,8 @@
 import os
 import platform
 import pwd
+# PREFIX LOCAL
+from portage.const import PORTAGE_GROUPNAME, PORTAGE_USERNAME, EPREFIX
 
 import portage
 from portage.localization import _
@@ -20,7 +22,8 @@
 
 ostype = platform.system()
 userland = "GNU"
-if ostype == "DragonFly" or ostype.endswith("BSD"):
+# PREFIX LOCAL: Prefix always has USERLAND=GNU
+if EPREFIX == "" and (ostype == "DragonFly" or ostype.endswith("BSD")):
     userland = "BSD"
 
 lchown = getattr(os, "lchown", None)
@@ -166,44 +169,42 @@
         try:
             portage_uid = pwd.getpwnam(_get_global("_portage_username")).pw_uid
         except KeyError:
-            keyerror = True
-            portage_uid = 0
+            # PREFIX LOCAL: some sysadmins are insane, bug #344307
+            username = _get_global("_portage_username")
+            if username.isdigit():
+                portage_uid = int(username)
+            else:
+                keyerror = True
+                portage_uid = 0
+            # END PREFIX LOCAL
 
         try:
             portage_gid = grp.getgrnam(_get_global("_portage_grpname")).gr_gid
         except KeyError:
-            keyerror = True
-            portage_gid = 0
+            # PREFIX LOCAL: some sysadmins are insane, bug #344307
+            grpname = _get_global("_portage_grpname")
+            if grpname.isdigit():
+                portage_gid = int(grpname)
+            else:
+                keyerror = True
+                portage_gid = 0
+            # END PREFIX LOCAL
 
         # Suppress this error message if both PORTAGE_GRPNAME and
         # PORTAGE_USERNAME are set to "root", for things like
         # Android (see bug #454060).
-        if keyerror and not (
-            _get_global("_portage_username") == "root"
-            and _get_global("_portage_grpname") == "root"
-        ):
-            writemsg(
-                colorize("BAD", _("portage: 'portage' user or group missing.")) + "\n",
-                noiselevel=-1,
-            )
-            writemsg(
-                _(
-                    "         For the defaults, line 1 goes into passwd, "
-                    "and 2 into group.\n"
-                ),
-                noiselevel=-1,
-            )
-            writemsg(
-                colorize(
-                    "GOOD",
-                    "         portage:x:250:250:portage:/var/tmp/portage:/bin/false",
-                )
-                + "\n",
-                noiselevel=-1,
-            )
-            writemsg(
-                colorize("GOOD", "         portage::250:portage") + "\n", noiselevel=-1
-            )
+        if keyerror and not (_get_global('_portage_username') == "root" and
+            _get_global('_portage_grpname') == "root"):
+            # PREFIX LOCAL: we need to fix this one day to distinguish prefix vs non-prefix
+            writemsg(colorize("BAD",
+                _("portage: '%s' user or '%s' group missing." % (_get_global('_portage_username'), _get_global('_portage_grpname')))) + "\n", noiselevel=-1)
+            writemsg(colorize("BAD",
+                _("         In Prefix Portage this is quite dramatic")) + "\n", noiselevel=-1)
+            writemsg(colorize("BAD",
+                _("         since it means you have thrown away yourself.")) + "\n", noiselevel=-1)
+            writemsg(colorize("BAD",
+                _("         Re-add yourself or re-bootstrap Gentoo Prefix.")) + "\n", noiselevel=-1)
+            # END PREFIX LOCAL
             portage_group_warning()
 
         globals()["portage_gid"] = portage_gid
@@ -331,13 +332,17 @@
         # from grp.getgrnam() with PyPy
         native_string = platform.python_implementation() == "PyPy"
 
-        v = settings.get("PORTAGE_GRPNAME", "portage")
+        # PREFIX LOCAL: use var iso hardwired 'portage'
+        v = settings.get("PORTAGE_GRPNAME", PORTAGE_GROUPNAME)
+        # END PREFIX LOCAL
         if native_string:
             v = portage._native_string(v)
         globals()["_portage_grpname"] = v
         _initialized_globals.add("_portage_grpname")
 
-        v = settings.get("PORTAGE_USERNAME", "portage")
+        # PREFIX LOCAL: use var iso hardwired 'portage'
+        v = settings.get("PORTAGE_USERNAME", PORTAGE_USERNAME)
+        # END PREFIX LOCAL
         if native_string:
             v = portage._native_string(v)
         globals()["_portage_username"] = v
diff --git a/lib/portage/dbapi/bintree.py b/lib/portage/dbapi/bintree.py
index f4251b4..005681a 100644
--- a/lib/portage/dbapi/bintree.py
+++ b/lib/portage/dbapi/bintree.py
@@ -132,6 +132,8 @@
             "SLOT",
             "USE",
             "_mtime_",
+            # PREFIX LOCAL
+            "EPREFIX",
         }
         self._aux_cache = {}
         self._aux_cache_slot_dict_cache = None
@@ -563,6 +565,8 @@
             "SIZE",
             "SLOT",
             "USE",
+            # PREFIX LOCAL
+            "EPREFIX",
         ]
         self._pkgindex_use_evaluated_keys = (
             "BDEPEND",
@@ -592,6 +596,8 @@
             "USE_EXPAND_HIDDEN",
             "USE_EXPAND_IMPLICIT",
             "USE_EXPAND_UNPREFIXED",
+            # PREFIX LOCAL
+            "EPREFIX",
         }
         self._pkgindex_default_pkg_data = {
             "BDEPEND": "",
@@ -615,6 +621,8 @@
             "USE": "",
         }
         self._pkgindex_inherited_keys = ["CHOST", "repository"]
+        # PREFIX LOCAL
+        self._pkgindex_inherited_keys += ["EPREFIX"]
 
         # Populate the header with appropriate defaults.
         self._pkgindex_default_header_data = {
diff --git a/lib/portage/dbapi/vartree.py b/lib/portage/dbapi/vartree.py
index c6b45ba..05e16b5 100644
--- a/lib/portage/dbapi/vartree.py
+++ b/lib/portage/dbapi/vartree.py
@@ -38,6 +38,9 @@
     "portage.util._xattr:xattr",
     "portage.util._dyn_libs.PreservedLibsRegistry:PreservedLibsRegistry",
     "portage.util._dyn_libs.LinkageMapELF:LinkageMapELF@LinkageMap",
+    # BEGIN PREFIX LOCAL
+    'portage.util._dyn_libs.LinkageMapMachO:LinkageMapMachO',
+    # END PREFIX LOCAL
     "portage.util._dyn_libs.NeededEntry:NeededEntry",
     "portage.util._async.SchedulerInterface:SchedulerInterface",
     "portage.util._eventloop.global_event_loop:global_event_loop",
@@ -56,6 +59,8 @@
     PRIVATE_PATH,
     VDB_PATH,
     SUPPORTED_GENTOO_BINPKG_FORMATS,
+    # PREFIX LOCAL
+    EPREFIX,
 )
 from portage.dbapi import dbapi
 from portage.exception import (
@@ -232,7 +237,17 @@
             settings["ROOT"],
             os.path.join(self._eroot, PRIVATE_PATH, "preserved_libs_registry"),
         )
-        self._linkmap = LinkageMap(self)
+
+        # BEGIN PREFIX LOCAL: set linkagemap to the platform specific
+        #                     provider
+        chost = self.settings.get('CHOST')
+        if not chost:
+            chost = 'lunix?' # this happens when profiles are not available
+        if chost.find('darwin') >= 0:
+            self._linkmap = LinkageMapMachO(self)
+        else:
+            self._linkmap = LinkageMap(self)
+        # END PREFIX LOCAL
         self._owners = self._owners_db(self)
 
         self._cached_counter = None
@@ -3715,7 +3730,13 @@
         def path_to_node(path):
             node = path_node_map.get(path)
             if node is None:
-                node = LinkageMap._LibGraphNode(linkmap._obj_key(path))
+                # BEGIN PREFIX LOCAL: use arch specific impl
+                chost = self.settings.get('CHOST')
+                if chost.find('darwin') >= 0:
+                    node = LinkageMapMachO._LibGraphNode(linkmap._obj_key(path))
+                else:
+                    node = LinkageMap._LibGraphNode(linkmap._obj_key(path))
+                # END PREFIX LOCAL
                 alt_path_node = lib_graph.get(node)
                 if alt_path_node is not None:
                     node = alt_path_node
diff --git a/lib/portage/getbinpkg.py b/lib/portage/getbinpkg.py
index fca44f0..0231908 100644
--- a/lib/portage/getbinpkg.py
+++ b/lib/portage/getbinpkg.py
@@ -19,6 +19,8 @@
 import time
 import tempfile
 import base64
+# PREFIX LOCAL
+from portage.const import CACHE_PATH
 import warnings
 
 from html.parser import HTMLParser as html_parser_HTMLParser
@@ -603,11 +605,13 @@
     if conn:
         keepconnection = 0
 
-    cache_path = "/var/cache/edb"
+    # PREFIX LOCAL
+    cache_path = CACHE_PATH
     metadatafilename = os.path.join(cache_path, "remote_metadata.pickle")
 
     if not makepickle:
-        makepickle = "/var/cache/edb/metadata.idx.most_recent"
+        # PREFIX LOCAL: use CACHE_PATH for EPREFIX
+        makepickle = os.path.join(cache_path, "metadata.idx.most_recent")
 
     try:
         conn = create_conn(baseurl, conn)[0]
diff --git a/lib/portage/meson.build b/lib/portage/meson.build
index 06dde8c..6a648fd 100644
--- a/lib/portage/meson.build
+++ b/lib/portage/meson.build
@@ -21,6 +21,7 @@
         'binpkg.py',
         'checksum.py',
         const_py,
+        'const_autotool.py',  # PREFIX_LOCAL
         'cvstree.py',
         'data.py',
         'debug.py',
diff --git a/lib/portage/package/ebuild/_config/special_env_vars.py b/lib/portage/package/ebuild/_config/special_env_vars.py
index 6020029..e74a3f2 100644
--- a/lib/portage/package/ebuild/_config/special_env_vars.py
+++ b/lib/portage/package/ebuild/_config/special_env_vars.py
@@ -240,6 +240,11 @@
         "STY",
         "WINDOW",
         "XAUTHORITY",
+        # BEGIN PREFIX LOCAL
+        "EXTRA_PATH",
+        "PORTAGE_GROUP",
+        "PORTAGE_USER",
+        # END PREFIX LOCAL
     )
 )
 
@@ -262,6 +267,19 @@
         "INFOPATH",
         "MANPATH",
         "USER",
+        # BEGIN PREFIX LOCAL
+        "HOST",
+        "GROUP",
+        "LOGNAME",
+        "MAIL",
+        "REMOTEHOST",
+        "SECURITYSESSIONID",
+        "TERMINFO",
+        "TERM_PROGRAM",
+        "TERM_PROGRAM_VERSION",
+        "VENDOR",
+        "__CF_USER_TEXT_ENCODING",
+        # END PREFIX LOCAL
         # variables that break bash
         "HISTFILE",
         "POSIXLY_CORRECT",
diff --git a/lib/portage/package/ebuild/config.py b/lib/portage/package/ebuild/config.py
index bafdc55..72670cb 100644
--- a/lib/portage/package/ebuild/config.py
+++ b/lib/portage/package/ebuild/config.py
@@ -65,6 +65,8 @@
 from portage.localization import _
 from portage.output import colorize
 from portage.process import fakeroot_capable, sandbox_capable
+# PREFIX LOCAL
+from portage.process import macossandbox_capable
 from portage.repository.config import (
     allow_profile_repo_deps,
     load_repository_config,
@@ -1095,8 +1097,15 @@
             }
 
             eroot_or_parent = first_existing(eroot)
-            unprivileged = False
+            # PREFIX LOCAL
+            unprivileged = portage.const.EPREFIX != ''
             try:
+                # PREFIX LOCAL: inventing UID/GID based on a path is a very
+                # bad idea, it breaks almost everything since group ids
+                # don't have to match, when a user has many
+                # This in particularly breaks the configure-set portage
+                # group and user (in portage/data.py)
+                raise OSError(2, "No such file or directory")
                 eroot_st = os.stat(eroot_or_parent)
             except OSError:
                 pass
diff --git a/lib/portage/package/ebuild/doebuild.py b/lib/portage/package/ebuild/doebuild.py
index bc51fdf..7994394 100644
--- a/lib/portage/package/ebuild/doebuild.py
+++ b/lib/portage/package/ebuild/doebuild.py
@@ -22,6 +22,8 @@
 from typing import Union
 import warnings
 import zlib
+# PREFIX LOCAL
+import platform
 
 import portage
 
@@ -67,6 +69,10 @@
     MISC_SH_BINARY,
     PORTAGE_PYM_PACKAGES,
     SUPPORTED_GENTOO_BINPKG_FORMATS,
+    # BEGIN PREFIX LOCAL
+    EPREFIX,
+    MACOSSANDBOX_PROFILE,
+    # END PREFIX LOCAL
 )
 from portage.data import portage_gid, portage_uid, secpass, uid, userpriv_groups
 from portage.dbapi.porttree import _parse_uri_map
@@ -331,6 +337,11 @@
                 path.append(p)
                 pathset.add(p)
 
+    # BEGIN PREFIX LOCAL: append EXTRA_PATH from make.globals
+    extrapath = [x for x in settings.get("EXTRA_PATH", "").split(":") if x]
+    path.extend(extrapath)
+    # END PREFIX LOCAL
+
     settings["PATH"] = ":".join(path)
 
 
@@ -1698,7 +1709,9 @@
         and "nouserpriv" not in restrict
     )
 
-    if not portage.process.sandbox_capable:
+    # PREFIX LOCAL: macOS sandbox
+    if not (portage.process.sandbox_capable
+            or portage.process.macossandbox_capable):
         nosandbox = True
 
     sesandbox = settings.selinux_enabled() and "sesandbox" in features
@@ -2058,6 +2071,10 @@
                 user = "root"
             elif portage_build_uid == portage_uid:
                 user = portage.data._portage_username
+            # BEGIN PREFIX LOCAL: accept numeric uid
+            else:
+                user = portage_uid
+            # END PREFIX LOCAL
         if user is not None:
             mysettings["PORTAGE_BUILD_USER"] = user
 
@@ -2070,6 +2087,10 @@
                 group = "root"
             elif portage_build_gid == portage_gid:
                 group = portage.data._portage_grpname
+            # BEGIN PREFIX LOCAL: accept numeric gid
+            else:
+                group = portage_gid
+            # END PREFIX LOCAL
         if group is not None:
             mysettings["PORTAGE_BUILD_GROUP"] = group
 
@@ -2081,7 +2102,9 @@
             and not fakeroot
         )
 
-    if not free and not (fakeroot or portage.process.sandbox_capable):
+    # PREFIX LOCAL: macOS sandbox
+    if not free and not (fakeroot or portage.process.sandbox_capable
+            or portage.process.macossandbox_capable):
         free = True
 
     if mysettings.mycpv is not None:
@@ -2099,6 +2122,79 @@
         keywords["opt_name"] += " fakeroot"
         keywords["fakeroot_state"] = os.path.join(mysettings["T"], "fakeroot.state")
         spawn_func = portage.process.spawn_fakeroot
+    # BEGIN PREFIX LOCAL
+    elif "sandbox" in features and platform.system() == 'Darwin':
+        keywords["opt_name"] += " macossandbox"
+        sbprofile = MACOSSANDBOX_PROFILE
+
+        # determine variable names from profile: split
+        # "text@@VARNAME@@moretext@@OTHERVAR@@restoftext" into
+        # ("text", # "VARNAME", "moretext", "OTHERVAR", "restoftext")
+        # and extract variable named by reading every second item.
+        variables = []
+        for line in sbprofile.split("\n"):
+            variables.extend(line.split("@@")[1:-1:2])
+
+        for var in variables:
+            paths = ""
+            if var in mysettings:
+                paths = mysettings[var]
+            else:
+                writemsg("Warning: sandbox profile references variable %s "
+                         "which is not set.\nThe rule using it will have no "
+                         "effect, which is most likely not the intended "
+                         "result.\nPlease check make.conf/make.globals.\n" %
+                         var)
+
+            # not set or empty value
+            if not paths:
+                sbprofile = sbprofile.replace("@@%s@@" % var, "")
+                continue
+
+            rules_literal = ""
+            rules_regex = ""
+
+            # FIXME: Allow for quoting inside the variable
+            # to allow paths with spaces in them?
+            for path in paths.split(" "):
+                # do a second round of token
+                # replacements to be able to reference
+                # settings like EPREFIX or
+                # PORTAGE_BUILDDIR.
+                for token in path.split("@@")[1:-1:2]:
+                    if token not in mysettings:
+                        continue
+
+                    path = path.replace("@@%s@@" % token, mysettings[token])
+
+                if "@@" in path:
+                    # unreplaced tokens left -
+                    # silently ignore path - needed
+                    # for PORTAGE_ACTUAL_DISTDIR
+                    # which isn't always set
+                    pass
+                elif path[-1] == os.sep:
+                    # path ends in slash - make it a
+                    # regex and allow access
+                    # recursively.
+                    path = path.replace(r'+', r'\+')
+                    path = path.replace(r'*', r'\*')
+                    path = path.replace(r'[', r'\[')
+                    path = path.replace(r']', r'\]')
+                    rules_regex += "    #\"^%s\"\n" % path
+                else:
+                    rules_literal += "    #\"%s\"\n" % path
+
+            rules = ""
+            if rules_literal:
+                rules += "  (literal\n" + rules_literal + "  )\n"
+            if rules_regex:
+                rules += "  (regex\n" + rules_regex + "  )\n"
+            sbprofile = sbprofile.replace("@@%s@@" % var, rules)
+
+        keywords["profile"] = sbprofile
+        spawn_func = portage.process.spawn_macossandbox
+    # END PREFIX LOCAL
     else:
         keywords["opt_name"] += " sandbox"
         spawn_func = portage.process.spawn_sandbox
@@ -2231,13 +2327,17 @@
         (
             {},
             [
+                # PREFIX LOCAL
                 "preinst_sfperms",
                 "preinst_suid_scan",
                 "preinst_qa_check",
             ],
         ),
     ),
-    "postinst": ["postinst_qa_check"],
+    "postinst": [
+            # PREFIX LOCAL
+            "postinst_qa_check",
+            ],
 }
 
 
diff --git a/lib/portage/package/ebuild/fetch.py b/lib/portage/package/ebuild/fetch.py
index bfa0c2b..ca15a6c 100644
--- a/lib/portage/package/ebuild/fetch.py
+++ b/lib/portage/package/ebuild/fetch.py
@@ -52,6 +52,8 @@
     checksum_str,
 )
 from portage.const import BASH_BINARY, CUSTOM_MIRRORS_FILE, GLOBAL_CONFIG_PATH
+# PREFIX LOCAL
+from portage.const import rootgid
 from portage.data import portage_gid, portage_uid, userpriv_groups
 from portage.exception import (
     FileNotFound,
@@ -234,7 +236,8 @@
         # to have root's gid. Therefore, use root's gid instead of
         # portage's gid to avoid spurious permissions adjustments
         # when inside fakeroot.
-        dir_gid = 0
+        # PREFIX LOCAL: do not assume root to be 0
+        dir_gid = rootgid
 
     userfetch = portage.data.secpass >= 2 and "userfetch" in settings.features
     userpriv = portage.data.secpass >= 2 and "userpriv" in settings.features
diff --git a/lib/portage/process.py b/lib/portage/process.py
index cc9ed7b..2365778 100644
--- a/lib/portage/process.py
+++ b/lib/portage/process.py
@@ -35,6 +35,8 @@
 )
 
 from portage.const import BASH_BINARY, SANDBOX_BINARY, FAKEROOT_BINARY
+# PREFIX LOCAL
+from portage.const import MACOSSANDBOX_BINARY
 from portage.exception import CommandNotFound
 from portage.proxy.objectproxy import ObjectProxy
 from portage.util._ctypes import find_library, LoadLibrary, ctypes
@@ -114,6 +116,9 @@
     FAKEROOT_BINARY, os.X_OK
 )
 
+# PREFIX LOCAL
+macossandbox_capable = (os.path.isfile(MACOSSANDBOX_BINARY) and
+                   os.access(MACOSSANDBOX_BINARY, os.X_OK))
 
 def sanitize_fds():
     """
@@ -191,6 +196,21 @@
     return spawn(args, opt_name=opt_name, **keywords)
 
 
+# BEGIN PREFIX LOCAL
+def spawn_macossandbox(mycommand, profile=None, opt_name=None, **keywords):
+	if not macossandbox_capable:
+		return spawn_bash(mycommand, opt_name=opt_name, **keywords)
+	args=[MACOSSANDBOX_BINARY]
+	if not opt_name:
+		opt_name = os.path.basename(mycommand.split()[0])
+	args.append("-p")
+	args.append(profile)
+	args.append(BASH_BINARY)
+	args.append("-c")
+	args.append(mycommand)
+	return spawn(args, opt_name=opt_name, **keywords)
+# END PREFIX LOCAL
+
 _exithandlers = []
 
 
diff --git a/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py b/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py
index cbeba37..a52bb4c 100644
--- a/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py
+++ b/lib/portage/tests/lazyimport/test_lazy_import_portage_baseline.py
@@ -24,6 +24,8 @@
             "portage.proxy.lazyimport",
             "portage.proxy.objectproxy",
             "portage._selinux",
+            # PREFIX LOCAL
+            "portage.const_autotool",
         ]
     )
 
diff --git a/lib/portage/tests/resolver/ResolverPlayground.py b/lib/portage/tests/resolver/ResolverPlayground.py
index 75c86b6..1cb3654 100644
--- a/lib/portage/tests/resolver/ResolverPlayground.py
+++ b/lib/portage/tests/resolver/ResolverPlayground.py
@@ -371,6 +371,8 @@
             metadata["CATEGORY"] = cat
             metadata["PF"] = pf
             metadata["BINPKG_FORMAT"] = binpkg_format
+            # PREFIX LOCAL
+            metadata["EPREFIX"] = self.eprefix
 
             repo_dir = self.pkgdir
             category_dir = os.path.join(repo_dir, cat)
diff --git a/lib/portage/util/__init__.py b/lib/portage/util/__init__.py
index 1f8c9e9..0be05ab 100644
--- a/lib/portage/util/__init__.py
+++ b/lib/portage/util/__init__.py
@@ -75,6 +75,8 @@
 from typing import Optional, TextIO
 
 import portage
+# PREFIX LOCAL
+from portage.const import EPREFIX
 
 portage.proxy.lazyimport.lazyimport(
     globals(),
@@ -2007,11 +2009,17 @@
     """ Return a list of paths that are used for library lookups """
     if env is None:
         env = os.environ
+    # BEGIN PREFIX LOCAL:
+    # For Darwin, match LD_LIBRARY_PATH with DYLD_LIBRARY_PATH.
+    # We don't need any host OS lib paths in Prefix, so just going with
+    # the prefixed one is fine.
     # the following is based on the information from ld.so(8)
     rval = env.get("LD_LIBRARY_PATH", "").split(":")
-    rval.extend(read_ld_so_conf(os.path.join(root, "etc", "ld.so.conf")))
-    rval.append("/usr/lib")
-    rval.append("/lib")
+    rval.extend(env.get("DYLD_LIBRARY_PATH", "").split(":"))
+    rval.extend(read_ld_so_conf(os.path.join(root, EPREFIX, "etc", "ld.so.conf")))
+    rval.append(f"{EPREFIX}/usr/lib")
+    rval.append(f"{EPREFIX}/lib")
+    # END PREFIX LOCAL
 
     return [normalize_path(x) for x in rval if x]
 
diff --git a/lib/portage/util/_dyn_libs/LinkageMapMachO.py b/lib/portage/util/_dyn_libs/LinkageMapMachO.py
new file mode 100644
index 0000000..e74f0c5
--- /dev/null
+++ b/lib/portage/util/_dyn_libs/LinkageMapMachO.py
@@ -0,0 +1,773 @@
+# Copyright 1998-2019 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+import errno
+import logging
+import subprocess
+
+import portage
+from portage import _encodings
+from portage import _os_merge
+from portage import _unicode_decode
+from portage import _unicode_encode
+from portage.cache.mappings import slot_dict_class
+from portage.exception import CommandNotFound
+from portage.localization import _
+from portage.util import getlibpaths
+from portage.util import grabfile
+from portage.util import normalize_path
+from portage.util import writemsg_level
+from portage.const import EPREFIX
+
+class LinkageMapMachO(object):
+
+	"""Models dynamic linker dependencies."""
+
+	_needed_aux_key = "NEEDED.MACHO.3"
+	_installname_map_class = slot_dict_class(
+		("consumers", "providers"), prefix="")
+
+	class _obj_properties_class(object):
+
+		__slots__ = ("arch", "needed", "install_name", "alt_paths",
+			"owner",)
+
+		def __init__(self, arch, needed, install_name, alt_paths, owner):
+			self.arch = arch
+			self.needed = needed
+			self.install_name = install_name
+			self.alt_paths = alt_paths
+			self.owner = owner
+
+	def __init__(self, vardbapi):
+		self._dbapi = vardbapi
+		self._root = self._dbapi.settings['ROOT']
+		self._libs = {}
+		self._obj_properties = {}
+		self._obj_key_cache = {}
+		self._path_key_cache = {}
+
+	def _clear_cache(self):
+		self._libs.clear()
+		self._obj_properties.clear()
+		self._obj_key_cache.clear()
+		self._path_key_cache.clear()
+
+	def _path_key(self, path):
+		key = self._path_key_cache.get(path)
+		if key is None:
+			key = self._ObjectKey(path, self._root)
+			self._path_key_cache[path] = key
+		return key
+
+	def _obj_key(self, path):
+		key = self._obj_key_cache.get(path)
+		if key is None:
+			key = self._ObjectKey(path, self._root)
+			self._obj_key_cache[path] = key
+		return key
+
+	class _ObjectKey(object):
+
+		"""Helper class used as _obj_properties keys for objects."""
+
+		__slots__ = ("_key",)
+
+		def __init__(self, obj, root):
+			"""
+			This takes a path to an object.
+
+			@param object: path to a file
+			@type object: string (example: '/usr/bin/bar')
+
+			"""
+			self._key = self._generate_object_key(obj, root)
+
+		def __hash__(self):
+			return hash(self._key)
+
+		def __eq__(self, other):
+			return self._key == other._key
+
+		def _generate_object_key(self, obj, root):
+			"""
+			Generate object key for a given object.
+
+			@param object: path to a file
+			@type object: string (example: '/usr/bin/bar')
+			@rtype: 2-tuple of types (long, int) if object exists. string if
+				object does not exist.
+			@return:
+				1. 2-tuple of object's inode and device from a stat call, if object
+					exists.
+				2. realpath of object if object does not exist.
+
+			"""
+
+			os = _os_merge
+
+			try:
+				_unicode_encode(obj,
+					encoding=_encodings['merge'], errors='strict')
+			except UnicodeEncodeError:
+				# The package appears to have been merged with a 
+				# different value of sys.getfilesystemencoding(),
+				# so fall back to utf_8 if appropriate.
+				try:
+					_unicode_encode(obj,
+						encoding=_encodings['fs'], errors='strict')
+				except UnicodeEncodeError:
+					pass
+				else:
+					os = portage.os
+
+			abs_path = os.path.join(root, obj.lstrip(os.sep))
+			try:
+				object_stat = os.stat(abs_path)
+			except OSError:
+				# Use the realpath as the key if the file does not exists on the
+				# filesystem.
+				return os.path.realpath(abs_path)
+			# Return a tuple of the device and inode.
+			return (object_stat.st_dev, object_stat.st_ino)
+
+		def file_exists(self):
+			"""
+			Determine if the file for this key exists on the filesystem.
+
+			@rtype: Boolean
+			@return:
+				1. True if the file exists.
+				2. False if the file does not exist or is a broken symlink.
+
+			"""
+			return isinstance(self._key, tuple)
+
+	class _LibGraphNode(_ObjectKey):
+		__slots__ = ("alt_paths",)
+
+		def __init__(self, key):
+			"""
+			Create a _LibGraphNode from an existing _ObjectKey.
+			This re-uses the _key attribute in order to avoid repeating
+			any previous stat calls, which helps to avoid potential race
+			conditions due to inconsistent stat results when the
+			file system is being modified concurrently.
+			"""
+			self._key = key._key
+			self.alt_paths = set()
+
+		def __str__(self):
+			return str(sorted(self.alt_paths))
+
+	def rebuild(self, exclude_pkgs=None, include_file=None,
+		preserve_paths=None):
+		"""
+		Raises CommandNotFound if there are preserved libs
+		and the scanmacho binary is not available.
+
+		@param exclude_pkgs: A set of packages that should be excluded from
+			the LinkageMap, since they are being unmerged and their NEEDED
+			entries are therefore irrelevant and would only serve to corrupt
+			the LinkageMap.
+		@type exclude_pkgs: set
+		@param include_file: The path of a file containing NEEDED entries for
+			a package which does not exist in the vardbapi yet because it is
+			currently being merged.
+		@type include_file: String
+		@param preserve_paths: Libraries preserved by a package instance that
+			is currently being merged. They need to be explicitly passed to the
+			LinkageMap, since they are not registered in the
+			PreservedLibsRegistry yet.
+		@type preserve_paths: set
+		"""
+
+		os = _os_merge
+		root = self._root
+		root_len = len(root) - 1
+		self._clear_cache()
+		libs = self._libs
+		obj_properties = self._obj_properties
+
+		lines = []
+
+		# Data from include_file is processed first so that it
+		# overrides any data from previously installed files.
+		if include_file is not None:
+			for line in grabfile(include_file):
+				lines.append((None, include_file, line))
+
+		aux_keys = [self._needed_aux_key]
+		can_lock = os.access(os.path.dirname(self._dbapi._dbroot), os.W_OK)
+		if can_lock:
+			self._dbapi.lock()
+		try:
+			for cpv in self._dbapi.cpv_all():
+				if exclude_pkgs is not None and cpv in exclude_pkgs:
+					continue
+				needed_file = self._dbapi.getpath(cpv,
+					filename=self._needed_aux_key)
+				for line in self._dbapi.aux_get(cpv, aux_keys)[0].splitlines():
+					lines.append((cpv, needed_file, line))
+		finally:
+			if can_lock:
+				self._dbapi.unlock()
+
+		# have to call scanmacho for preserved libs here as they aren't 
+		# registered in NEEDED.MACHO.3 files
+		plibs = {}
+		if preserve_paths is not None:
+			plibs.update((x, None) for x in preserve_paths)
+		if self._dbapi._plib_registry and \
+			self._dbapi._plib_registry.hasEntries():
+			for cpv, items in \
+				self._dbapi._plib_registry.getPreservedLibs().items():
+				if exclude_pkgs is not None and cpv in exclude_pkgs:
+					# These preserved libs will either be unmerged,
+					# rendering them irrelevant, or they will be
+					# preserved in the replacement package and are
+					# already represented via the preserve_paths
+					# parameter.
+					continue
+				plibs.update((x, cpv) for x in items)
+		if plibs:
+			args = [os.path.join(EPREFIX or "/", "usr/bin/scanmacho"), "-qF", "%a;%F;%S;%n"]
+			args.extend(os.path.join(root, x.lstrip("." + os.sep)) \
+				for x in plibs)
+			try:
+				proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+			except EnvironmentError as e:
+				if e.errno != errno.ENOENT:
+					raise
+				raise CommandNotFound(args[0])
+			else:
+				for l in proc.stdout:
+					try:
+						l = _unicode_decode(l,
+							encoding=_encodings['content'], errors='strict')
+					except UnicodeDecodeError:
+						l = _unicode_decode(l,
+							encoding=_encodings['content'], errors='replace')
+						writemsg_level(_("\nError decoding characters " \
+							"returned from scanmacho: %s\n\n") % (l,),
+							level=logging.ERROR, noiselevel=-1)
+					l = l.rstrip("\n")
+					if not l:
+						continue
+					fields = l.split(";")
+					if len(fields) < 4:
+						writemsg_level("\nWrong number of fields " + \
+							"returned from scanmacho: %s\n\n" % (l,),
+							level=logging.ERROR, noiselevel=-1)
+						continue
+					fields[1] = fields[1][root_len:]
+					owner = plibs.pop(fields[1], None)
+					lines.append((owner, "scanmacho", ";".join(fields)))
+				proc.wait()
+				proc.stdout.close()
+
+		if plibs:
+			# Preserved libraries that did not appear in the scanmacho output.
+			# This is known to happen with statically linked libraries.
+			# Generate dummy lines for these, so we can assume that every
+			# preserved library has an entry in self._obj_properties. This
+			# is important in order to prevent findConsumers from raising
+			# an unwanted KeyError.
+			for x, cpv in plibs.items():
+				lines.append((cpv, "plibs", ";".join(['', x, '', '', ''])))
+
+		# Share identical frozenset instances when available,
+		# in order to conserve memory.
+		frozensets = {}
+
+		for owner, location, l in lines:
+			l = l.rstrip("\n")
+			if not l:
+				continue
+			fields = l.split(";")
+			if len(fields) < 4:
+				writemsg_level(_("\nWrong number of fields " \
+					"in %s: %s\n\n") % (location, l),
+					level=logging.ERROR, noiselevel=-1)
+				continue
+			arch = fields[0]
+			obj = fields[1]
+			install_name = os.path.normpath(fields[2])
+			needed = frozenset(x for x in fields[3].split(",") if x)
+			needed = frozensets.setdefault(needed, needed)
+
+			obj_key = self._obj_key(obj)
+			indexed = True
+			myprops = obj_properties.get(obj_key)
+			if myprops is None:
+				indexed = False
+				myprops = self._obj_properties_class(
+					arch, needed, install_name, [], owner)
+				obj_properties[obj_key] = myprops
+			# All object paths are added into the obj_properties tuple.
+			myprops.alt_paths.append(obj)
+
+			# Don't index the same file more that once since only one
+			# set of data can be correct and therefore mixing data
+			# may corrupt the index (include_file overrides previously
+			# installed).
+			if indexed:
+				continue
+
+			arch_map = libs.get(arch)
+			if arch_map is None:
+				arch_map = {}
+				libs[arch] = arch_map
+			if install_name:
+				installname_map = arch_map.get(install_name)
+				if installname_map is None:
+					installname_map = self._installname_map_class(
+						providers=[], consumers=[])
+					arch_map[install_name] = installname_map
+				installname_map.providers.append(obj_key)
+			for needed_installname in needed:
+				installname_map = arch_map.get(needed_installname)
+				if installname_map is None:
+					installname_map = self._installname_map_class(
+						providers=[], consumers=[])
+					arch_map[needed_installname] = installname_map
+				installname_map.consumers.append(obj_key)
+
+		for arch, install_names in libs.items():
+			for install_name_node in install_names.values():
+				install_name_node.providers = tuple(set(install_name_node.providers))
+				install_name_node.consumers = tuple(set(install_name_node.consumers))
+
+	def listBrokenBinaries(self, debug=False):
+		"""
+		Find binaries and their needed install_names, which have no providers.
+
+		@param debug: Boolean to enable debug output
+		@type debug: Boolean
+		@rtype: dict (example: {'/usr/bin/foo': set(['/usr/lib/libbar.dylib'])})
+		@return: The return value is an object -> set-of-install_names mapping, where
+			object is a broken binary and the set consists of install_names needed by
+			object that have no corresponding libraries to fulfill the dependency.
+
+		"""
+
+		os = _os_merge
+
+		class _LibraryCache(object):
+
+			"""
+			Caches properties associated with paths.
+
+			The purpose of this class is to prevent multiple instances of
+			_ObjectKey for the same paths.
+
+			"""
+
+			def __init__(cache_self):
+				cache_self.cache = {}
+
+			def get(cache_self, obj):
+				"""
+				Caches and returns properties associated with an object.
+
+				@param obj: absolute path (can be symlink)
+				@type obj: string (example: '/usr/lib/libfoo.dylib')
+				@rtype: 4-tuple with types
+					(string or None, string or None, 2-tuple, Boolean)
+				@return: 4-tuple with the following components:
+					1. arch as a string or None if it does not exist,
+					2. soname as a string or None if it does not exist,
+					3. obj_key as 2-tuple,
+					4. Boolean representing whether the object exists.
+					(example: ('libfoo.1.dylib', (123L, 456L), True))
+
+				"""
+				if obj in cache_self.cache:
+					return cache_self.cache[obj]
+				else:
+					obj_key = self._obj_key(obj)
+					# Check that the library exists on the filesystem.
+					if obj_key.file_exists():
+						# Get the install_name from LinkageMapMachO._obj_properties if
+						# it exists. Otherwise, None.
+						obj_props = self._obj_properties.get(obj_key)
+						if obj_props is None:
+							arch = None
+							install_name = None
+						else:
+							arch = obj_props.arch
+							install_name = obj_props.install_name
+						return cache_self.cache.setdefault(obj, \
+								(arch, install_name, obj_key, True))
+					else:
+						return cache_self.cache.setdefault(obj, \
+								(None, None, obj_key, False))
+
+		rValue = {}
+		cache = _LibraryCache()
+		providers = self.listProviders()
+
+		# Iterate over all obj_keys and their providers.
+		for obj_key, install_names in providers.items():
+			obj_props = self._obj_properties[obj_key]
+			arch = obj_props.arch
+			objs = obj_props.alt_paths
+			# Iterate over each needed install_name and the set of
+			# library paths that fulfill the install_name to determine
+			# if the dependency is broken.
+			for install_name, libraries in install_names.items():
+				# validLibraries is used to store libraries, which
+				# satisfy install_name, so if no valid libraries are
+				# found, the install_name is not satisfied for obj_key.
+				# If unsatisfied, objects associated with obj_key must
+				# be emerged.
+				validLibrary = set() # for compat with LinkageMap
+				cachedArch, cachedInstallname, cachedKey, cachedExists = \
+						cache.get(install_name)
+				# Check that the this library provides the needed soname.  Doing
+				# this, however, will cause consumers of libraries missing
+				# sonames to be unnecessarily emerged. (eg libmix.so)
+				if cachedInstallname == install_name and cachedArch == arch:
+					validLibrary.add(cachedKey)
+					if debug and cachedKey not in \
+							set(map(self._obj_key_cache.get, libraries)):
+						# XXX This is most often due to soname symlinks not in
+						# a library's directory.  We could catalog symlinks in
+						# LinkageMap to avoid checking for this edge case here.
+						print(_("Found provider outside of findProviders:"), \
+								install_name, "->", cachedRealpath)
+				if debug and cachedArch == arch and \
+						cachedKey in self._obj_properties:
+					print(_("Broken symlink or missing/bad install_name:"), \
+							install_name, '->', cachedRealpath, \
+							"with install_name", cachedInstallname, "but expecting", install_name)
+				# This conditional checks if there are no libraries to
+				# satisfy the install_name (empty set).
+				if not validLibrary:
+					for obj in objs:
+						rValue.setdefault(obj, set()).add(install_name)
+					# If no valid libraries have been found by this
+					# point, then the install_name does not exist in the
+					# filesystem, but if there are libraries (from the
+					# providers mapping), it is likely that soname
+					# symlinks or the actual libraries are missing or
+					# broken.  Thus those libraries are added to rValue
+					# in order to emerge corrupt library packages.
+					for lib in libraries:
+						rValue.setdefault(lib, set()).add(install_name)
+						if debug:
+							if not os.path.isfile(lib):
+								writemsg_level(_("Missing library:") + " %s\n" % (lib,),
+									level=logging.DEBUG,
+									noiselevel=-1)
+							else:
+								writemsg_level(_("Possibly missing symlink:") + \
+									"%s\n" % (os.path.join(os.path.dirname(lib), soname)),
+									level=logging.DEBUG,
+									noiselevel=-1)
+		return rValue
+
+	def listProviders(self):
+		"""
+		Find the providers for all object keys in LinkageMap.
+
+		@rtype: dict (example:
+			{(123L, 456L): {'libbar.dylib': set(['/lib/libbar.1.5.dylib'])}})
+		@return: The return value is an object -> providers mapping, where
+			providers is a mapping of install_name -> set-of-library-paths returned
+			from the findProviders method.
+
+		"""
+		rValue = {}
+		if not self._libs:
+			self.rebuild()
+		# Iterate over all object keys within LinkageMap.
+		for obj_key in self._obj_properties:
+			rValue.setdefault(obj_key, self.findProviders(obj_key))
+		return rValue
+
+	def isMasterLink(self, obj):
+		"""
+		Determine whether an object is a "master" symlink, which means
+		that its basename is the same as the beginning part of the
+		install_name and it lacks the install_name's version component.
+
+		Examples:
+
+		install_name              | master symlink name
+		-----------------------------------------------
+		libarchive.2.8.4.dylib    | libarchive.dylib
+		(typically the install_name is libarchive.2.dylib)
+
+		@param obj: absolute path to an object
+		@type obj: string (example: '/usr/bin/foo')
+		@rtype: Boolean
+		@return:
+			1. True if obj is a master link
+			2. False if obj is not a master link
+
+		"""
+		os = _os_merge
+		obj_key = self._obj_key(obj)
+		if obj_key not in self._obj_properties:
+			raise KeyError("%s (%s) not in object list" % (obj_key, obj))
+		basename = os.path.basename(obj)
+		install_name = self._obj_properties[obj_key].install_name
+		return (len(basename) < len(os.path.basename(install_name)) and \
+			basename.endswith(".dylib") and \
+			os.path.basename(install_name).startswith(basename[:-6]))
+
+	def listLibraryObjects(self):
+		"""
+		Return a list of library objects.
+
+		Known limitation: library objects lacking an soname are not included.
+
+		@rtype: list of strings
+		@return: list of paths to all providers
+
+		"""
+		rValue = []
+		if not self._libs:
+			self.rebuild()
+		for arch_map in self._libs.values():
+			for soname_map in arch_map.values():
+				for obj_key in soname_map.providers:
+					rValue.extend(self._obj_properties[obj_key].alt_paths)
+		return rValue
+
+	def getOwners(self, obj):
+		"""
+		Return the package(s) associated with an object. Raises KeyError
+		if the object is unknown. Returns an empty tuple if the owner(s)
+		are unknown.
+
+		NOTE: For preserved libraries, the owner(s) may have been
+		previously uninstalled, but these uninstalled owners can be
+		returned by this method since they are registered in the
+		PreservedLibsRegistry.
+
+		@param obj: absolute path to an object
+		@type obj: string (example: '/usr/bin/bar')
+		@rtype: tuple
+		@return: a tuple of cpv
+		"""
+		if not self._libs:
+			self.rebuild()
+		if isinstance(obj, self._ObjectKey):
+			obj_key = obj
+		else:
+			obj_key = self._obj_key_cache.get(obj)
+			if obj_key is None:
+				raise KeyError("%s not in object list" % obj)
+		obj_props = self._obj_properties.get(obj_key)
+		if obj_props is None:
+			raise KeyError("%s not in object list" % obj_key)
+		if obj_props.owner is None:
+			return ()
+		return (obj_props.owner,)
+
+	def getSoname(self, obj):
+		"""
+		Return the install_name associated with an object.  To match
+		soname behaviour, the leading path is stripped.
+
+		@param obj: absolute path to an object
+		@type obj: string (example: '/usr/bin/bar')
+		@rtype: string
+		@return: install_name basename as a string
+
+		"""
+		os = _os_merge
+		if not self._libs:
+			self.rebuild()
+		if isinstance(obj, self._ObjectKey):
+			obj_key = obj
+			if obj_key not in self._obj_properties:
+				raise KeyError("%s not in object list" % obj_key)
+			return os.path.basename(self._obj_properties[obj_key].install_name)
+		if obj not in self._obj_key_cache:
+			raise KeyError("%s not in object list" % obj)
+		return os.path.basename(
+				self._obj_properties[self._obj_key_cache[obj]].install_name)
+
+	def findProviders(self, obj):
+		"""
+		Find providers for an object or object key.
+
+		This method may be called with a key from _obj_properties.
+
+		In some cases, not all valid libraries are returned.  This may occur when
+		an soname symlink referencing a library is in an object's runpath while
+		the actual library is not.  We should consider cataloging symlinks within
+		LinkageMap as this would avoid those cases and would be a better model of
+		library dependencies (since the dynamic linker actually searches for
+		files named with the soname in the runpaths).
+
+		@param obj: absolute path to an object or a key from _obj_properties
+		@type obj: string (example: '/usr/bin/bar') or _ObjectKey
+		@rtype: dict (example: {'libbar.dylib': set(['/lib/libbar.1.5.dylib'])})
+		@return: The return value is a install_name -> set-of-library-paths, where
+		set-of-library-paths satisfy install_name.
+
+		"""
+
+		os = _os_merge
+
+		rValue = {}
+
+		if not self._libs:
+			self.rebuild()
+
+		# Determine the obj_key from the arguments.
+		if isinstance(obj, self._ObjectKey):
+			obj_key = obj
+			if obj_key not in self._obj_properties:
+				raise KeyError("%s not in object list" % obj_key)
+		else:
+			obj_key = self._obj_key(obj)
+			if obj_key not in self._obj_properties:
+				raise KeyError("%s (%s) not in object list" % (obj_key, obj))
+
+		obj_props = self._obj_properties[obj_key]
+		arch = obj_props.arch
+		needed = obj_props.needed
+		install_name = obj_props.install_name
+		for install_name in needed:
+			rValue[install_name] = set()
+			if arch not in self._libs or install_name not in self._libs[arch]:
+				continue
+			# For each potential provider of the install_name, add it to
+			# rValue if it exists.  (Should be one)
+			for provider_key in self._libs[arch][install_name].providers:
+				providers = self._obj_properties[provider_key].alt_paths
+				for provider in providers:
+					if os.path.exists(provider):
+						rValue[install_name].add(provider)
+		return rValue
+
+	def findConsumers(self, obj, exclude_providers=None, greedy=True):
+		"""
+		Find consumers of an object or object key.
+
+		This method may be called with a key from _obj_properties.  If this
+		method is going to be called with an object key, to avoid not catching
+		shadowed libraries, do not pass new _ObjectKey instances to this method.
+		Instead pass the obj as a string.
+
+		In some cases, not all consumers are returned.  This may occur when
+		an soname symlink referencing a library is in an object's runpath while
+		the actual library is not. For example, this problem is noticeable for
+		binutils since it's libraries are added to the path via symlinks that
+		are gemerated in the /usr/$CHOST/lib/ directory by binutils-config.
+		Failure to recognize consumers of these symlinks makes preserve-libs
+		fail to preserve binutils libs that are needed by these unrecognized
+		consumers.
+
+		Note that library consumption via dlopen (common for kde plugins) is
+		currently undetected. However, it is possible to use the
+		corresponding libtool archive (*.la) files to detect such consumers
+		(revdep-rebuild is able to detect them).
+
+		The exclude_providers argument is useful for determining whether
+		removal of one or more packages will create unsatisfied consumers. When
+		this option is given, consumers are excluded from the results if there
+		is an alternative provider (which is not excluded) of the required
+		soname such that the consumers will remain satisfied if the files
+		owned by exclude_providers are removed.
+
+		@param obj: absolute path to an object or a key from _obj_properties
+		@type obj: string (example: '/usr/bin/bar') or _ObjectKey
+		@param exclude_providers: A collection of callables that each take a
+			single argument referring to the path of a library (example:
+			'/usr/lib/libssl.0.9.8.dylib'), and return True if the library is
+			owned by a provider which is planned for removal.
+		@type exclude_providers: collection
+		@param greedy: If True, then include consumers that are satisfied
+		by alternative providers, otherwise omit them. Default is True.
+		@type greedy: Boolean
+		@rtype: set of strings (example: set(['/bin/foo', '/usr/bin/bar']))
+		@return: The return value is a install_name -> set-of-library-paths, where
+		set-of-library-paths satisfy install_name.
+
+		"""
+
+		os = _os_merge
+
+		if not self._libs:
+			self.rebuild()
+
+		# Determine the obj_key and the set of objects matching the arguments.
+		if isinstance(obj, self._ObjectKey):
+			obj_key = obj
+			if obj_key not in self._obj_properties:
+				raise KeyError("%s not in object list" % obj_key)
+			objs = self._obj_properties[obj_key].alt_paths
+		else:
+			objs = set([obj])
+			obj_key = self._obj_key(obj)
+			if obj_key not in self._obj_properties:
+				raise KeyError("%s (%s) not in object list" % (obj_key, obj))
+
+		# If there is another version of this lib with the
+		# same install_name and the install_name symlink points to that
+		# other version, this lib will be shadowed and won't
+		# have any consumers.
+		if not isinstance(obj, self._ObjectKey):
+			install_name = self._obj_properties[obj_key].install_name
+			master_link = os.path.join(self._root,
+					install_name.lstrip(os.path.sep))
+			obj_path = os.path.join(self._root, obj.lstrip(os.sep))
+			try:
+				master_st = os.stat(master_link)
+				obj_st = os.stat(obj_path)
+			except OSError:
+				pass
+			else:
+				if (obj_st.st_dev, obj_st.st_ino) != \
+					(master_st.st_dev, master_st.st_ino):
+					return set()
+
+		obj_props = self._obj_properties[obj_key]
+		arch = obj_props.arch
+		install_name = obj_props.install_name
+
+		install_name_node = None
+		arch_map = self._libs.get(arch)
+		if arch_map is not None:
+			install_name_node = arch_map.get(install_name)
+
+		satisfied_consumer_keys = set()
+		if install_name_node is not None:
+			if exclude_providers is not None and not greedy:
+				relevant_dir_keys = set()
+				for provider_key in install_name_node.providers:
+					if not greedy and provider_key == obj_key:
+						continue
+					provider_objs = self._obj_properties[provider_key].alt_paths
+					for p in provider_objs:
+						provider_excluded = False
+						if exclude_providers is not None:
+							for excluded_provider_isowner in exclude_providers:
+								if excluded_provider_isowner(p):
+									provider_excluded = True
+									break
+						if not provider_excluded:
+							# This provider is not excluded. It will
+							# satisfy a consumer of this install_name.
+							relevant_dir_keys.add(self._path_key(p))
+
+				if relevant_dir_keys:
+					for consumer_key in install_name_node.consumers:
+							satisfied_consumer_keys.add(consumer_key)
+
+		rValue = set()
+		if install_name_node is not None:
+			# For each potential consumer, add it to rValue.
+			for consumer_key in install_name_node.consumers:
+				if consumer_key in satisfied_consumer_keys:
+					continue
+				consumer_props = self._obj_properties[consumer_key]
+				consumer_objs = consumer_props.alt_paths
+				rValue.update(consumer_objs)
+		return rValue
diff --git a/lib/portage/util/_dyn_libs/meson.build b/lib/portage/util/_dyn_libs/meson.build
index f744d2a..705b51d 100644
--- a/lib/portage/util/_dyn_libs/meson.build
+++ b/lib/portage/util/_dyn_libs/meson.build
@@ -1,6 +1,7 @@
 py.install_sources(
     [
         'LinkageMapELF.py',
+        'LinkageMapMachO.py',   # PREFIX_LOCAL
         'NeededEntry.py',
         'PreservedLibsRegistry.py',
         'display_preserved_libs.py',
diff --git a/lib/portage/util/_info_files.py b/lib/portage/util/_info_files.py
index 45d674b..a5b2b30 100644
--- a/lib/portage/util/_info_files.py
+++ b/lib/portage/util/_info_files.py
@@ -9,16 +9,20 @@
 
 import portage
 from portage import os
+# PREFIX LOCAL
+from portage.const import EPREFIX
 
 
 def chk_updated_info_files(root, infodirs, prev_mtimes):
-    if os.path.exists("/usr/bin/install-info"):
+    # PREFIX LOCAL
+    if os.path.exists(EPREFIX + "/usr/bin/install-info"):
         out = portage.output.EOutput()
         regen_infodirs = []
         for z in infodirs:
             if z == "":
                 continue
-            inforoot = portage.util.normalize_path(root + z)
+            # PREFIX LOCAL
+            inforoot = portage.util.normalize_path(root + EPREFIX + z)
             if os.path.isdir(inforoot) and not [
                 x for x in os.listdir(inforoot) if x.startswith(".keepinfodir")
             ]:
@@ -75,7 +79,8 @@
                     try:
                         proc = subprocess.Popen(
                             [
-                                "/usr/bin/install-info",
+                                # PREFIX LOCAL
+                                f"{EPREFIX}/usr/bin/install-info",
                                 f"--dir-file={os.path.join(inforoot, 'dir')}",
                                 os.path.join(inforoot, x),
                             ],
diff --git a/lib/portage/util/env_update.py b/lib/portage/util/env_update.py
index b19a853..809712f 100644
--- a/lib/portage/util/env_update.py
+++ b/lib/portage/util/env_update.py
@@ -97,7 +97,8 @@
     else:
         settings = env
 
-    eprefix = settings.get("EPREFIX", "")
+    # PREFIX LOCAL
+    eprefix = settings.get("EPREFIX", portage.const.EPREFIX)
     eprefix_lstrip = eprefix.lstrip(os.sep)
     eroot = (
         normalize_path(os.path.join(target_root, eprefix_lstrip)).rstrip(os.sep)
diff --git a/lib/portage/util/hooks.py b/lib/portage/util/hooks.py
index cbb15f1..c0a37ff 100644
--- a/lib/portage/util/hooks.py
+++ b/lib/portage/util/hooks.py
@@ -12,11 +12,15 @@
 from portage.util import writemsg_level, _recursive_file_list
 from warnings import warn
 
+# PREFIX LOCAL
+from portage.const import EPREFIX
+
 bad = create_color_func("BAD")
 warn = create_color_func("WARN")
 
 
-def get_hooks_from_dir(rel_directory, prefix="/"):
+# PREFIX LOCAL: prefix=EPREFIX
+def get_hooks_from_dir(rel_directory, prefix=EPREFIX):
     directory = os.path.join(prefix, portage.USER_CONFIG_PATH, rel_directory)
 
     hooks = OrderedDict()
@@ -39,7 +43,8 @@
     return hooks
 
 
-def perform_hooks(rel_directory, *argv, prefix="/"):
+# PREFIX LOCAL: prefix=EPREFIX
+def perform_hooks(rel_directory, *argv, prefix=EPREFIX):
     for filepath, name in get_hooks_from_dir(rel_directory, prefix).items():
         hook_command = filepath + " " + " ".join(map(str, argv))
         retval = portage.process.spawn(hook_command)
diff --git a/man/Makefile.am b/man/Makefile.am
new file mode 100644
index 0000000..4034c99
--- /dev/null
+++ b/man/Makefile.am
@@ -0,0 +1,17 @@
+SHELL = @PORTAGE_BASH@
+
+man_MANS = \
+	color.map.5 \
+	dispatch-conf.1 \
+	ebuild.1 \
+	ebuild.5 \
+	egencache.1 \
+	emaint.1 \
+	emerge.1 \
+	env-update.1 \
+	etc-update.1 \
+	make.conf.5 \
+	portage.5 \
+	quickpkg.1
+
+EXTRA_DIST = $(man_MANS)
diff --git a/man/ebuild.5 b/man/ebuild.5
index a32ba48..687c4f9 100644
--- a/man/ebuild.5
+++ b/man/ebuild.5
@@ -884,6 +884,12 @@
 This variable is intended to be used on files of binary packages which ignore
 CFLAGS, CXXFLAGS, FFLAGS, FCFLAGS, and LDFLAGS variables.
 .TP
+.B QA_IGNORE_INSTALL_NAME_FILES
+This should contain a list of file names (without path) that should be
+ignored in the install_name check.  That is, if these files point to
+something not available in the image directory or live filesystem, these
+files are ignored, albeit being broken.
+.TP
 .B QA_MULTILIB_PATHS
 This should contain a list of file paths, relative to the image directory, of
 files that should be ignored for the multilib\-strict checks.
diff --git a/man/emerge.1 b/man/emerge.1
index 43dc3f2..609c9ac 100644
--- a/man/emerge.1
+++ b/man/emerge.1
@@ -1445,6 +1445,12 @@
 Tools such as dispatch\-conf, cfg\-update, and etc\-update are also available
 to aid in the merging of these files. They provide interactive merging and can
 auto\-merge trivial changes.
+.LP
+When an offset prefix (\fBEPREFIX\fR) is active, all paths in
+\fBCONFIG_PROTECT\fR and \fBCONFIG_PROTECT_MASK\fR are prefixed with the
+offset by Portage before they are considered.  Hence, these paths never
+contain the offset prefix, and the variables can be defined in
+offset-unaware locations, such as the profiles. 
 .SH "REPORTING BUGS"
 Please report any bugs you encounter through our website:
 .LP
@@ -1464,6 +1470,7 @@
 Jason Stubbs <jstubbs@gentoo.org>
 Brian Harring <ferringb@gmail.com>
 Zac Medico <zmedico@gentoo.org>
+Fabian Groffen <grobian@gentoo.org>
 Arfrever Frehtes Taifersar Arahesis <arfrever@apache.org>
 .fi
 .SH "FILES"
diff --git a/man/make.conf.5 b/man/make.conf.5
index e13f6ee..cfd8434 100644
--- a/man/make.conf.5
+++ b/man/make.conf.5
@@ -266,6 +266,9 @@
 All files and/or directories that are defined here will have "config file
 protection" enabled for them. See the \fBCONFIGURATION FILES\fR section
 of \fBemerge\fR(1) for more information.
+Note that if an offset prefix (\fBEPREFIX\fR) is activated, all paths defined
+in \fBCONFIG_PROTECT\fR are prefixed by Portage with the offset before
+they are used.
 .TP
 \fBCONFIG_PROTECT_MASK\fR = \fI[space delimited list of files and/or \
 directories]\fR
@@ -715,6 +718,9 @@
 .TP
 .B sandbox
 Enable sandbox\-ing when running \fBemerge\fR(1) and \fBebuild\fR(1).
+On Mac OS X platforms that have /usr/bin/sandbox-exec available (10.5
+and later), this particular sandbox implementation is used instead of
+sys-apps/sandbox.
 .TP
 .B sesandbox
 Enable SELinux sandbox\-ing.  Do not toggle this \fBFEATURE\fR yourself.
diff --git a/subst-install.in b/subst-install.in
new file mode 100644
index 0000000..07576ee
--- /dev/null
+++ b/subst-install.in
@@ -0,0 +1,87 @@
+#!@PORTAGE_BASH@
+
+# for expansion below we need some things to be defined
+prefix="@prefix@"
+exec_prefix="@exec_prefix@"
+
+# For bug #279550 we have to do some nasty trick to make sure that sed
+# doesn't strip the backslash in the replacement value (because it can
+# be a backreference) and hence escape those.  Eventually in strings we
+# need to escape the backslash too, such that the single backslash
+# doesn't get lost when considered an invalid escape
+rootuser='@rootuser@'
+portagegroup='@portagegroup@'
+portageuser='@portageuser@'
+rootuser=${rootuser//\\/\\\\}
+portagegroup=${portagegroup//\\/\\\\\\\\}
+portageuser=${portageuser//\\/\\\\\\\\}
+
+# there are many ways to do this all dynamic, but we only care for raw
+# speed here, so let configure fill in this list and be done with it
+at='@'
+sedexp=(
+	-r
+	-e "s,${at}PORTAGE_BASE${at},@PORTAGE_BASE@,g"
+	-e "s,${at}PORTAGE_BASE_PATH${at},@PORTAGE_BASE@,g"
+	-e "s,${at}PORTAGE_BIN_PATH${at},@PORTAGE_BASE@/bin,g"
+	-e "s,${at}PORTAGE_BASH${at},@PORTAGE_BASH@,g"
+	-e "s,${at}PORTAGE_EPREFIX${at},@PORTAGE_EPREFIX@,g"
+	-e "s,${at}PORTAGE_MV${at},@PORTAGE_MV@,g"
+	-e "s,${at}PREFIX_PORTAGE_PYTHON${at},@PREFIX_PORTAGE_PYTHON@,g"
+	-e "s,${at}EPREFIX${at},@PORTAGE_EPREFIX@,g"
+	-e "s,${at}INSTALL_TYPE${at},SYSTEM,g"
+	-e "s,${at}datadir${at},@datadir@,g"
+	-e "s,${at}portagegroup${at},${portagegroup},g"
+	-e "s,${at}portageuser${at},${portageuser},g"
+	-e "s,${at}rootgid${at},@rootgid@,g"
+	-e "s,${at}rootuid${at},@rootuid@,g"
+	-e "s,${at}rootuser${at},${rootuser},g"
+	-e "s,${at}sysconfdir${at},@sysconfdir@,g"
+)
+
+if [[ $1 == --hprefixify ]] ; then
+	shift
+	dirs='/(usr|lib(|[onx]?32|n?64)|etc|bin|sbin|var|opt|run)'
+	sedexp+=(
+		-e 's,([^[:alnum:]}\)\.])'"${dirs}"',\1@PORTAGE_EPREFIX@/\2,g'
+		-e 's,^'"${dirs}"',@PORTAGE_EPREFIX@/\1,'
+	)
+fi
+
+sources=( )
+target=
+args=( "$@" )
+
+while [[ ${#@} != 0 ]] ; do
+	case "$1" in
+		-t)
+			[[ -n ${target} ]] && sources=( "${sources[@]}" "${target##*/}" )
+			shift
+			target=":${1}"
+		;;
+		-*)
+			shift
+		;;
+		*)
+			if [[ -z ${target} ]] ; then
+				target="${1}"
+			elif [[ ${target} != ":"* ]] ; then
+				sources=( "${sources[@]}" "${target##*/}" )
+				target="${1}"
+			else
+				sources=( "${sources[@]}" "${1##*/}" )
+			fi
+		;;
+	esac
+	shift
+done
+
+target=${target#:}
+INSTALL="@INSTALL@"
+echo @INSTALL_DATA@ "${args[@]}"
+if [[ ! -d ${target} ]] ; then
+	# either install will die, or it was just a single file copy
+	@INSTALL_DATA@ "${args[@]}" && sed -i "${sedexp[@]}" "${target}"
+else
+	@INSTALL_DATA@ "${args[@]}" && sed -i "${sedexp[@]}" "${sources[@]/#/${target}/}"
+fi
diff --git a/tarball.sh b/tarball.sh
new file mode 100755
index 0000000..a8f5255
--- /dev/null
+++ b/tarball.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+
+if [ -z "$1" ]; then
+	echo
+	echo "You need to have the version specified."
+	echo "e.g.: $0 2.0.39-r37"
+	echo
+	exit 0
+fi
+
+export PKG="prefix-portage"
+export TMP="/var/tmp/${PKG}-build.$$"
+export V="$1"
+export DEST="${TMP}/${PKG}-${V}"
+export TARFILE="/var/tmp/${PKG}-${V}.tar.bz2"
+
+# hypothetically it can exist
+rm -Rf "${TMP}"
+
+# create copy of source
+install -d -m0755 "${DEST}"
+rsync -a --exclude='.git' --exclude='.hg' --exclude="repoman/" . "${DEST}"
+
+cd "${DEST}"
+
+# expand version
+sed -i -e '/^VERSION\s*=/s/^.*$/VERSION = "'${V}_prefix'"/' \
+	lib/portage/__init__.py
+sed -i -e "/\<version : /s/'[^']\+'/'${V}-prefix'/" meson.build
+sed -i -e "1s/VERSION/${V}-prefix/" man/{,ru/}*.[15]
+sed -i -e "s/@version@/${V}/" configure.ac
+
+# cleanup cruft
+find -name '*~' | xargs --no-run-if-empty rm -f
+find -name '*.#*' | xargs --no-run-if-empty rm -f
+find -name '*.pyc' | xargs --no-run-if-empty rm -f
+find -name '*.pyo' | xargs --no-run-if-empty rm -f
+find -name '*.orig' | xargs --no-run-if-empty rm -f
+rm -Rf autom4te.cache
+
+# we don't need these (why?)
+rm -f  bin/emerge.py  bin/{pmake,sandbox}
+
+# generate a configure file
+chmod a+x autogen.sh && ./autogen.sh || { echo "autogen failed!"; exit -1; };
+rm -f autogen.sh tabcheck.py tarball.sh commit
+
+# produce final tarball
+cd "${TMP}"
+tar --numeric-owner -jcf "${TARFILE}" ${PKG}-${V}
+
+cd /
+rm -Rf "${TMP}"
+ls -la "${TARFILE}"