diff options
52 files changed, 1986 insertions, 86 deletions
diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..62b0edcbd --- /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 000000000..9bcfa00e7 --- /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 @@ -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 @@ Package Manager Specification Project (PMS) standardises and documents 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 000000000..1d9ccf55f --- /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 000000000..463753c45 --- /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 000000000..e6dc6724f --- /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 4d4efd496..b1636d6e8 100755 --- a/bin/ebuild-helpers/dohtml +++ b/bin/ebuild-helpers/dohtml @@ -16,8 +16,10 @@ fi # 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 3d8b31eee..21da85845 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 901277c96..afed4d4d5 100755 --- a/bin/ebuild-pyhelper +++ b/bin/ebuild-pyhelper @@ -13,8 +13,10 @@ fi 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 99da05543..9959b6a5a 100755 --- a/bin/emerge-webrsync +++ b/bin/emerge-webrsync @@ -78,14 +78,22 @@ emerge=$(PATH="${BASH_SOURCE[0]%/*}:${PATH}" type -P emerge) 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 @@ sync_local() { [[ ${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 edbd6fab2..229daff6a 100644 --- a/bin/install-qa-check.d/05prefix +++ b/bin/install-qa-check.d/05prefix @@ -34,9 +34,15 @@ install_qa_check_prefix() { # 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 @@ install_qa_check_prefix() { 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 06be030fb..47e18cc6b 100644 --- a/bin/isolated-functions.sh +++ b/bin/isolated-functions.sh @@ -450,16 +450,24 @@ else 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 @@ debug-print() { 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 4ce3acbfd..47ee72917 100755 --- a/bin/misc-functions.sh +++ b/bin/misc-functions.sh @@ -162,6 +162,31 @@ install_qa_check() { 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 @@ install_qa_check() { 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 @@ install_qa_check() { 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 @@ __dyn_package() { ${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 @@ __dyn_package() { __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 ebcf5f242..31b02cedf 100644 --- a/bin/phase-functions.sh +++ b/bin/phase-functions.sh @@ -146,7 +146,8 @@ __filter_readonly_variables() { 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 @@ __ebuild_main() { __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 index 3a2560aab..c3c83c91d 100644..100755 --- a/bin/save-ebuild-env.sh +++ b/bin/save-ebuild-env.sh @@ -119,6 +119,10 @@ __save_ebuild_env() { # 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 000000000..64d2fa7a7 --- /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 4375665f4..502f0641a 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 2bb7a6559..a5931a177 100644 --- a/cnf/make.globals +++ b/cnf/make.globals @@ -130,12 +130,24 @@ PORTAGE_SYNC_STALE="30" 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_WORKDIR_MODE="0700" 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 @@ PORTAGE_XATTR_EXCLUDE="btrfs.* security.evm security.ima 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 f16fd352e..3924461c9 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 000000000..12b669efb --- /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 000000000..7d4193cc9 --- /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 b47280343..9dbd79fd1 100644 --- a/lib/_emerge/EbuildPhase.py +++ b/lib/_emerge/EbuildPhase.py @@ -32,6 +32,8 @@ from portage.util.futures import asyncio 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 @@ class _PostPhaseCommands(CompositeTask): 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 79011380d..7d13897ba 100644 --- a/lib/_emerge/Package.py +++ b/lib/_emerge/Package.py @@ -86,6 +86,8 @@ class Package(Task): "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 6853ec491..63e7ad1ee 100644 --- a/lib/_emerge/depgraph.py +++ b/lib/_emerge/depgraph.py @@ -12207,6 +12207,18 @@ def _get_masking_status(pkg, pkgsettings, root_config, myrepo=None, use=None): 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 c3c42d8e6..3d4452e68 100644 --- a/lib/_emerge/emergelog.py +++ b/lib/_emerge/emergelog.py @@ -9,12 +9,15 @@ from portage import _unicode_decode 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 aa81bdb4c..f097b653c 100644 --- a/lib/portage/__init__.py +++ b/lib/portage/__init__.py @@ -163,6 +163,10 @@ try: 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 2154213b7..1909199ef 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 @@ BASH_BINARY = f"{BINARY_PREFIX}/bin/bash" 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 000000000..ee540319c --- /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 4b9b74c7b..e57e672c3 100644 --- a/lib/portage/data.py +++ b/lib/portage/data.py @@ -6,6 +6,8 @@ import grp 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 @@ portage.proxy.lazyimport.lazyimport( 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 @@ def _get_global(k): 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 @@ def _init(settings): # 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 f4251b47d..005681adc 100644 --- a/lib/portage/dbapi/bintree.py +++ b/lib/portage/dbapi/bintree.py @@ -132,6 +132,8 @@ class bindbapi(fakedbapi): "SLOT", "USE", "_mtime_", + # PREFIX LOCAL + "EPREFIX", } self._aux_cache = {} self._aux_cache_slot_dict_cache = None @@ -563,6 +565,8 @@ class binarytree: "SIZE", "SLOT", "USE", + # PREFIX LOCAL + "EPREFIX", ] self._pkgindex_use_evaluated_keys = ( "BDEPEND", @@ -592,6 +596,8 @@ class binarytree: "USE_EXPAND_HIDDEN", "USE_EXPAND_IMPLICIT", "USE_EXPAND_UNPREFIXED", + # PREFIX LOCAL + "EPREFIX", } self._pkgindex_default_pkg_data = { "BDEPEND": "", @@ -615,6 +621,8 @@ class binarytree: "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 c6b45ba42..05e16b569 100644 --- a/lib/portage/dbapi/vartree.py +++ b/lib/portage/dbapi/vartree.py @@ -38,6 +38,9 @@ portage.proxy.lazyimport.lazyimport( "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 @@ from portage.const import ( PRIVATE_PATH, VDB_PATH, SUPPORTED_GENTOO_BINPKG_FORMATS, + # PREFIX LOCAL + EPREFIX, ) from portage.dbapi import dbapi from portage.exception import ( @@ -232,7 +237,17 @@ class vardbapi(dbapi): 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 @@ class dblink: 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 fca44f052..0231908d3 100644 --- a/lib/portage/getbinpkg.py +++ b/lib/portage/getbinpkg.py @@ -19,6 +19,8 @@ import socket 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 @@ def dir_get_metadata( 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 06dde8ca7..6a648fd9d 100644 --- a/lib/portage/meson.build +++ b/lib/portage/meson.build @@ -21,6 +21,7 @@ py.install_sources( '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 6020029e3..e74a3f2b3 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 @@ environ_whitelist = frozenset( "STY", "WINDOW", "XAUTHORITY", + # BEGIN PREFIX LOCAL + "EXTRA_PATH", + "PORTAGE_GROUP", + "PORTAGE_USER", + # END PREFIX LOCAL ) ) @@ -262,6 +267,19 @@ environ_filter = frozenset( "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 bafdc55a0..72670cbe1 100644 --- a/lib/portage/package/ebuild/config.py +++ b/lib/portage/package/ebuild/config.py @@ -65,6 +65,8 @@ from portage.exception import InvalidDependString, PortageException 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 @@ class config: } 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 bc51fdff2..7994394bd 100644 --- a/lib/portage/package/ebuild/doebuild.py +++ b/lib/portage/package/ebuild/doebuild.py @@ -22,6 +22,8 @@ import time from typing import Union import warnings import zlib +# PREFIX LOCAL +import platform import portage @@ -67,6 +69,10 @@ from portage.const import ( 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 @@ def _doebuild_path(settings, eapi=None): 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 @@ def _spawn_actionmap(settings): 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 @@ def spawn( 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 @@ def spawn( 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 @@ def spawn( 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 @@ def spawn( 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 @@ _post_phase_cmds = { ( {}, [ + # 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 bfa0c2b27..ca15a6c7d 100644 --- a/lib/portage/package/ebuild/fetch.py +++ b/lib/portage/package/ebuild/fetch.py @@ -52,6 +52,8 @@ from portage.checksum import ( 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 @@ def _ensure_distdir(settings, distdir): # 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 cc9ed7bf7..2365778e6 100644 --- a/lib/portage/process.py +++ b/lib/portage/process.py @@ -35,6 +35,8 @@ portage.proxy.lazyimport.lazyimport( ) 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_capable = os.path.isfile(FAKEROOT_BINARY) and os.access( 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 @@ def spawn_fakeroot(mycommand, fakeroot_state=None, opt_name=None, **keywords): 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 cbeba37b5..a52bb4cbb 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 @@ class LazyImportPortageBaselineTestCase(TestCase): "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 75c86b615..1cb365449 100644 --- a/lib/portage/tests/resolver/ResolverPlayground.py +++ b/lib/portage/tests/resolver/ResolverPlayground.py @@ -371,6 +371,8 @@ class ResolverPlayground: 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 1f8c9e94f..0be05ab03 100644 --- a/lib/portage/util/__init__.py +++ b/lib/portage/util/__init__.py @@ -75,6 +75,8 @@ import glob from typing import Optional, TextIO import portage +# PREFIX LOCAL +from portage.const import EPREFIX portage.proxy.lazyimport.lazyimport( globals(), @@ -2007,11 +2009,17 @@ def getlibpaths(root, env=None): """ 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 000000000..e74f0c5ba --- /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 f744d2a08..705b51d22 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 45d674b9b..a5b2b30b4 100644 --- a/lib/portage/util/_info_files.py +++ b/lib/portage/util/_info_files.py @@ -9,16 +9,20 @@ import subprocess 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 @@ def chk_updated_info_files(root, infodirs, prev_mtimes): 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 b19a85325..809712fa8 100644 --- a/lib/portage/util/env_update.py +++ b/lib/portage/util/env_update.py @@ -97,7 +97,8 @@ def _env_update(makelinks, target_root, prev_mtimes, contents, env, writemsg_lev 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 cbb15f123..c0a37fff7 100644 --- a/lib/portage/util/hooks.py +++ b/lib/portage/util/hooks.py @@ -12,11 +12,15 @@ from portage.output import create_color_func 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 @@ def get_hooks_from_dir(rel_directory, prefix="/"): 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 000000000..4034c99ba --- /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 a32ba4828..687c4f9d8 100644 --- a/man/ebuild.5 +++ b/man/ebuild.5 @@ -884,6 +884,12 @@ characters. 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 43dc3f26b..609c9ac99 100644 --- a/man/emerge.1 +++ b/man/emerge.1 @@ -1445,6 +1445,12 @@ add this to \fBmake.conf\fR(5): 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 @@ Marius Mauch <genone@gentoo.org> 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 e13f6eec4..cfd843455 100644 --- a/man/make.conf.5 +++ b/man/make.conf.5 @@ -266,6 +266,9 @@ Defaults to "/lib/modules/* *.py[co]". 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 @@ dependencies. .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 000000000..07576eede --- /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 000000000..a8f52555d --- /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}" |