Skip to content

Commit

Permalink
Do not export PMS variables in EAPI 9 (or if FEATURES=-export-pms-vars)
Browse files Browse the repository at this point in the history
Instead of passing PMS variables as part of the process environment,
we pass them via a file that is later sourced by the ebuild's
bash.

Since, for example, A is usually the greatest contributor to the
process environment, removing it from the process environment
significantly avoids running into MAX_ARG_STRLEN when spawning a new
child process.

This means that A and other PMS variables are no longer exported in
the ebuild and hence unavaiable to child processes. However, A is
mostly used as part of the default_src_unpack function and there A
does not need to be exported.

This started as a change that only unexported A, but was later
extended to most PMS variables as suggested by ulm in
https://bugs.gentoo.org/721088#c23.

Thanks to Zac Medico for helpful input on this change, and to Eli
Schwartz for suggesting that (bash) helpers should simply source the
environment file introduced by this change.

Closes: https://bugs.gentoo.org/721088
Signed-off-by: Florian Schmaus <[email protected]>
  • Loading branch information
Flowdalic committed Jan 9, 2025
1 parent 6f958be commit 6e5e977
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 3 deletions.
9 changes: 9 additions & 0 deletions bin/isolated-functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ if ___eapi_has_version_functions; then
source "${PORTAGE_BIN_PATH}/eapi7-ver-funcs.sh" || exit 1
fi

if [[ -v PORTAGE_EBUILD_EXTRA_SOURCE ]]; then
source "${PORTAGE_EBUILD_EXTRA_SOURCE}" || exit 1
# We delierbately do not unset PORTABE_EBUILD_EXTRA_SOURCE, so
# that it keeps being exported in the environment of this
# process and its child processeses. There, for example portage
# helper like doins, can pick it up and set the PMS variables
# (usually by sourcing isolated-functions.sh).
fi

# We need this next line for "die" and "assert". It expands
# It _must_ preceed all the calls to die and assert.
shopt -s expand_aliases
Expand Down
1 change: 1 addition & 0 deletions bin/phase-functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ PORTAGE_READONLY_VARS="D EBUILD EBUILD_PHASE EBUILD_PHASE_FUNC \
PORTAGE_BUILD_USER PORTAGE_BUNZIP2_COMMAND \
PORTAGE_BZIP2_COMMAND PORTAGE_COLORMAP PORTAGE_CONFIGROOT \
PORTAGE_DEBUG PORTAGE_DEPCACHEDIR PORTAGE_EBUILD_EXIT_FILE \
PORTAGE_EBUILD_EXTRA_SOURCE \
PORTAGE_ECLASS_LOCATIONS PORTAGE_EXPLICIT_INHERIT \
PORTAGE_GID PORTAGE_GRPNAME PORTAGE_INST_GID PORTAGE_INST_UID \
PORTAGE_INTERNAL_CALLER PORTAGE_IPC_DAEMON PORTAGE_IUSE PORTAGE_LOG_FILE \
Expand Down
2 changes: 1 addition & 1 deletion cnf/make.globals
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ FEATURES="assume-digests binpkg-docompress binpkg-dostrip binpkg-logs
network-sandbox news parallel-fetch pkgdir-index-trusted pid-sandbox
preserve-libs protect-owned qa-unresolved-soname-deps sandbox strict
unknown-features-warn unmerge-logs unmerge-orphans userfetch
userpriv usersandbox usersync"
userpriv usersandbox usersync export-pms-vars"

# Ignore file collisions in /lib/modules since files inside this directory
# are never unmerged, and therefore collisions must be ignored in order for
Expand Down
6 changes: 6 additions & 0 deletions lib/_emerge/EbuildMetadataPhase.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class EbuildMetadataPhase(SubProcess):
"repo_path",
"settings",
"deallocate_config",
"portage_ebuild_extra_source",
"write_auxdb",
) + (
"_eapi",
Expand Down Expand Up @@ -165,6 +166,9 @@ def _async_start_done(self, future):
self.cancel()
self._was_cancelled()

self.portage_ebuild_extra_source = self.settings.get(
"PORTAGE_EBUILD_EXTRA_SOURCE"
)
if self.deallocate_config is not None and not self.deallocate_config.done():
self.deallocate_config.set_result(self.settings)

Expand All @@ -191,6 +195,8 @@ def _unregister(self):
if self._files is not None:
self.scheduler.remove_reader(self._files.ebuild)
SubProcess._unregister(self)
if self.portage_ebuild_extra_source:
os.unlink(self.portage_ebuild_extra_source)

def _async_waitpid_cb(self, *args, **kwargs):
"""
Expand Down
1 change: 1 addition & 0 deletions lib/portage/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@
"distlocks",
"downgrade-backup",
"ebuild-locks",
"export-pms-vars",
"fail-clean",
"fakeroot",
"fixlafiles",
Expand Down
10 changes: 9 additions & 1 deletion lib/portage/eapi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2010-2021 Gentoo Authors
# Copyright 2010-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import collections
Expand Down Expand Up @@ -48,6 +48,10 @@ def eapi_supports_prefix(eapi: str) -> bool:
return _get_eapi_attrs(eapi).prefix


def eapi_exports_pms_vars(eapi: str) -> bool:
return _get_eapi_attrs(eapi).exports_pms_vars


def eapi_exports_AA(eapi: str) -> bool:
return _get_eapi_attrs(eapi).exports_AA

Expand Down Expand Up @@ -157,6 +161,7 @@ def eapi_has_sysroot(eapi: str) -> bool:
"exports_ECLASSDIR",
"exports_KV",
"exports_merge_type",
"exports_pms_vars",
"exports_PORTDIR",
"exports_replace_vars",
"feature_flag_test",
Expand Down Expand Up @@ -198,6 +203,7 @@ class Eapi:
"6",
"7",
"8",
"9",
)

_eapi_val: int = -1
Expand Down Expand Up @@ -236,6 +242,7 @@ def _get_eapi_attrs(eapi_str: Optional[str]) -> _eapi_attrs:
exports_ECLASSDIR=False,
exports_KV=False,
exports_merge_type=True,
exports_pms_vars=True,
exports_PORTDIR=True,
exports_replace_vars=True,
feature_flag_test=False,
Expand Down Expand Up @@ -275,6 +282,7 @@ def _get_eapi_attrs(eapi_str: Optional[str]) -> _eapi_attrs:
exports_ECLASSDIR=eapi <= Eapi("6"),
exports_KV=eapi <= Eapi("3"),
exports_merge_type=eapi >= Eapi("4"),
exports_pms_vars=eapi <= Eapi("8"),
exports_PORTDIR=eapi <= Eapi("6"),
exports_replace_vars=eapi >= Eapi("4"),
feature_flag_test=False,
Expand Down
102 changes: 101 additions & 1 deletion lib/portage/package/ebuild/doebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
from portage.eapi import (
eapi_exports_KV,
eapi_exports_merge_type,
eapi_exports_pms_vars,
eapi_exports_replace_vars,
eapi_has_required_use,
eapi_has_src_prepare_and_src_configure,
Expand Down Expand Up @@ -189,6 +190,57 @@
"RESTRICT",
)

# The following is a set of PMS § 11.1 and § 7.4 without
# - TMPDIR
# - HOME
# because these variables are often assumed to be exported and
# therefore consumed by child processes.
_unexported_pms_vars = frozenset(
# fmt: off
[
# PMS § 11.1 Defined Variables
"P", # NOT-EXPORTED: tendency to break Makefiles when exported
"PF",
"PN",
"CATEGORY",
"PV",
"PR",
"PVR",
"A", # NOT-EXPORTED: largest contributor to process environment when exported
"AA", # NOT-EXPORTED: unused after EAPI 4
"FILESDIR",
"DISTDIR",
"WORKDIR",
"S",
"PORTDIR",
"ECLASSDIR",
"ROOT",
"EROOT",
"SYSROOT",
"ESYSROOT",
"BROOT",
"T",
# "TMPDIR", # EXPORTED: often assumed to be exported and available to child processes
# "HOME", # EXPORTED: often assumed to be exported and available to child processes
"EPREFIX",
"D", # NOT-EXPORTED: tendency to break Makefiles when exported
"ED",
"DESTTREE",
"INSDESTTREE",
"EBUILD_PHASE",
"EBUILD_PHASE_FUNC",
"KV",
"MERGE_TYPE",
"REPLACING_VERSIONS",
"REPLACED_BY_VERSION",
# PMS 7.4 Magic Ebuild-defined Variables
"ECLASS",
"INHERITED",
"DEFINED_PHASES",
]
# fmt: on
)


def _doebuild_spawn(phase, settings, actionmap=None, **kwargs):
"""
Expand Down Expand Up @@ -2133,9 +2185,57 @@ def spawn(
logname_backup = mysettings.configdict["env"].get("LOGNAME")
mysettings.configdict["env"]["LOGNAME"] = logname

eapi = mysettings["EAPI"]

unexported_env_vars = None
if "export-pms-vars" not in mysettings.features or not eapi_exports_pms_vars(eapi):
unexported_env_vars = _unexported_pms_vars

if unexported_env_vars:
# Starting with EAPI 9 (or if FEATURES="-export-pms-vars"),
# PMS variables should not longer be exported.
orig_env = mysettings.environ()
# Copy since we are potentially removing keys from the dict.
env = orig_env.copy()

t = env["T"]
# The os.path.isdir() is probably not required, as T should
# exists if we are processing a phase that is in
# _phase_func_map. But better safe than sorry.
if mysettings.get("EBUILD_PHASE") in _phase_func_map.keys() and os.path.isdir(
t
):
ebuild_extra_source_path = os.path.join(t, ".portage-ebuild-extra-source")
else:
ebuild_extra_source_fd, ebuild_extra_source_path = tempfile.mkstemp(
prefix="portage-ebuild-extra-source-",
)
# Make sure that the file is writeable by us (see below)
# and that it is world readable.
os.fchmod(
ebuild_extra_source_fd,
stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH,
)
os.close(ebuild_extra_source_fd)
# The file will be deleted by EbuildMetadataPhase._unregister()
mysettings["PORTAGE_EBUILD_EXTRA_SOURCE"] = ebuild_extra_source_path

with open(ebuild_extra_source_path, mode="w") as f:
for var_name in unexported_env_vars:
var_value = orig_env.get(var_name)
if var_value is None:
continue
quoted_var_value = shlex.quote(var_value)
f.write(f"{var_name}={quoted_var_value}\n")
del env[var_name]

env["PORTAGE_EBUILD_EXTRA_SOURCE"] = str(ebuild_extra_source_path)
else:
env = mysettings.environ()

try:
if keywords.get("returnpid") or keywords.get("returnproc"):
return spawn_func(mystring, env=mysettings.environ(), **keywords)
return spawn_func(mystring, env=env, **keywords)

proc = EbuildSpawnProcess(
background=False,
Expand Down

0 comments on commit 6e5e977

Please sign in to comment.