#!/bin/bash # Copyright 1999-2014 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 # # Miscellaneous shell functions that make use of the ebuild env but don't need # to be included directly in ebuild.sh. # # We're sourcing ebuild.sh here so that we inherit all of it's goodness, # including bashrc trickery. This approach allows us to do our miscellaneous # shell work within the same env that ebuild.sh has, but without polluting # ebuild.sh itself with unneeded logic and shell code. # # XXX hack: clear the args so ebuild.sh doesn't see them setglobal MISC_FUNCTIONS_ARGS = @Argv shift $Argc source "$(PORTAGE_BIN_PATH)/ebuild.sh" || exit 1 proc install_symlink_html_docs { if ! ___eapi_has_prefix_variables { local ED=$(D) } cd $(ED) || die "cd failed" #symlink the html documentation (if DOC_SYMLINKS_DIR is set in make.conf) if test -n $(DOC_SYMLINKS_DIR) { local mydocdir docdir for docdir in [$(HTMLDOC_DIR:-does/not/exist) "$(PF)/html" "$(PF)/HTML" "$(P)/html" "$(P)/HTML]" { if test -d "usr/share/doc/$(docdir)" { setglobal mydocdir = ""/usr/share/doc/$(docdir)"" } } if test -n $(mydocdir) { local mysympath if test -z $(SLOT) -o $(SLOT%/*) = "0" { setglobal mysympath = ""$(DOC_SYMLINKS_DIR)/$(CATEGORY)/$(PN)"" } else { setglobal mysympath = ""$(DOC_SYMLINKS_DIR)/$(CATEGORY)/$(PN)-$(SLOT%/*)"" } einfo "Symlinking $(mysympath) to the HTML documentation" dodir "$(DOC_SYMLINKS_DIR)/$(CATEGORY)" dosym $(mydocdir) $(mysympath) } } } # replacement for "readlink -f" or "realpath" setglobal READLINK_F_WORKS = ''"" proc canonicalize { if [[ -z ${READLINK_F_WORKS} ]] { if [[ $(readlink -f -- /../ 2>/dev/null) == "/" ]] { setglobal READLINK_F_WORKS = 'true' } else { setglobal READLINK_F_WORKS = 'false' } } if $(READLINK_F_WORKS) { readlink -f -- @Argv return } local f=$1 b n=10 wd=$[pwd] while sh-expr ' n-- > 0 ' { { setglobal f = $(f%/) } setglobal b = $(f##*/) cd $(f%"${b}") !2 >/dev/null || break if [[ ! -L ${b} ]] { setglobal f = $[pwd -P] echo "$(f%/)/$(b)" cd $(wd) return 0 } setglobal f = $[readlink $(b)] } cd $(wd) return 1 } proc prepcompress { local -a include exclude incl_d incl_f local f g i real_f real_d if ! ___eapi_has_prefix_variables { local ED=$(D) } # Canonicalize path names and check for their existence. setglobal real_d = $[canonicalize $(ED)] for (( i = 0; i < ${#PORTAGE_DOCOMPRESS[@]}; i++ )); do real_f=$(canonicalize "${ED}${PORTAGE_DOCOMPRESS[i]}") f=${real_f#"${real_d}"} if [[ ${real_f} != "${f}" ]] && [[ -d ${real_f} || -f ${real_f} ]] then include[${#include[@]}]=${f:-/} elif [[ ${i} -ge 3 ]]; then ewarn "prepcompress:" \ "ignoring nonexistent path '${PORTAGE_DOCOMPRESS[i]}'" fi done for (( i = 0; i < ${#PORTAGE_DOCOMPRESS_SKIP[@]}; i++ )); do real_f=$(canonicalize "${ED}${PORTAGE_DOCOMPRESS_SKIP[i]}") f=${real_f#"${real_d}"} if [[ ${real_f} != "${f}" ]] && [[ -d ${real_f} || -f ${real_f} ]] then exclude[${#exclude[@]}]=${f:-/} elif [[ ${i} -ge 1 ]]; then ewarn "prepcompress:" \ "ignoring nonexistent path '${PORTAGE_DOCOMPRESS_SKIP[i]}'" fi done # Remove redundant entries from lists. # For the include list, remove any entries that are: # a) contained in a directory in the include or exclude lists, or # b) identical with an entry in the exclude list. for (( i = ${#include[@]} - 1; i >= 0; i-- )); do f=${include[i]} for g in "${include[@]}"; do if [[ ${f} == "${g%/}"/* ]]; then unset include[i] continue 2 fi done for g in "${exclude[@]}"; do if [[ ${f} = "${g}" || ${f} == "${g%/}"/* ]]; then unset include[i] continue 2 fi done done # For the exclude list, remove any entries that are: # a) contained in a directory in the exclude list, or # b) _not_ contained in a directory in the include list. for (( i = ${#exclude[@]} - 1; i >= 0; i-- )); do f=${exclude[i]} for g in "${exclude[@]}"; do if [[ ${f} == "${g%/}"/* ]]; then unset exclude[i] continue 2 fi done for g in "${include[@]}"; do [[ ${f} == "${g%/}"/* ]] && continue 2 done unset exclude[i] done # Split the include list into directories and files for f in [$(include[@])] { if [[ -d ${ED}${f} ]] { compat array-assign incl_d '${#incl_d[@]}' $(f) } else { compat array-assign incl_f '${#incl_f[@]}' $(f) } } # Queue up for compression. # ecompress{,dir} doesn't like to be called with empty argument lists. [[ ${#incl_d[@]} -gt 0 ]] && ecompressdir --limit $(PORTAGE_DOCOMPRESS_SIZE_LIMIT:-0) --queue $(incl_d[@]) [[ ${#incl_f[@]} -gt 0 ]] && ecompress --queue $(incl_f[@]/#/${ED}) [[ ${#exclude[@]} -gt 0 ]] && ecompressdir --ignore $(exclude[@]) return 0 } proc install_qa_check { local d f i qa_var x paths qa_checks=() checks_run=() if ! ___eapi_has_prefix_variables { local EPREFIX= ED=$(D) } cd $(ED) || die "cd failed" # Collect the paths for QA checks, highest prio first. setglobal paths = ''( # sysadmin overrides "${PORTAGE_OVERRIDE_EPREFIX}"/usr/local/lib/install-qa-check.d # system-wide package installs "${PORTAGE_OVERRIDE_EPREFIX}"/usr/lib/install-qa-check.d ) # Now repo-specific checks. # (yes, PORTAGE_ECLASS_LOCATIONS contains repo paths...) for d in [$(PORTAGE_ECLASS_LOCATIONS[@])] { setglobal paths = ''( "${d}"/metadata/install-qa-check.d ) } setglobal paths = ''( # Portage built-in checks "${PORTAGE_OVERRIDE_EPREFIX}"/usr/lib/portage/install-qa-check.d "${PORTAGE_BIN_PATH}"/install-qa-check.d ) # Collect file names of QA checks. We need them early to support # overrides properly. for d in [$(paths[@])] { for f in ["$(d)"/*] { [[ -f ${f} ]] && setglobal qa_checks = ''( "${f##*/}" ) } } # Now we need to sort the filenames lexically, and process # them in order. while read -r -d '' f { # Find highest priority file matching the basename. for d in [$(paths[@])] { [[ -f ${d}/${f} ]] && break } # Run in a subshell to treat it like external script, # but use 'source' to pass all variables through. shell { # Allow inheriting eclasses. # XXX: we want this only in repository-wide checks. setglobal _IN_INSTALL_QA_CHECK = '1' source "$(d)/$(f)" || eerror "Post-install QA check $(f) failed to run" } } < <(printf "%s\0" "${qa_checks[@]}" | LC_ALL=C sort -u -z) export STRIP_MASK prepall ___eapi_has_docompress && prepcompress ecompressdir --dequeue ecompress --dequeue # 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 # containing pre-built binaries. if type -P scanelf > /dev/null { # Save NEEDED information after removing self-contained providers rm -f "$PORTAGE_BUILDDIR"/build-info/NEEDED{,.ELF.2} scanelf -qyRF '%a;%p;%S;%r;%n' $(D) | do { while env IFS= read -r l { setglobal arch = $(l%%;*); setglobal l = $(l#*;) setglobal obj = ""/$(l%%;*)""; setglobal l = $(l#*;) setglobal soname = $(l%%;*); setglobal l = $(l#*;) setglobal rpath = $(l%%;*); setglobal l = $(l#*;); test $(rpath) = " - " && setglobal rpath = ''"" setglobal needed = $(l%%;*); setglobal l = $(l#*;) echo "$(obj) $(needed)" >> "$(PORTAGE_BUILDDIR)"/build-info/NEEDED echo "$(arch:3);$(obj);$(soname);$(rpath);$(needed)" >> "$(PORTAGE_BUILDDIR)"/build-info/NEEDED.ELF.2 } } test -n $(QA_SONAME_NO_SYMLINK) && \ echo $(QA_SONAME_NO_SYMLINK) > \ "$(PORTAGE_BUILDDIR)"/build-info/QA_SONAME_NO_SYMLINK if has binchecks $(RESTRICT) && \ test -s "$(PORTAGE_BUILDDIR)/build-info/NEEDED.ELF.2" { eqawarn "QA Notice: RESTRICT=binchecks prevented checks on these ELF files:" eqawarn $[while read -r x { setglobal x = $(x#*;) ; setglobal x = $(x%%;*) ; echo $(x#${EPREFIX}) ; }] } } # Portage regenerates this on the installed system. rm -f "${ED}"/usr/share/info/dir{,.gz,.bz2} || die "rm failed!" } proc postinst_qa_check { local d f paths qa_checks=() if ! ___eapi_has_prefix_variables { local EPREFIX= EROOT=$(ROOT) } cd $(EROOT) || die "cd failed" # Collect the paths for QA checks, highest prio first. setglobal paths = ''( # sysadmin overrides "${PORTAGE_OVERRIDE_EPREFIX}"/usr/local/lib/postinst-qa-check.d # system-wide package installs "${PORTAGE_OVERRIDE_EPREFIX}"/usr/lib/postinst-qa-check.d ) # Now repo-specific checks. # (yes, PORTAGE_ECLASS_LOCATIONS contains repo paths...) for d in [$(PORTAGE_ECLASS_LOCATIONS[@])] { setglobal paths = ''( "${d}"/metadata/postinst-qa-check.d ) } setglobal paths = ''( # Portage built-in checks "${PORTAGE_OVERRIDE_EPREFIX}"/usr/lib/portage/postinst-qa-check.d "${PORTAGE_BIN_PATH}"/postinst-qa-check.d ) # Collect file names of QA checks. We need them early to support # overrides properly. for d in [$(paths[@])] { for f in ["$(d)"/*] { [[ -f ${f} ]] && setglobal qa_checks = ''( "${f##*/}" ) } } # Now we need to sort the filenames lexically, and process # them in order. while read -r -d '' f { # Find highest priority file matching the basename. for d in [$(paths[@])] { [[ -f ${d}/${f} ]] && break } # Run in a subshell to treat it like external script, # but use 'source' to pass all variables through. shell { # Allow inheriting eclasses. # XXX: we want this only in repository-wide checks. setglobal _IN_INSTALL_QA_CHECK = '1' source "$(d)/$(f)" || eerror "Post-postinst QA check $(f) failed to run" } } < <(printf "%s\0" "${qa_checks[@]}" | LC_ALL=C sort -u -z) } proc install_mask { local root="$1" shift local install_mask="$ifsjoin(Argv)" # We think of $install_mask as a space-separated list of # globs. We don't want globbing in the "for" loop; that is, we # want to keep the asterisks in the indivual entries. local shopts=$Flags set -o noglob local no_inst for no_inst in [$(install_mask)] { # Here, $no_inst is a single "entry" potentially # containing a glob. From now on, we *do* want to # expand it. set +o noglob # The standard case where $no_inst is something that # the shell could expand on its own. if [[ -e "${root}"/${no_inst} || -L "${root}"/${no_inst} || "${root}"/${no_inst} != $(echo "${root}"/${no_inst}) ]] { __quiet_mode || einfo "Removing $(no_inst)" rm -Rf "$(root)"/$(no_inst) > !/dev/null } # We also want to allow the user to specify a "bare # glob." For example, $no_inst="*.a" should prevent # ALL files ending in ".a" from being installed, # regardless of their location/depth. We achieve this # by passing the pattern to `find`. find $(root) '(' -path $(no_inst) -or -name $(no_inst) ')' \ -print0 !2 > /dev/null \ | env LC_ALL=C sort -z \ | while read -r -d { __quiet_mode || einfo "Removing /$(REPLY#${root})" rm -Rf $(REPLY) > !/dev/null } } # set everything back the way we found it set +o noglob set -$(shopts) } proc preinst_mask { if test -z $(D) { eerror "$(FUNCNAME): D is unset" return 1 } if ! ___eapi_has_prefix_variables { local ED=$(D) } # Make sure $PWD is not ${D} so that we don't leave gmon.out files # in there in case any tools were built with -pg in CFLAGS. cd $(T) # remove man pages, info pages, docs if requested local f for f in [man info doc] { if has no$(f) $FEATURES { setglobal INSTALL_MASK = ""$(INSTALL_MASK) /usr/share/$(f)"" } } install_mask $(ED) $(INSTALL_MASK) # remove share dir if unnessesary if has nodoc $FEATURES || has noman $FEATURES || has noinfo $FEATURES { rmdir "$(ED)usr/share" &> /dev/null } } proc preinst_sfperms { if test -z $(D) { eerror "$(FUNCNAME): D is unset" return 1 } if ! ___eapi_has_prefix_variables { local ED=$(D) } # Smart FileSystem Permissions if has sfperms $FEATURES { local i find $(ED) -type f -perm -4000 -print0 | \ while read -r -d $'\0' i { if test -n $[find $i -perm -2000] { ebegin ">>> SetUID and SetGID: [chmod o-r] /$(i#${ED})" chmod o-r $i eend $Status } else { ebegin ">>> SetUID: [chmod go-r] /$(i#${ED})" chmod go-r $i eend $Status } } find $(ED) -type f -perm -2000 -print0 | \ while read -r -d $'\0' i { if test -n $[find $i -perm -4000] { # This case is already handled # by the SetUID check above. true } else { ebegin ">>> SetGID: [chmod o-r] /$(i#${ED})" chmod o-r $i eend $Status } } } } proc preinst_suid_scan { if test -z $(D) { eerror "$(FUNCNAME): D is unset" return 1 } if ! ___eapi_has_prefix_variables { local ED=$(D) } # total suid control. if has suidctl $FEATURES { local i sfconf x setglobal sfconf = "$(PORTAGE_CONFIGROOT)etc/portage/suidctl.conf" # sandbox prevents us from writing directly # to files outside of the sandbox, but this # can easly be bypassed using the addwrite() function addwrite $(sfconf) __vecho ">>> Performing suid scan in $(ED)" for i in [$[find $(ED) -type f '(' -perm -4000 -o -perm -2000 ')']] { if test -s $(sfconf) { setglobal install_path = "/$(i#${ED})" if grep -q "^$(install_path)\$" $(sfconf) { __vecho "- $(install_path) is an approved suid file" } else { __vecho ">>> Removing sbit on non registered $(install_path)" env LC_ALL=C sleep 1.5 setglobal ls_ret = $[ls -ldh $(i)] chmod ugo-s $(i) grep "^#$(install_path)$" $(sfconf) > /dev/null || do { __vecho ">>> Appending commented out entry to $(sfconf) for $(PF)" echo "## $(ls_ret%${ED}*)$(install_path)" >> $(sfconf) echo "#$(install_path)" >> $(sfconf) # no delwrite() eh? # delwrite ${sconf} } } } else { __vecho "suidctl feature set but you are lacking a $(sfconf)" } } } } proc preinst_selinux_labels { if test -z $(D) { eerror "$(FUNCNAME): D is unset" return 1 } if has selinux $(FEATURES) { # SELinux file labeling (needs to execute after preinst) # only attempt to label if setfiles is executable # and 'context' is available on selinuxfs. if test -f /selinux/context -o -f /sys/fs/selinux/context && \ test -x /usr/sbin/setfiles -a -x /usr/sbin/selinuxconfig { __vecho ">>> Setting SELinux security labels" shell { eval $[/usr/sbin/selinuxconfig] || \ die "Failed to determine SELinux policy paths."; addwrite /selinux/context addwrite /sys/fs/selinux/context /usr/sbin/setfiles -F $(file_contexts_path) -r $(D) $(D) } || die "Failed to set SELinux security labels." } else { # nonfatal, since merging can happen outside a SE kernel # like during a recovery situation __vecho "!!! Unable to set SELinux security labels" } } } proc __dyn_package { local PROOT if ! ___eapi_has_prefix_variables { local EPREFIX= ED=$(D) } # Make sure $PWD is not ${D} so that we don't leave gmon.out files # in there in case any tools were built with -pg in CFLAGS. cd $(T) || die if [[ -n ${PKG_INSTALL_MASK} ]] { setglobal PROOT = "$(T)/packaging/" # make a temporary copy of ${D} so that any modifications we do that # are binpkg specific, do not influence the actual installed image. rm -rf $(PROOT) || die "failed removing stale package tree" cp -pPR $[cp --help | grep -qs -e-l && echo -l] \ $(D) $(PROOT) \ || die "failed creating packaging tree" install_mask "$(PROOT%/)$(EPREFIX)/" $(PKG_INSTALL_MASK) } else { setglobal PROOT = $(D) } local tar_options="" [[ $PORTAGE_VERBOSE = 1 ]] && setglobal tar_options = '" -v'" has xattr $(FEATURES) && [[ $(tar --help 2> /dev/null) == *--xattrs* ]] && setglobal tar_options = '" --xattrs'" # Sandbox is disabled in case the user wants to use a symlink # for $PKGDIR and/or $PKGDIR/All. export SANDBOX_ON="0" test -z $(PORTAGE_BINPKG_TMPFILE) && \ die "PORTAGE_BINPKG_TMPFILE is unset" mkdir -p $(PORTAGE_BINPKG_TMPFILE%/*) || die "mkdir failed" test -z $(PORTAGE_COMPRESSION_COMMAND) && \ die "PORTAGE_COMPRESSION_COMMAND is unset" tar $tar_options -cf - $PORTAGE_BINPKG_TAR_OPTS -C $(PROOT) . | \ $PORTAGE_COMPRESSION_COMMAND -c > $PORTAGE_BINPKG_TMPFILE assert "failed to pack binary package: '$PORTAGE_BINPKG_TMPFILE'" env PYTHONPATH=$(PORTAGE_PYTHONPATH:-${PORTAGE_PYM_PATH}) \ $(PORTAGE_PYTHON:-/usr/bin/python) "$PORTAGE_BIN_PATH"/xpak-helper.py recompose \ $PORTAGE_BINPKG_TMPFILE "$PORTAGE_BUILDDIR/build-info" if test $Status -ne 0 { rm -f $(PORTAGE_BINPKG_TMPFILE) die "Failed to append metadata to the tbz2 file" } local md5_hash="" if type md5sum &>/dev/null { setglobal md5_hash = $[md5sum $(PORTAGE_BINPKG_TMPFILE)] setglobal md5_hash = $(md5_hash%% *) } elif type md5 &>/dev/null { setglobal md5_hash = $[md5 $(PORTAGE_BINPKG_TMPFILE)] setglobal md5_hash = $(md5_hash##* ) } test -n $(md5_hash) && \ echo $(md5_hash) > "$(PORTAGE_BUILDDIR)"/build-info/BINPKGMD5 __vecho ">>> Done." # cleanup our temp tree [[ -n ${PKG_INSTALL_MASK} ]] && rm -rf $(PROOT) cd $(PORTAGE_BUILDDIR) >> "$PORTAGE_BUILDDIR/.packaged" || \ die "Failed to create $PORTAGE_BUILDDIR/.packaged" } proc __dyn_spec { local sources_dir=$(T)/rpmbuild/SOURCES mkdir -p $(sources_dir) declare -a tar_args=("${EBUILD}") [[ -d ${FILESDIR} ]] && setglobal tar_args = ''("${EBUILD}" "${FILESDIR}") tar czf "$(sources_dir)/$(PF).tar.gz" \ $(tar_args[@]) || \ die "Failed to create base rpm tarball." cat << """ > ${PF}.spec Summary: $(DESCRIPTION) Name: $(PN) Version: $(PV) Release: $(PR) License: GPL Group: portage/$(CATEGORY) Source: $(PF).tar.gz %description $(DESCRIPTION) $(HOMEPAGE) %prep %setup -c %build %install %clean %files / """ > $(PF).spec Summary: ${DESCRIPTION} Name: ${PN} Version: ${PV} Release: ${PR} License: GPL Group: portage/${CATEGORY} Source: ${PF}.tar.gz %description ${DESCRIPTION} ${HOMEPAGE} %prep %setup -c %build %install %clean %files / __END1__ } proc __dyn_rpm { if ! ___eapi_has_prefix_variables { local EPREFIX= } cd $(T) || die "cd failed" local machine_name=$(CHOST%%-*) local dest_dir=$(T)/rpmbuild/RPMS/$(machine_name) addwrite $(RPMDIR) __dyn_spec env HOME=$(T) \ rpmbuild -bb --clean --nodeps --rmsource "$(PF).spec" --buildroot $(D) --target $(CHOST) || die "Failed to integrate rpm spec file" install -D "$(dest_dir)/$(PN)-$(PV)-$(PR).$(machine_name).rpm" \ "$(RPMDIR)/$(CATEGORY)/$(PN)-$(PV)-$(PR).rpm" || \ die "Failed to move rpm" } proc die_hooks { [[ -f $PORTAGE_BUILDDIR/.die_hooks ]] && return local x for x in [$EBUILD_DEATH_HOOKS] { $x > !2 } > "$PORTAGE_BUILDDIR/.die_hooks" } proc success_hooks { local x for x in [$EBUILD_SUCCESS_HOOKS] { $x } } proc install_hooks { local hooks_dir="$(PORTAGE_CONFIGROOT)etc/portage/hooks/install" local fp local ret=0 shopt -s nullglob for fp in ["$(hooks_dir)"/*] { if test -x $fp { $fp setglobal ret = $shExpr(' $ret | $? ') } } shopt -u nullglob return $ret } proc eqatag { __eqatag $(@) } if test -n $(MISC_FUNCTIONS_ARGS) { __source_all_bashrcs test $PORTAGE_DEBUG == "1" && set -x for x in [$(MISC_FUNCTIONS_ARGS)] { $(x) } unset x [[ -n $PORTAGE_EBUILD_EXIT_FILE ]] && > $PORTAGE_EBUILD_EXIT_FILE if [[ -n $PORTAGE_IPC_DAEMON ]] { [[ ! -s $SANDBOX_LOG ]] "$PORTAGE_BIN_PATH"/ebuild-ipc exit $Status } } :