aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Groffen <grobian@gentoo.org>2019-02-28 13:31:19 +0100
committerFabian Groffen <grobian@gentoo.org>2019-02-28 13:31:19 +0100
commit991d7349c8faab28bf956c00e477e115654ba3b0 (patch)
tree972397af92c66b767237a06bc338b3ca253bf0a6
parentLinkageMapMachO: ensure availability of 'os' (diff)
parentUpdates for portage-2.3.62 release (diff)
downloadportage-991d7349.tar.gz
portage-991d7349.tar.bz2
portage-991d7349.zip
Merge remote-tracking branch 'overlays-gentoo-org/master' into prefix
Signed-off-by: Fabian Groffen <grobian@gentoo.org>
-rw-r--r--RELEASE-NOTES50
-rwxr-xr-xbin/ebuild.sh30
-rwxr-xr-xbin/misc-functions.sh2
-rw-r--r--bin/pid-ns-init105
-rw-r--r--bin/postinst-qa-check.d/50gnome2-utils64
-rw-r--r--bin/postinst-qa-check.d/50xdg-utils54
l---------bin/preinst-qa-check.d/50gnome2-utils1
-rw-r--r--bin/socks5-server.py8
-rw-r--r--cnf/make.conf.example8
-rw-r--r--cnf/make.globals3
-rw-r--r--lib/_emerge/SpawnProcess.py2
-rw-r--r--lib/_emerge/resolver/output.py13
-rw-r--r--lib/portage/dbapi/porttree.py20
-rw-r--r--lib/portage/locks.py150
-rw-r--r--lib/portage/package/ebuild/_config/special_env_vars.py3
-rw-r--r--lib/portage/package/ebuild/config.py4
-rw-r--r--lib/portage/package/ebuild/doebuild.py3
-rw-r--r--lib/portage/process.py38
-rw-r--r--lib/portage/tests/resolver/ResolverPlayground.py2
-rw-r--r--lib/portage/tests/util/test_install_mask.py36
-rw-r--r--lib/portage/tests/util/test_socks5.py211
-rw-r--r--lib/portage/util/cpuinfo.py33
-rw-r--r--lib/portage/util/futures/executor/fork.py4
-rw-r--r--lib/portage/util/futures/iter_completed.py18
-rw-r--r--lib/portage/util/install_mask.py89
-rw-r--r--lib/portage/util/socks5.py48
-rw-r--r--man/make.conf.56
-rwxr-xr-xsetup.py2
28 files changed, 839 insertions, 168 deletions
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index 0a5d49bb6..b817561ef 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -1,6 +1,56 @@
Release Notes; upgrade information mainly.
Features/major bugfixes are listed in NEWS
+portage-2.3.62
+==================================
+* Bug Fixes:
+ - Bug 678278 unprivileged sync emergelog lock permission denied
+
+
+portage-2.3.61
+==================================
+* Bug Fixes:
+ - Bug 677776 gnome2_icon_cache_update -> xdg_icon_cache_update
+ - Bug 677800 Don't define a default for ACCEPT_LICENSE
+ - Bug 678218 locks: handle sshfs hardlink inode numbers
+ - FL-6227 cpuinfo: use better available CPU calculation
+
+
+portage-2.3.60
+==================================
+* Bug Fixes:
+ - Bug 636798 handle lock file removal on NFS
+
+
+portage-2.3.59
+==================================
+* Bug Fixes:
+ - Bug 675868 pid-sandbox: pid-ns-init TIOCSCTTY after setsid
+
+
+portage-2.3.58
+==================================
+* Bug Fixes:
+ - Bug 675868 run pid-sandbox pid-ns-init as root
+ - Bug 675870 setsid for pid-sandbox process group signals
+ - Bug 676014 use local ECLASS variable during ebuild inherit
+
+
+portage-2.3.57
+==================================
+* Bug Fixes:
+ - Bug 675756 emerge: compare new SLOT USE to installed SLOT
+ - Bug 675826 INSTALL_MASK scalability: minimize fnmatch calls
+ - Bug 675828 pid-sandbox: fix child process signal disposition
+
+
+portage-2.3.56
+==================================
+* Bug Fixes:
+ - Bug 675284 restore canonicalize func
+ - Bug 675312 pid-sandbox: execute pid-ns-init as pid 1
+
+
portage-2.3.55
==================================
* Bug Fixes:
diff --git a/bin/ebuild.sh b/bin/ebuild.sh
index eb5615056..5acf36995 100755
--- a/bin/ebuild.sh
+++ b/bin/ebuild.sh
@@ -240,24 +240,22 @@ inherit() {
ECLASS_DEPTH=$(($ECLASS_DEPTH + 1))
if [[ ${ECLASS_DEPTH} -gt 1 ]]; then
debug-print "*** Multiple Inheritence (Level: ${ECLASS_DEPTH})"
- fi
- if [[ -n $ECLASS && -n ${!__export_funcs_var} ]] ; then
- echo "QA Notice: EXPORT_FUNCTIONS is called before inherit in" \
- "$ECLASS.eclass. For compatibility with <=portage-2.1.6.7," \
- "only call EXPORT_FUNCTIONS after inherit(s)." \
- | fmt -w 75 | while read -r ; do eqawarn "$REPLY" ; done
+ # Since ECLASS_DEPTH > 1, the following variables are locals from the
+ # previous inherit call in the call stack.
+ if [[ -n ${ECLASS} && -n ${!__export_funcs_var} ]] ; then
+ eqawarn "QA Notice: EXPORT_FUNCTIONS is called before inherit in ${ECLASS}.eclass."
+ eqawarn "For compatibility with <=portage-2.1.6.7, only call EXPORT_FUNCTIONS"
+ eqawarn "after inherit(s)."
+ fi
fi
+ local -x ECLASS
+ local __export_funcs_var
local repo_location
local location
local potential_location
local x
-
- # These variables must be restored before returning.
- local PECLASS=$ECLASS
- local prev_export_funcs_var=$__export_funcs_var
-
local B_IUSE
local B_REQUIRED_USE
local B_DEPEND
@@ -269,7 +267,7 @@ inherit() {
location=""
potential_location=""
- export ECLASS="$1"
+ ECLASS="$1"
__export_funcs_var=__export_functions_$ECLASS_DEPTH
unset $__export_funcs_var
@@ -379,12 +377,6 @@ inherit() {
shift
done
((--ECLASS_DEPTH)) # Returns 1 when ECLASS_DEPTH reaches 0.
- if (( ECLASS_DEPTH > 0 )) ; then
- export ECLASS=$PECLASS
- __export_funcs_var=$prev_export_funcs_var
- else
- unset ECLASS __export_funcs_var
- fi
return 0
}
@@ -476,7 +468,7 @@ __try_source() {
# If $- contains x, then tracing has already been enabled
# elsewhere for some reason. We preserve it's state so as
# not to interfere.
- if [[ ${qa} ]]; then
+ if ! ${qa} ; then
source "${1}"
else
__qa_source "${1}"
diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh
index 692becaf6..d01ffe43a 100755
--- a/bin/misc-functions.sh
+++ b/bin/misc-functions.sh
@@ -47,7 +47,6 @@ install_symlink_html_docs() {
fi
}
-# PREFIX LOCAL: we need this on some platforms
# replacement for "readlink -f" or "realpath"
READLINK_F_WORKS=""
canonicalize() {
@@ -81,7 +80,6 @@ canonicalize() {
cd "${wd}"
return 1
}
-# END PREFIX LOCAL
install_qa_check() {
local d f i qa_var x paths qa_checks=() checks_run=()
diff --git a/bin/pid-ns-init b/bin/pid-ns-init
index 843257b70..cfbd65280 100644
--- a/bin/pid-ns-init
+++ b/bin/pid-ns-init
@@ -1,23 +1,120 @@
#!/usr/bin/env python
-# Copyright 2018 Gentoo Authors
+# Copyright 2018-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
+import errno
+import fcntl
+import functools
import os
+import platform
+import signal
+import subprocess
import sys
+import termios
+
+
+KILL_SIGNALS = (
+ signal.SIGINT,
+ signal.SIGTERM,
+ signal.SIGHUP,
+)
+
+
+def forward_kill_signal(pid, signum, frame):
+ if pid == 0:
+ # Avoid a signal feedback loop, since signals sent to the
+ # process group are also sent to the current process.
+ signal.signal(signum, signal.SIG_DFL)
+ os.kill(pid, signum)
+
+
+def preexec_fn(uid, gid, groups, umask):
+ if gid is not None:
+ os.setgid(gid)
+ if groups is not None:
+ os.setgroups(groups)
+ if uid is not None:
+ os.setuid(uid)
+ if umask is not None:
+ os.umask(umask)
+
+ # CPython >= 3 subprocess.Popen handles this internally.
+ if sys.version_info.major < 3 or platform.python_implementation() != 'CPython':
+ for signum in (
+ signal.SIGHUP,
+ signal.SIGINT,
+ signal.SIGPIPE,
+ signal.SIGQUIT,
+ signal.SIGTERM,
+ ):
+ signal.signal(signum, signal.SIG_DFL)
def main(argv):
if len(argv) < 2:
- return 'Usage: {} <main-child-pid>'.format(argv[0])
- main_child_pid = int(argv[1])
+ return 'Usage: {} <main-child-pid> or <uid> <gid> <groups> <umask> <pass_fds> <binary> <argv0> [arg]..'.format(argv[0])
+
+ if len(argv) == 2:
+ # The child process is init (pid 1) in a child pid namespace, and
+ # the current process supervises from within the global pid namespace
+ # (forwarding signals to init and forwarding exit status to the parent
+ # process).
+ main_child_pid = int(argv[1])
+ setsid = False
+ proc = None
+ else:
+ # The current process is init (pid 1) in a child pid namespace.
+ uid, gid, groups, umask, pass_fds, binary, args = argv[1], argv[2], argv[3], argv[4], tuple(int(fd) for fd in argv[5].split(',')), argv[6], argv[7:]
+ uid = int(uid) if uid else None
+ gid = int(gid) if gid else None
+ groups = tuple(int(group) for group in groups.split(',')) if groups else None
+ umask = int(umask) if umask else None
+
+ popen_kwargs = {}
+ popen_kwargs['preexec_fn'] = functools.partial(preexec_fn, uid, gid, groups, umask)
+ if sys.version_info.major > 2:
+ popen_kwargs['pass_fds'] = pass_fds
+ # Isolate parent process from process group SIGSTOP (bug 675870)
+ setsid = True
+ os.setsid()
+ if sys.stdout.isatty():
+ try:
+ fcntl.ioctl(sys.stdout, termios.TIOCSCTTY, 0)
+ except EnvironmentError as e:
+ if e.errno == errno.EPERM:
+ # This means that stdout refers to the controlling terminal
+ # of the parent process, and in this case we do not want to
+ # steel it.
+ pass
+ else:
+ raise
+ proc = subprocess.Popen(args, executable=binary, **popen_kwargs)
+ main_child_pid = proc.pid
+
+ # If setsid has been called, use kill(0, signum) to
+ # forward signals to the entire process group.
+ sig_handler = functools.partial(forward_kill_signal, 0 if setsid else main_child_pid)
+ for signum in KILL_SIGNALS:
+ signal.signal(signum, sig_handler)
# wait for child processes
while True:
- pid, status = os.wait()
+ try:
+ pid, status = os.wait()
+ except EnvironmentError as e:
+ if e.errno == errno.EINTR:
+ continue
+ raise
if pid == main_child_pid:
+ if proc is not None:
+ # Suppress warning messages like this:
+ # ResourceWarning: subprocess 1234 is still running
+ proc.returncode = 0
+
if os.WIFEXITED(status):
return os.WEXITSTATUS(status)
elif os.WIFSIGNALED(status):
+ signal.signal(os.WTERMSIG(status), signal.SIG_DFL)
os.kill(os.getpid(), os.WTERMSIG(status))
# go to the unreachable place
break
diff --git a/bin/postinst-qa-check.d/50gnome2-utils b/bin/postinst-qa-check.d/50gnome2-utils
deleted file mode 100644
index 29ea7c844..000000000
--- a/bin/postinst-qa-check.d/50gnome2-utils
+++ /dev/null
@@ -1,64 +0,0 @@
-# check for missing calls to gnome2-utils regen functions
-
-gnome2_icon_cache_check() {
- type -P gtk-update-icon-cache &>/dev/null || return
-
- local d f all_files=() missing
- for d in usr/share/icons/*/; do
- # gnome2_icon_cache_update updates only themes with an index
- [[ -f ${d}/index.theme ]] || continue
-
- local files=() find_args=(
- # gtk-update-icon-cache supports only specific file
- # suffixes; match that to avoid false positives
- '(' -name '*.png' -o -name '*.svg'
- -o -name '*.xpm' -o -name '*.icon' ')'
- )
- # if the cache does not exist at all, we complain for any file
- # otherwise, we look for files newer than the cache
- [[ -f ${d}/icon-theme.cache ]] &&
- find_args+=( -newercm "${d}"/icon-theme.cache ) || missing=1
-
- # (use -mindepth 2 to easily skip the cache files)
- while read -r -d $'\0' f; do
- files+=( "${f}" )
- done < <(find "${d}" -mindepth 2 -type f "${find_args[@]}" -print0)
-
- # if any files were found, update the db to avoid repeating
- # the warning for subsequent packages
- if [[ ${files[@]} ]]; then
- all_files+=("${files[@]}")
- addwrite "${d}"
- gtk-update-icon-cache -qf "${d}"
- fi
- done
-
- # preinst initializes the baseline state for the posinst check
- [[ ${PORTAGE_QA_PHASE} == preinst ]] && return
-
- # parallel-install makes it impossible to blame a specific package
- has parallel-install ${FEATURES} && return
-
- # avoid false-positives on first install (bug 649464)
- [[ ${PN} == gtk-update-icon-cache ]] && return
-
- # The eqatag call is prohibitively expensive if the cache is
- # missing and there are a large number of files.
- if [[ -z ${missing} && ${all_files[@]} ]]; then
- eqawarn "QA Notice: new icons were found installed but GTK+ icon cache"
- eqawarn "has not been updated:"
- eqatag -v gnome2-utils.icon-cache "${all_files[@]/#//}"
- eqawarn "Please make sure to call gnome2_icon_cache_update()"
- eqawarn "in pkg_postinst() and pkg_postrm() phases of appropriate pkgs."
- fi
-}
-
-gnome2_utils_postinst_check() {
- cd "${EROOT:-/}" || die
- gnome2_icon_cache_check
-}
-
-gnome2_utils_postinst_check
-: # guarantee successful exit
-
-# vim:ft=sh
diff --git a/bin/postinst-qa-check.d/50xdg-utils b/bin/postinst-qa-check.d/50xdg-utils
index b33df4743..9a7c036e6 100644
--- a/bin/postinst-qa-check.d/50xdg-utils
+++ b/bin/postinst-qa-check.d/50xdg-utils
@@ -46,6 +46,59 @@ xdg_desktop_database_check() {
fi
}
+xdg_icon_cache_check() {
+ type -P gtk-update-icon-cache &>/dev/null || return
+
+ local d f all_files=() missing
+ for d in usr/share/icons/*/; do
+ # xdg_icon_cache_update updates only themes with an index
+ [[ -f ${d}/index.theme ]] || continue
+
+ local files=() find_args=(
+ # gtk-update-icon-cache supports only specific file
+ # suffixes; match that to avoid false positives
+ '(' -name '*.png' -o -name '*.svg'
+ -o -name '*.xpm' -o -name '*.icon' ')'
+ )
+ # if the cache does not exist at all, we complain for any file
+ # otherwise, we look for files newer than the cache
+ [[ -f ${d}/icon-theme.cache ]] &&
+ find_args+=( -newercm "${d}"/icon-theme.cache ) || missing=1
+
+ # (use -mindepth 2 to easily skip the cache files)
+ while read -r -d $'\0' f; do
+ files+=( "${f}" )
+ done < <(find "${d}" -mindepth 2 -type f "${find_args[@]}" -print0)
+
+ # if any files were found, update the db to avoid repeating
+ # the warning for subsequent packages
+ if [[ ${files[@]} ]]; then
+ all_files+=("${files[@]}")
+ addwrite "${d}"
+ gtk-update-icon-cache -qf "${d}"
+ fi
+ done
+
+ # preinst initializes the baseline state for the posinst check
+ [[ ${PORTAGE_QA_PHASE} == preinst ]] && return
+
+ # parallel-install makes it impossible to blame a specific package
+ has parallel-install ${FEATURES} && return
+
+ # avoid false-positives on first install (bug 649464)
+ [[ ${PN} == gtk-update-icon-cache ]] && return
+
+ # The eqatag call is prohibitively expensive if the cache is
+ # missing and there are a large number of files.
+ if [[ -z ${missing} && ${all_files[@]} ]]; then
+ eqawarn "QA Notice: new icons were found installed but icon cache"
+ eqawarn "has not been updated:"
+ eqatag -v xdg-utils.icon-cache "${all_files[@]/#//}"
+ eqawarn "Please make sure to call xdg_icon_cache_update()"
+ eqawarn "in pkg_postinst() and pkg_postrm() phases of appropriate pkgs."
+ fi
+}
+
xdg_mimeinfo_database_check() {
type -P update-mime-database &>/dev/null || return
@@ -92,6 +145,7 @@ xdg_mimeinfo_database_check() {
xdg_utils_postinst_check() {
cd "${EROOT:-/}" || die
xdg_desktop_database_check
+ xdg_icon_cache_check
xdg_mimeinfo_database_check
}
diff --git a/bin/preinst-qa-check.d/50gnome2-utils b/bin/preinst-qa-check.d/50gnome2-utils
deleted file mode 120000
index ee57f814d..000000000
--- a/bin/preinst-qa-check.d/50gnome2-utils
+++ /dev/null
@@ -1 +0,0 @@
-../postinst-qa-check.d/50gnome2-utils \ No newline at end of file
diff --git a/bin/socks5-server.py b/bin/socks5-server.py
index d46cf5345..f5d995f0d 100644
--- a/bin/socks5-server.py
+++ b/bin/socks5-server.py
@@ -17,6 +17,12 @@ else:
# getattr() necessary because async is a keyword in Python >=3.7.
asyncio_ensure_future = getattr(asyncio, 'async')
+try:
+ current_task = asyncio.current_task
+except AttributeError:
+ # Deprecated since Python 3.7
+ current_task = asyncio.Task.current_task
+
class Socks5Server(object):
"""
@@ -154,7 +160,7 @@ class Socks5Server(object):
# otherwise, start two loops:
# remote -> local...
t = asyncio_ensure_future(self.handle_proxied_conn(
- proxied_reader, writer, asyncio.Task.current_task()))
+ proxied_reader, writer, current_task()))
# and local -> remote...
try:
diff --git a/cnf/make.conf.example b/cnf/make.conf.example
index 81b9c0328..34957eddd 100644
--- a/cnf/make.conf.example
+++ b/cnf/make.conf.example
@@ -1,4 +1,4 @@
-# Copyright 1999-2013 Gentoo Foundation
+# Copyright 1999-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# Contains local system settings for Portage system
@@ -84,11 +84,11 @@
# license_groups file (see portage(5) man page). In addition to license
# and group names, the * and -* wildcard tokens are also supported.
#
-# Accept any license except those in the EULA license group (default).
-#ACCEPT_LICENSE="* -@EULA"
-#
# Only accept licenses in the FREE license group (i.e. Free Software).
#ACCEPT_LICENSE="-* @FREE"
+#
+# Accept any license except those in the EULA license group.
+#ACCEPT_LICENSE="* -@EULA"
# Portage Directories
# ===================
diff --git a/cnf/make.globals b/cnf/make.globals
index 5013957ea..e71325c91 100644
--- a/cnf/make.globals
+++ b/cnf/make.globals
@@ -1,4 +1,4 @@
-# Copyright 1999-2018 Gentoo Authors
+# Copyright 1999-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
# System-wide defaults for the Portage system
@@ -23,7 +23,6 @@ FCFLAGS=""
# Approved by the mirror-admin team.
GENTOO_MIRRORS="http://distfiles.gentoo.org"
-ACCEPT_LICENSE="* -@EULA"
ACCEPT_PROPERTIES="*"
ACCEPT_RESTRICT="*"
diff --git a/lib/_emerge/SpawnProcess.py b/lib/_emerge/SpawnProcess.py
index cd535d143..395d66bb9 100644
--- a/lib/_emerge/SpawnProcess.py
+++ b/lib/_emerge/SpawnProcess.py
@@ -32,7 +32,7 @@ class SpawnProcess(SubProcess):
_spawn_kwarg_names = ("env", "opt_name", "fd_pipes",
"uid", "gid", "groups", "umask", "logfile",
"path_lookup", "pre_exec", "close_fds", "cgroup",
- "unshare_ipc", "unshare_net")
+ "unshare_ipc", "unshare_mount", "unshare_pid", "unshare_net")
__slots__ = ("args",) + \
_spawn_kwarg_names + ("_pipe_logger", "_selinux_type",)
diff --git a/lib/_emerge/resolver/output.py b/lib/_emerge/resolver/output.py
index 24340576c..ed88cc51f 100644
--- a/lib/_emerge/resolver/output.py
+++ b/lib/_emerge/resolver/output.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2018 Gentoo Foundation
+# Copyright 2010-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
"""Resolver output display operation.
@@ -673,9 +673,14 @@ class Display(object):
pkg_info.previous_pkg = self.vardb.match_pkgs(
Atom('=' + pkg.cpv))[0]
else:
- slot_matches = self.vardb.match_pkgs(pkg.slot_atom)
- if slot_matches:
- pkg_info.previous_pkg = slot_matches[0]
+ cp_slot_matches = self.vardb.match_pkgs(pkg.slot_atom)
+ if cp_slot_matches:
+ pkg_info.previous_pkg = cp_slot_matches[0]
+ else:
+ cp_matches = self.vardb.match_pkgs(Atom(pkg.cp))
+ if cp_matches:
+ # Use highest installed other-slot package instance.
+ pkg_info.previous_pkg = cp_matches[-1]
return pkg_info
diff --git a/lib/portage/dbapi/porttree.py b/lib/portage/dbapi/porttree.py
index 76b7967f7..64a5f3681 100644
--- a/lib/portage/dbapi/porttree.py
+++ b/lib/portage/dbapi/porttree.py
@@ -1339,11 +1339,19 @@ class portagetree(object):
" constructor is unused",
DeprecationWarning, stacklevel=2)
- self.portroot = settings["PORTDIR"]
self.__virtual = virtual
self.dbapi = portdbapi(mysettings=settings)
@property
+ def portroot(self):
+ """Deprecated. Use the portdbapi getRepositoryPath method instead."""
+ warnings.warn("The portroot attribute of "
+ "portage.dbapi.porttree.portagetree is deprecated. Use the "
+ "portdbapi getRepositoryPath method instead.",
+ DeprecationWarning, stacklevel=3)
+ return self.settings['PORTDIR']
+
+ @property
def root(self):
warnings.warn("The root attribute of " + \
"portage.dbapi.porttree.portagetree" + \
@@ -1383,7 +1391,11 @@ class portagetree(object):
return self.dbapi.cp_all()
def getname(self, pkgname):
- "returns file location for this particular package (DEPRECATED)"
+ """Deprecated. Use the portdbapi findname method instead."""
+ warnings.warn("The getname method of "
+ "portage.dbapi.porttree.portagetree is deprecated. "
+ "Use the portdbapi findname method instead.",
+ DeprecationWarning, stacklevel=2)
if not pkgname:
return ""
mysplit = pkgname.split("/")
@@ -1459,11 +1471,11 @@ def _async_manifest_fetchlist(portdb, repo_config, cp, cpv_list=None,
@param cpv_list: list of ebuild cpv values for a Manifest
@type cpv_list: list
@param max_jobs: max number of futures to process concurrently (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_jobs: int
@param max_load: max load allowed when scheduling a new future,
otherwise schedule no more than 1 future at a time (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_load: int or float
@param loop: event loop
@type loop: EventLoop
diff --git a/lib/portage/locks.py b/lib/portage/locks.py
index a4e7ec53f..609c8b2dc 100644
--- a/lib/portage/locks.py
+++ b/lib/portage/locks.py
@@ -1,5 +1,5 @@
# portage: Lock management code
-# Copyright 2004-2014 Gentoo Foundation
+# Copyright 2004-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
__all__ = ["lockdir", "unlockdir", "lockfile", "unlockfile", \
@@ -20,6 +20,7 @@ from portage.exception import (DirectoryNotFound, FileNotFound,
InvalidData, TryAgain, OperationNotPermitted, PermissionDenied,
ReadOnlyFileSystem)
from portage.util import writemsg
+from portage.util.install_mask import _raise_exc
from portage.localization import _
if sys.hexversion >= 0x3000000:
@@ -106,7 +107,34 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
If wantnewlockfile is True then this creates a lockfile in the parent
directory as the file: '.' + basename + '.portage_lockfile'.
"""
+ lock = None
+ while lock is None:
+ lock = _lockfile_iteration(mypath, wantnewlockfile=wantnewlockfile,
+ unlinkfile=unlinkfile, waiting_msg=waiting_msg, flags=flags)
+ if lock is None:
+ writemsg(_("lockfile removed by previous lock holder, retrying\n"), 1)
+ return lock
+
+def _lockfile_iteration(mypath, wantnewlockfile=False, unlinkfile=False,
+ waiting_msg=None, flags=0):
+ """
+ Acquire a lock on mypath, without retry. Return None if the lockfile
+ was removed by previous lock holder (caller must retry).
+
+ @param mypath: lock file path
+ @type mypath: str
+ @param wantnewlockfile: use a separate new lock file
+ @type wantnewlockfile: bool
+ @param unlinkfile: remove lock file prior to unlock
+ @type unlinkfile: bool
+ @param waiting_msg: message to show before blocking
+ @type waiting_msg: str
+ @param flags: lock flags (only supports os.O_NONBLOCK)
+ @type flags: int
+ @rtype: bool
+ @return: unlockfile tuple on success, None if retry is needed
+ """
if not mypath:
raise InvalidData(_("Empty path given"))
@@ -148,18 +176,17 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
preexisting = os.path.exists(lockfilename)
old_mask = os.umask(000)
try:
- try:
- myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660)
- except OSError as e:
- func_call = "open('%s')" % lockfilename
- if e.errno == OperationNotPermitted.errno:
- raise OperationNotPermitted(func_call)
- elif e.errno == PermissionDenied.errno:
- raise PermissionDenied(func_call)
- elif e.errno == ReadOnlyFileSystem.errno:
- raise ReadOnlyFileSystem(func_call)
+ while True:
+ try:
+ myfd = os.open(lockfilename, os.O_CREAT|os.O_RDWR, 0o660)
+ except OSError as e:
+ if e.errno in (errno.ENOENT, errno.ESTALE) and os.path.isdir(os.path.dirname(lockfilename)):
+ # Retry required for NFS (see bug 636798).
+ continue
+ else:
+ _raise_exc(e)
else:
- raise
+ break
if not preexisting:
try:
@@ -272,14 +299,18 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
raise
- if isinstance(lockfilename, basestring) and \
- myfd != HARDLINK_FD and _fstat_nlink(myfd) == 0:
- # The file was deleted on us... Keep trying to make one...
- os.close(myfd)
- writemsg(_("lockfile recurse\n"), 1)
- lockfilename, myfd, unlinkfile, locking_method = lockfile(
- mypath, wantnewlockfile=wantnewlockfile, unlinkfile=unlinkfile,
- waiting_msg=waiting_msg, flags=flags)
+ if isinstance(lockfilename, basestring) and myfd != HARDLINK_FD and unlinkfile:
+ try:
+ removed = _lockfile_was_removed(myfd, lockfilename)
+ except Exception:
+ # Do not leak the file descriptor here.
+ os.close(myfd)
+ raise
+ else:
+ if removed:
+ # Removed by previous lock holder... Caller will retry...
+ os.close(myfd)
+ return None
if myfd != HARDLINK_FD:
@@ -298,6 +329,85 @@ def lockfile(mypath, wantnewlockfile=0, unlinkfile=0,
writemsg(str((lockfilename, myfd, unlinkfile)) + "\n", 1)
return (lockfilename, myfd, unlinkfile, locking_method)
+
+def _lockfile_was_removed(lock_fd, lock_path):
+ """
+ Check if lock_fd still refers to a file located at lock_path, since
+ the file may have been removed by a concurrent process that held the
+ lock earlier. This implementation includes support for NFS, where
+ stat is not reliable for removed files due to the default file
+ attribute cache behavior ('ac' mount option).
+
+ @param lock_fd: an open file descriptor for a lock file
+ @type lock_fd: int
+ @param lock_path: path of lock file
+ @type lock_path: str
+ @rtype: bool
+ @return: True if lock_path exists and corresponds to lock_fd, False otherwise
+ """
+ try:
+ fstat_st = os.fstat(lock_fd)
+ except OSError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ _raise_exc(e)
+ return True
+
+ # Since stat is not reliable for removed files on NFS with the default
+ # file attribute cache behavior ('ac' mount option), create a temporary
+ # hardlink in order to prove that the file path exists on the NFS server.
+ hardlink_path = hardlock_name(lock_path)
+ try:
+ os.unlink(hardlink_path)
+ except OSError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ _raise_exc(e)
+ try:
+ try:
+ os.link(lock_path, hardlink_path)
+ except OSError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ _raise_exc(e)
+ return True
+
+ hardlink_stat = os.stat(hardlink_path)
+ if hardlink_stat.st_ino != fstat_st.st_ino or hardlink_stat.st_dev != fstat_st.st_dev:
+ # Create another hardlink in order to detect whether or not
+ # hardlink inode numbers are expected to match. For example,
+ # inode numbers are not expected to match for sshfs.
+ inode_test = hardlink_path + '-inode-test'
+ try:
+ os.unlink(inode_test)
+ except OSError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ _raise_exc(e)
+ try:
+ os.link(hardlink_path, inode_test)
+ except OSError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ _raise_exc(e)
+ return True
+ else:
+ if not os.path.samefile(hardlink_path, inode_test):
+ # This implies that inode numbers are not expected
+ # to match for this file system, so use a simple
+ # stat call to detect if lock_path has been removed.
+ return not os.path.exists(lock_path)
+ finally:
+ try:
+ os.unlink(inode_test)
+ except OSError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ _raise_exc(e)
+ return True
+ finally:
+ try:
+ os.unlink(hardlink_path)
+ except OSError as e:
+ if e.errno not in (errno.ENOENT, errno.ESTALE):
+ _raise_exc(e)
+ return False
+
+
def _fstat_nlink(fd):
"""
@param fd: an open file descriptor
diff --git a/lib/portage/package/ebuild/_config/special_env_vars.py b/lib/portage/package/ebuild/_config/special_env_vars.py
index 70a9c83c1..c16d4d644 100644
--- a/lib/portage/package/ebuild/_config/special_env_vars.py
+++ b/lib/portage/package/ebuild/_config/special_env_vars.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2018 Gentoo Foundation
+# Copyright 2010-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
from __future__ import unicode_literals
@@ -208,7 +208,6 @@ global_only_vars = frozenset([
])
default_globals = {
- 'ACCEPT_LICENSE': '* -@EULA',
'ACCEPT_PROPERTIES': '*',
'PORTAGE_BZIP2_COMMAND': 'bzip2',
}
diff --git a/lib/portage/package/ebuild/config.py b/lib/portage/package/ebuild/config.py
index 45dd5841e..8e9a74760 100644
--- a/lib/portage/package/ebuild/config.py
+++ b/lib/portage/package/ebuild/config.py
@@ -1,4 +1,4 @@
-# Copyright 2010-2018 Gentoo Foundation
+# Copyright 2010-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
from __future__ import unicode_literals
@@ -2284,7 +2284,7 @@ class config(object):
for curdb in mydbs:
mysplit.extend(curdb.get('ACCEPT_LICENSE', '').split())
mysplit = prune_incremental(mysplit)
- accept_license_str = ' '.join(mysplit)
+ accept_license_str = ' '.join(mysplit) or '* -@EULA'
self.configlist[-1]['ACCEPT_LICENSE'] = accept_license_str
self._license_manager.set_accept_license_str(accept_license_str)
else:
diff --git a/lib/portage/package/ebuild/doebuild.py b/lib/portage/package/ebuild/doebuild.py
index 8e3683452..a104a4953 100644
--- a/lib/portage/package/ebuild/doebuild.py
+++ b/lib/portage/package/ebuild/doebuild.py
@@ -112,7 +112,8 @@ _ipc_phases = frozenset([
])
# phases which execute in the global PID namespace
-_global_pid_phases = frozenset(['preinst', 'postinst', 'prerm', 'postrm', 'config'])
+_global_pid_phases = frozenset([
+ 'config', 'depend', 'preinst', 'prerm', 'postinst', 'postrm'])
# phases in which networking access is allowed
_networked_phases = frozenset([
diff --git a/lib/portage/process.py b/lib/portage/process.py
index 37e96879e..8951a4f7f 100644
--- a/lib/portage/process.py
+++ b/lib/portage/process.py
@@ -1,5 +1,5 @@
# portage.py -- core Portage functionality
-# Copyright 1998-2018 Gentoo Authors
+# Copyright 1998-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
@@ -482,7 +482,7 @@ def _exec(binary, mycommand, opt_name, fd_pipes,
@param gid: Group ID to run the process under
@type gid: Integer
@param groups: Groups the Process should be in.
- @type groups: Integer
+ @type groups: List
@param uid: User ID to run the process under
@type uid: Integer
@param umask: an int representing a unix umask (see man chmod for umask details)
@@ -579,15 +579,37 @@ def _exec(binary, mycommand, opt_name, fd_pipes,
noiselevel=-1)
else:
if unshare_pid:
- # pid namespace requires us to become init
- fork_ret = os.fork()
- if fork_ret != 0:
- os.execv(portage._python_interpreter, [
+ main_child_pid = os.fork()
+ if main_child_pid == 0:
+ # pid namespace requires us to become init
+ binary, myargs = portage._python_interpreter, [
portage._python_interpreter,
os.path.join(portage._bin_path,
'pid-ns-init'),
- '%s' % fork_ret,
- ])
+ _unicode_encode('' if uid is None else str(uid)),
+ _unicode_encode('' if gid is None else str(gid)),
+ _unicode_encode('' if groups is None else ','.join(str(group) for group in groups)),
+ _unicode_encode('' if umask is None else str(umask)),
+ _unicode_encode(','.join(str(fd) for fd in fd_pipes)),
+ binary] + myargs
+ uid = None
+ gid = None
+ groups = None
+ umask = None
+ else:
+ # Execute a supervisor process which will forward
+ # signals to init and forward exit status to the
+ # parent process. The supervisor process runs in
+ # the global pid namespace, so skip /proc remount
+ # and other setup that's intended only for the
+ # init process.
+ binary, myargs = portage._python_interpreter, [
+ portage._python_interpreter,
+ os.path.join(portage._bin_path,
+ 'pid-ns-init'), str(main_child_pid)]
+
+ os.execve(binary, myargs, env)
+
if unshare_mount:
# mark the whole filesystem as slave to avoid
# mounts escaping the namespace
diff --git a/lib/portage/tests/resolver/ResolverPlayground.py b/lib/portage/tests/resolver/ResolverPlayground.py
index 670c2a845..b8f251e0c 100644
--- a/lib/portage/tests/resolver/ResolverPlayground.py
+++ b/lib/portage/tests/resolver/ResolverPlayground.py
@@ -86,6 +86,7 @@ class ResolverPlayground(object):
"basename",
"bzip2",
"cat",
+ "chgrp",
"chmod",
"chown",
"cp",
@@ -99,6 +100,7 @@ class ResolverPlayground(object):
"mkdir",
"mktemp",
"mv",
+ "readlink",
"rm",
"sed",
"sort",
diff --git a/lib/portage/tests/util/test_install_mask.py b/lib/portage/tests/util/test_install_mask.py
index f651eb4b7..6a29db79a 100644
--- a/lib/portage/tests/util/test_install_mask.py
+++ b/lib/portage/tests/util/test_install_mask.py
@@ -119,6 +119,42 @@ class InstallMaskTestCase(TestCase):
),
)
),
+ (
+ '/usr/share/locale '
+ '-/usr/share/locale/en* '
+ '-/usr/share/locale/kf5_all_languages '
+ '-/usr/share/locale/locale.alias',
+ (
+ (
+ 'usr/share/locale/en',
+ False,
+ ),
+ (
+ 'usr/share/locale/en_GB',
+ False,
+ ),
+ (
+ 'usr/share/locale/en/kf5_all_languages',
+ False,
+ ),
+ (
+ 'usr/share/locale/locale.alias',
+ False,
+ ),
+ (
+ 'usr/share/locale/es',
+ True,
+ ),
+ (
+ 'usr/share/locale/fr',
+ True,
+ ),
+ (
+ 'usr/share/locale',
+ True,
+ ),
+ )
+ ),
)
for install_mask_str, paths in cases:
diff --git a/lib/portage/tests/util/test_socks5.py b/lib/portage/tests/util/test_socks5.py
new file mode 100644
index 000000000..5db85b0a6
--- /dev/null
+++ b/lib/portage/tests/util/test_socks5.py
@@ -0,0 +1,211 @@
+# Copyright 2019 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+import functools
+import platform
+import shutil
+import socket
+import struct
+import sys
+import tempfile
+import time
+
+import portage
+from portage.tests import TestCase
+from portage.util._eventloop.global_event_loop import global_event_loop
+from portage.util import socks5
+from portage.const import PORTAGE_BIN_PATH
+
+try:
+ from http.server import BaseHTTPRequestHandler, HTTPServer
+except ImportError:
+ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+
+try:
+ from urllib.request import urlopen
+except ImportError:
+ from urllib import urlopen
+
+
+class _Handler(BaseHTTPRequestHandler):
+
+ def __init__(self, content, *args, **kwargs):
+ self.content = content
+ BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
+
+ def do_GET(self):
+ doc = self.send_head()
+ if doc is not None:
+ self.wfile.write(doc)
+
+ def do_HEAD(self):
+ self.send_head()
+
+ def send_head(self):
+ doc = self.content.get(self.path)
+ if doc is None:
+ self.send_error(404, "File not found")
+ return None
+
+ self.send_response(200)
+ self.send_header("Content-type", "text/plain")
+ self.send_header("Content-Length", len(doc))
+ self.send_header("Last-Modified", self.date_time_string(time.time()))
+ self.end_headers()
+ return doc
+
+ def log_message(self, fmt, *args):
+ pass
+
+
+class AsyncHTTPServer(object):
+ def __init__(self, host, content, loop):
+ self._host = host
+ self._content = content
+ self._loop = loop
+ self.server_port = None
+ self._httpd = None
+
+ def __enter__(self):
+ httpd = self._httpd = HTTPServer((self._host, 0), functools.partial(_Handler, self._content))
+ self.server_port = httpd.server_port
+ self._loop.add_reader(httpd.socket.fileno(), self._httpd._handle_request_noblock)
+ return self
+
+ def __exit__(self, exc_type, exc_value, exc_traceback):
+ if self._httpd is not None:
+ self._loop.remove_reader(self._httpd.socket.fileno())
+ self._httpd.socket.close()
+ self._httpd = None
+
+
+class AsyncHTTPServerTestCase(TestCase):
+
+ @staticmethod
+ def _fetch_directly(host, port, path):
+ # NOTE: python2.7 does not have context manager support here
+ try:
+ f = urlopen('http://{host}:{port}{path}'.format( # nosec
+ host=host, port=port, path=path))
+ return f.read()
+ finally:
+ if f is not None:
+ f.close()
+
+ def test_http_server(self):
+ host = '127.0.0.1'
+ content = b'Hello World!\n'
+ path = '/index.html'
+ loop = global_event_loop()
+ for i in range(2):
+ with AsyncHTTPServer(host, {path: content}, loop) as server:
+ for j in range(2):
+ result = loop.run_until_complete(loop.run_in_executor(None,
+ self._fetch_directly, host, server.server_port, path))
+ self.assertEqual(result, content)
+
+
+class _socket_file_wrapper(portage.proxy.objectproxy.ObjectProxy):
+ """
+ A file-like object that wraps a socket and closes the socket when
+ closed. Since python2.7 does not support socket.detach(), this is a
+ convenient way to have a file attached to a socket that closes
+ automatically (without resource warnings about unclosed sockets).
+ """
+
+ __slots__ = ('_file', '_socket')
+
+ def __init__(self, socket, f):
+ object.__setattr__(self, '_socket', socket)
+ object.__setattr__(self, '_file', f)
+
+ def _get_target(self):
+ return object.__getattribute__(self, '_file')
+
+ def __getattribute__(self, attr):
+ if attr == 'close':
+ return object.__getattribute__(self, 'close')
+ return super(_socket_file_wrapper, self).__getattribute__(attr)
+
+ def __enter__(self):
+ return self
+
+ def close(self):
+ object.__getattribute__(self, '_file').close()
+ object.__getattribute__(self, '_socket').close()
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ self.close()
+
+
+def socks5_http_get_ipv4(proxy, host, port, path):
+ """
+ Open http GET request via socks5 proxy listening on a unix socket,
+ and return a file to read the response body from.
+ """
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ f = _socket_file_wrapper(s, s.makefile('rb', 1024))
+ try:
+ s.connect(proxy)
+ s.send(struct.pack('!BBB', 0x05, 0x01, 0x00))
+ vers, method = struct.unpack('!BB', s.recv(2))
+ s.send(struct.pack('!BBBB', 0x05, 0x01, 0x00, 0x01))
+ s.send(socket.inet_pton(socket.AF_INET, host))
+ s.send(struct.pack('!H', port))
+ reply = struct.unpack('!BBB', s.recv(3))
+ if reply != (0x05, 0x00, 0x00):
+ raise AssertionError(repr(reply))
+ struct.unpack('!B4sH', s.recv(7)) # contains proxied address info
+ s.send("GET {} HTTP/1.1\r\nHost: {}:{}\r\nAccept: */*\r\nConnection: close\r\n\r\n".format(
+ path, host, port).encode())
+ headers = []
+ while True:
+ headers.append(f.readline())
+ if headers[-1] == b'\r\n':
+ return f
+ except Exception:
+ f.close()
+ raise
+
+
+class Socks5ServerTestCase(TestCase):
+
+ @staticmethod
+ def _fetch_via_proxy(proxy, host, port, path):
+ with socks5_http_get_ipv4(proxy, host, port, path) as f:
+ return f.read()
+
+ def test_socks5_proxy(self):
+
+ loop = global_event_loop()
+
+ host = '127.0.0.1'
+ content = b'Hello World!'
+ path = '/index.html'
+ proxy = None
+ tempdir = tempfile.mkdtemp()
+
+ try:
+ with AsyncHTTPServer(host, {path: content}, loop) as server:
+
+ settings = {
+ 'PORTAGE_TMPDIR': tempdir,
+ 'PORTAGE_BIN_PATH': PORTAGE_BIN_PATH,
+ }
+
+ try:
+ proxy = socks5.get_socks5_proxy(settings)
+ except NotImplementedError:
+ # bug 658172 for python2.7
+ self.skipTest('get_socks5_proxy not implemented for {} {}.{}'.format(
+ platform.python_implementation(), *sys.version_info[:2]))
+ else:
+ loop.run_until_complete(socks5.proxy.ready())
+
+ result = loop.run_until_complete(loop.run_in_executor(None,
+ self._fetch_via_proxy, proxy, host, server.server_port, path))
+
+ self.assertEqual(result, content)
+ finally:
+ socks5.proxy.stop()
+ shutil.rmtree(tempdir)
diff --git a/lib/portage/util/cpuinfo.py b/lib/portage/util/cpuinfo.py
index 669e707b5..78b969f47 100644
--- a/lib/portage/util/cpuinfo.py
+++ b/lib/portage/util/cpuinfo.py
@@ -1,15 +1,44 @@
-# Copyright 2015 Gentoo Foundation
+# Copyright 2015-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
__all__ = ['get_cpu_count']
+# Before you set out to change this function, figure out what you're really
+# asking:
+#
+# - How many CPUs exist in this system (e.g. that the kernel is aware of?)
+# This is 'getconf _NPROCESSORS_CONF' / get_nprocs_conf(3)
+# In modern Linux, implemented by counting CPUs in /sys/devices/system/cpu/
+#
+# - How many CPUs in this system are ONLINE right now?
+# This is 'getconf _NPROCESSORS_ONLN' / get_nprocs(3)
+# In modern Linux, implemented by parsing /sys/devices/system/cpu/online
+#
+# - How many CPUs are available to this program?
+# This is 'nproc' / sched_getaffinity(2), which is implemented in modern
+# Linux kernels by querying the kernel scheduler; This might not be available
+# in some non-Linux systems!
+#
+# - How many CPUs are available to this thread?
+# This is pthread_getaffinity_np(3)
+#
+# As a further warning, the results returned by this function can differ
+# between runs, if altered by the scheduler or other external factors.
def get_cpu_count():
"""
- Try to obtain the number of CPUs available.
+ Try to obtain the number of CPUs available to this process.
@return: Number of CPUs or None if unable to obtain.
"""
+ try:
+ import os
+ # This was introduced in Python 3.3 only, but exists in Linux
+ # all the way back to the 2.5.8 kernel.
+ # This NOT available in FreeBSD!
+ return len(os.sched_getaffinity(0))
+ except (ImportError, NotImplementedError, AttributeError):
+ pass
try:
import multiprocessing
diff --git a/lib/portage/util/futures/executor/fork.py b/lib/portage/util/futures/executor/fork.py
index 72844403c..add7b3c9e 100644
--- a/lib/portage/util/futures/executor/fork.py
+++ b/lib/portage/util/futures/executor/fork.py
@@ -7,13 +7,13 @@ __all__ = (
import collections
import functools
-import multiprocessing
import os
import sys
import traceback
from portage.util._async.AsyncFunction import AsyncFunction
from portage.util.futures import asyncio
+from portage.util.cpuinfo import get_cpu_count
class ForkExecutor(object):
@@ -24,7 +24,7 @@ class ForkExecutor(object):
This is entirely driven by an event loop.
"""
def __init__(self, max_workers=None, loop=None):
- self._max_workers = max_workers or multiprocessing.cpu_count()
+ self._max_workers = max_workers or get_cpu_count()
self._loop = asyncio._wrap_loop(loop)
self._submit_queue = collections.deque()
self._running_tasks = {}
diff --git a/lib/portage/util/futures/iter_completed.py b/lib/portage/util/futures/iter_completed.py
index 31b5e0c78..9554b4338 100644
--- a/lib/portage/util/futures/iter_completed.py
+++ b/lib/portage/util/futures/iter_completed.py
@@ -2,11 +2,11 @@
# Distributed under the terms of the GNU General Public License v2
import functools
-import multiprocessing
from portage.util._async.AsyncTaskFuture import AsyncTaskFuture
from portage.util._async.TaskScheduler import TaskScheduler
from portage.util.futures import asyncio
+from portage.util.cpuinfo import get_cpu_count
def iter_completed(futures, max_jobs=None, max_load=None, loop=None):
@@ -18,11 +18,11 @@ def iter_completed(futures, max_jobs=None, max_load=None, loop=None):
@param futures: iterator of asyncio.Future (or compatible)
@type futures: iterator
@param max_jobs: max number of futures to process concurrently (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_jobs: int
@param max_load: max load allowed when scheduling a new future,
otherwise schedule no more than 1 future at a time (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_load: int or float
@param loop: event loop
@type loop: EventLoop
@@ -47,11 +47,11 @@ def async_iter_completed(futures, max_jobs=None, max_load=None, loop=None):
@param futures: iterator of asyncio.Future (or compatible)
@type futures: iterator
@param max_jobs: max number of futures to process concurrently (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_jobs: int
@param max_load: max load allowed when scheduling a new future,
otherwise schedule no more than 1 future at a time (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_load: int or float
@param loop: event loop
@type loop: EventLoop
@@ -61,8 +61,8 @@ def async_iter_completed(futures, max_jobs=None, max_load=None, loop=None):
"""
loop = asyncio._wrap_loop(loop)
- max_jobs = max_jobs or multiprocessing.cpu_count()
- max_load = max_load or multiprocessing.cpu_count()
+ max_jobs = max_jobs or get_cpu_count()
+ max_load = max_load or get_cpu_count()
future_map = {}
def task_generator():
@@ -120,11 +120,11 @@ def iter_gather(futures, max_jobs=None, max_load=None, loop=None):
@param futures: iterator of asyncio.Future (or compatible)
@type futures: iterator
@param max_jobs: max number of futures to process concurrently (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_jobs: int
@param max_load: max load allowed when scheduling a new future,
otherwise schedule no more than 1 future at a time (default
- is multiprocessing.cpu_count())
+ is portage.util.cpuinfo.get_cpu_count())
@type max_load: int or float
@param loop: event loop
@type loop: EventLoop
diff --git a/lib/portage/util/install_mask.py b/lib/portage/util/install_mask.py
index 32627eb05..037fc8bc3 100644
--- a/lib/portage/util/install_mask.py
+++ b/lib/portage/util/install_mask.py
@@ -1,15 +1,18 @@
-# Copyright 2018 Gentoo Foundation
+# Copyright 2018-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
__all__ = ['install_mask_dir', 'InstallMask']
+import collections
import errno
import fnmatch
+import functools
+import operator
import sys
from portage import os, _unicode_decode
from portage.exception import (
- OperationNotPermitted, PermissionDenied, FileNotFound)
+ OperationNotPermitted, PermissionDenied, ReadOnlyFileSystem, FileNotFound)
from portage.util import normalize_path
if sys.hexversion >= 0x3000000:
@@ -18,13 +21,82 @@ else:
_unicode = unicode
+def _defaultdict_tree():
+ return collections.defaultdict(_defaultdict_tree)
+
+
+_pattern = collections.namedtuple('_pattern', (
+ 'orig_index',
+ 'is_inclusive',
+ 'pattern',
+ 'leading_slash',
+))
+
+
class InstallMask(object):
def __init__(self, install_mask):
"""
@param install_mask: INSTALL_MASK value
@type install_mask: str
"""
- self._install_mask = install_mask.split()
+ # Patterns not anchored with leading slash
+ self._unanchored = []
+
+ # Patterns anchored with leading slash are indexed by leading
+ # non-glob components, making it possible to minimize the
+ # number of fnmatch calls. For example:
+ # /foo*/bar -> {'.': ['/foo*/bar']}
+ # /foo/bar* -> {'foo': {'.': ['/foo/bar*']}}
+ # /foo/bar/ -> {'foo': {'bar': {'.': ['/foo/bar/']}}}
+ self._anchored = _defaultdict_tree()
+ for orig_index, pattern in enumerate(install_mask.split()):
+ # if pattern starts with -, possibly exclude this path
+ is_inclusive = not pattern.startswith('-')
+ if not is_inclusive:
+ pattern = pattern[1:]
+ pattern_obj = _pattern(orig_index, is_inclusive, pattern, pattern.startswith('/'))
+ # absolute path pattern
+ if pattern_obj.leading_slash:
+ current_dir = self._anchored
+ for component in list(filter(None, pattern.split('/'))):
+ if '*' in component:
+ break
+ else:
+ current_dir = current_dir[component]
+ current_dir.setdefault('.', []).append(pattern_obj)
+
+ # filename
+ else:
+ self._unanchored.append(pattern_obj)
+
+ def _iter_relevant_patterns(self, path):
+ """
+ Iterate over patterns that may be relevant for the given path.
+
+ Patterns anchored with leading / are indexed by leading
+ non-glob components, making it possible to minimize the
+ number of fnmatch calls.
+ """
+ current_dir = self._anchored
+ components = list(filter(None, path.split('/')))
+ patterns = []
+ patterns.extend(current_dir.get('.', []))
+ for component in components:
+ next_dir = current_dir.get(component, None)
+ if next_dir is None:
+ break
+ current_dir = next_dir
+ patterns.extend(current_dir.get('.', []))
+
+ if patterns:
+ # Sort by original pattern index, since order matters for
+ # non-inclusive patterns.
+ patterns.extend(self._unanchored)
+ if any(not pattern.is_inclusive for pattern in patterns):
+ patterns.sort(key=operator.attrgetter('orig_index'))
+ return iter(patterns)
+
+ return iter(self._unanchored)
def match(self, path):
"""
@@ -34,13 +106,11 @@ class InstallMask(object):
@return: True if path matches INSTALL_MASK, False otherwise
"""
ret = False
- for pattern in self._install_mask:
- # if pattern starts with -, possibly exclude this path
- is_inclusive = not pattern.startswith('-')
- if not is_inclusive:
- pattern = pattern[1:]
+
+ for pattern_obj in self._iter_relevant_patterns(path):
+ is_inclusive, pattern = pattern_obj.is_inclusive, pattern_obj.pattern
# absolute path pattern
- if pattern.startswith('/'):
+ if pattern_obj.leading_slash:
# handle trailing slash for explicit directory match
if path.endswith('/'):
pattern = pattern.rstrip('/') + '/'
@@ -60,6 +130,7 @@ _exc_map = {
errno.ENOENT: FileNotFound,
errno.EPERM: OperationNotPermitted,
errno.EACCES: PermissionDenied,
+ errno.EROFS: ReadOnlyFileSystem,
}
diff --git a/lib/portage/util/socks5.py b/lib/portage/util/socks5.py
index 74b0714eb..59e6699ec 100644
--- a/lib/portage/util/socks5.py
+++ b/lib/portage/util/socks5.py
@@ -1,13 +1,18 @@
# SOCKSv5 proxy manager for network-sandbox
-# Copyright 2015 Gentoo Foundation
+# Copyright 2015-2019 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
+import errno
import os
import signal
+import socket
+import portage.data
from portage import _python_interpreter
from portage.data import portage_gid, portage_uid, userpriv_groups
from portage.process import atexit_register, spawn
+from portage.util.futures.compat_coroutine import coroutine
+from portage.util.futures import asyncio
class ProxyManager(object):
@@ -36,9 +41,16 @@ class ProxyManager(object):
self.socket_path = os.path.join(settings['PORTAGE_TMPDIR'],
'.portage.%d.net.sock' % os.getpid())
server_bin = os.path.join(settings['PORTAGE_BIN_PATH'], 'socks5-server.py')
+ spawn_kwargs = {}
+ # The portage_uid check solves EPERM failures in Travis CI.
+ if portage.data.secpass > 1 and os.geteuid() != portage_uid:
+ spawn_kwargs.update(
+ uid=portage_uid,
+ gid=portage_gid,
+ groups=userpriv_groups,
+ umask=0o077)
self._pids = spawn([_python_interpreter, server_bin, self.socket_path],
- returnpid=True, uid=portage_uid, gid=portage_gid,
- groups=userpriv_groups, umask=0o077)
+ returnpid=True, **spawn_kwargs)
def stop(self):
"""
@@ -60,6 +72,36 @@ class ProxyManager(object):
return self.socket_path is not None
+ @coroutine
+ def ready(self):
+ """
+ Wait for the proxy socket to become ready. This method is a coroutine.
+ """
+
+ while True:
+ try:
+ wait_retval = os.waitpid(self._pids[0], os.WNOHANG)
+ except OSError as e:
+ if e.errno == errno.EINTR:
+ continue
+ raise
+
+ if wait_retval is not None and wait_retval != (0, 0):
+ raise OSError(3, 'No such process')
+
+ try:
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.socket_path)
+ except EnvironmentError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ yield asyncio.sleep(0.2)
+ else:
+ break
+ finally:
+ s.close()
+
+
proxy = ProxyManager()
diff --git a/man/make.conf.5 b/man/make.conf.5
index 523501300..35fe6a4ab 100644
--- a/man/make.conf.5
+++ b/man/make.conf.5
@@ -1,4 +1,4 @@
-.TH "MAKE.CONF" "5" "Nov 2018" "Portage VERSION" "Portage"
+.TH "MAKE.CONF" "5" "Feb 2019" "Portage VERSION" "Portage"
.SH "NAME"
make.conf \- custom settings for Portage
.SH "SYNOPSIS"
@@ -64,7 +64,7 @@ file (see \fBportage\fR(5)). In addition to license and group names, the
for further information:
\fIhttps://www.gentoo.org/glep/glep-0023.html\fR.
.br
-Defaults to the value of * -@EULA.
+Defaults to the value defined in the profile.
.br
.I Examples:
.nf
@@ -72,7 +72,7 @@ Defaults to the value of * -@EULA.
ACCEPT_LICENSE="-* @FREE"
# As before, but exclude the "Artistic" license
ACCEPT_LICENSE="-* @FREE -Artistic"
-# Accept any license except those in the EULA license group (default)
+# Accept any license except those in the EULA license group
ACCEPT_LICENSE="* -@EULA"
.fi
.TP
diff --git a/setup.py b/setup.py
index b93e2dae0..1fbbffd5e 100755
--- a/setup.py
+++ b/setup.py
@@ -662,7 +662,7 @@ class build_ext(_build_ext):
setup(
name = 'portage',
- version = '2.3.55',
+ version = '2.3.62',
url = 'https://wiki.gentoo.org/wiki/Project:Portage',
author = 'Gentoo Portage Development Team',
author_email = 'dev-portage@gentoo.org',