diff options
Diffstat (limited to 'eclass/cdrom.eclass')
-rw-r--r-- | eclass/cdrom.eclass | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/eclass/cdrom.eclass b/eclass/cdrom.eclass new file mode 100644 index 000000000000..47e2c6342e06 --- /dev/null +++ b/eclass/cdrom.eclass @@ -0,0 +1,302 @@ +# Copyright 1999-2017 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +# @ECLASS: cdrom.eclass +# @MAINTAINER: +# games@gentoo.org +# @BLURB: Functions for CD-ROM handling +# @DESCRIPTION: +# Acquire CD(s) for those lovely CD-based emerges. Yes, this violates +# the whole "non-interactive" policy, but damnit I want CD support! +# +# Do not call these functions in pkg_* phases like pkg_setup as they +# should not be used for binary packages. Most packages using this +# eclass will require RESTRICT="bindist" but the point still stands. +# The functions are generally called in src_unpack. + +if [[ -z ${_CDROM_ECLASS} ]]; then +_CDROM_ECLASS=1 + +inherit portability + +# @ECLASS-VARIABLE: CDROM_OPTIONAL +# @DEFAULT_UNSET +# @DESCRIPTION: +# By default, the eclass sets PROPERTIES="interactive" on the assumption +# that people will be using these. If your package optionally supports +# disc-based installs then set this to "yes" and we'll set things +# conditionally based on USE="cdinstall". +if [[ ${CDROM_OPTIONAL} == "yes" ]] ; then + IUSE="cdinstall" + PROPERTIES="cdinstall? ( interactive )" +else + PROPERTIES="interactive" +fi + +# @FUNCTION: cdrom_get_cds +# @USAGE: <cd1 file>[:alt cd1 file] [cd2 file[:alt cd2 file]] [...] +# @DESCRIPTION: +# Attempt to locate a CD based upon a file that is on the CD. +# +# If the data spans multiple discs then additional arguments can be +# given to check for more files. Call cdrom_load_next_cd() to scan for +# the next disc in the set. +# +# Sometimes it is necessary to support alternative CD "sets" where the +# contents differ. Alternative files for each disc can be appended to +# each argument, separated by the : character. This feature is +# frequently used to support installing from an existing installation. +# Note that after the first disc is detected, the set is locked so +# cdrom_load_next_cd() will only scan for files in that specific set on +# subsequent discs. +# +# The given files can be within named subdirectories. It is not +# necessary to specify different casings of the same filename as +# matching is done case-insensitively. Filenames can include special +# characters such as spaces. Only : is not allowed. +# +# If you don't want each disc to be referred to as "CD #1", "CD #2", +# etc. then you can optionally provide your own names. Set CDROM_NAME +# for a single disc, CDROM_NAMES as an array for multiple discs, or +# individual CDROM_NAME_# variables for each disc starting from 1. +# +# Despite what you may have seen in older ebuilds, it has never been +# possible to provide per-set disc names. This would not make sense as +# all the names are initially displayed before the first disc has been +# detected. As a workaround, you can redefine the name variable(s) +# after the first disc has been detected. +# +# This function ends with a cdrom_load_next_cd() call to scan for the +# first disc. For more details about variables read and written by this +# eclass, see that function's description. +cdrom_get_cds() { + unset CDROM_SET + export CDROM_CURRENT_CD=0 CDROM_CHECKS=( "${@}" ) + + # If the user has set CD_ROOT or CD_ROOT_1, don't bother informing + # them about which discs are needed as they presumably already know. + if [[ -n ${CD_ROOT}${CD_ROOT_1} ]] ; then + : + + # Single disc info. + elif [[ ${#} -eq 1 ]] ; then + einfo "This ebuild will need the ${CDROM_NAME:-CD for ${PN}}" + echo + einfo "If you do not have the CD, but have the data files" + einfo "mounted somewhere on your filesystem, just export" + einfo "the variable CD_ROOT so that it points to the" + einfo "directory containing the files." + echo + einfo "For example:" + einfo "export CD_ROOT=/mnt/cdrom" + echo + + # Multi disc info. + else + _cdrom_set_names + einfo "This package may need access to ${#} CDs." + local cdcnt + for cdcnt in $(seq ${#}); do + local var=CDROM_NAME_${cdcnt} + [[ ! -z ${!var} ]] && einfo " CD ${cdcnt}: ${!var}" + done + echo + einfo "If you do not have the CDs, but have the data files" + einfo "mounted somewhere on your filesystem, just export" + einfo "the following variables so they point to the right place:" + einfo $(printf "CD_ROOT_%d " $(seq ${#})) + echo + einfo "Or, if you have all the files in the same place, or" + einfo "you only have one CD, you can export CD_ROOT" + einfo "and that place will be used as the same data source" + einfo "for all the CDs." + echo + einfo "For example:" + einfo "export CD_ROOT=/mnt/cdrom" + echo + fi + + # Scan for the first disc. + cdrom_load_next_cd +} + +# @FUNCTION: cdrom_load_next_cd +# @DESCRIPTION: +# If multiple arguments were given to cdrom_get_cds() then you can call +# this function to scan for the next disc. This function is also called +# implicitly to scan for the first disc. +# +# The file(s) given to cdrom_get_cds() are scanned for on any mounted +# filesystem that resembles optical media. If no match is found then +# the user is prompted to insert and mount the disc and press enter to +# rescan. This will loop continuously until a match is found or the +# user aborts with Ctrl+C. +# +# The user can override the scan location by setting CD_ROOT for a +# single disc, CD_ROOT if multiple discs are merged into the same +# directory tree (useful for existing installations), or individual +# CD_ROOT_# variables for each disc starting from 1. If no match is +# found then the function dies with an error as a rescan will not help +# in this instance. +# +# Users wanting to set CD_ROOT or CD_ROOT_# for specific packages +# persistently can do so using Portage's /etc/portage/env feature. +# +# Regardless of which scanning method is used, several variables are set +# by this function for you to use: +# +# CDROM_ROOT: Root path of the detected disc. +# CDROM_MATCH: Path of the matched file, relative to CDROM_ROOT. +# CDROM_ABSMATCH: Absolute path of the matched file. +# CDROM_SET: The matching set number, starting from 0. +# +# The casing of CDROM_MATCH may not be the same as the argument given to +# cdrom_get_cds() as matching is done case-insensitively. You should +# therefore use this variable (or CDROM_ABSMATCH) when performing file +# operations to ensure the file is found. Use newins rather than doins +# to keep the final result consistent and take advantage of Bash +# case-conversion features like ${FOO,,}. +# +# Chances are that you'll need more than just the matched file from each +# disc though. You should not assume the casing of these files either +# but dealing with this goes beyond the scope of this ebuild. For a +# good example, see games-action/descent2-data, which combines advanced +# globbing with advanced tar features to concisely deal with +# case-insensitive matching, case conversion, file moves, and +# conditional exclusion. +# +# Copying directly from a mounted disc using doins/newins will remove +# any read-only permissions but be aware of these when copying to an +# intermediate directory first. Attempting to clean a build directory +# containing read-only files as a non-root user will result in an error. +# If you're using tar as suggested above then you can easily work around +# this with --mode=u+w. +# +# Note that you can only go forwards in the disc list, so make sure you +# only call this function when you're done using the current disc. +# +# If you cd to any location within CDROM_ROOT then remember to leave the +# directory before calling this function again, otherwise the user won't +# be able to unmount the current disc. +cdrom_load_next_cd() { + local showedmsg=0 showjolietmsg=0 + + unset CDROM_ROOT + ((++CDROM_CURRENT_CD)) + + _cdrom_set_names + + while true ; do + local i cdset + : CD_ROOT_${CDROM_CURRENT_CD} + export CDROM_ROOT=${CD_ROOT:-${!_}} + IFS=: read -r -a cdset -d "" <<< "${CDROM_CHECKS[$((${CDROM_CURRENT_CD} - 1))]}" + + for i in $(seq ${CDROM_SET:-0} ${CDROM_SET:-$((${#cdset[@]} - 1))}); do + local f=${cdset[${i}]} point= node= fs= opts= + + if [[ -z ${CDROM_ROOT} ]] ; then + while read point node fs opts ; do + has "${fs}" cd9660 iso9660 udf || continue + point=${point//\040/ } + export CDROM_MATCH=$(_cdrom_glob_match "${point}" "${f}") + [[ -z ${CDROM_MATCH} ]] && continue + export CDROM_ROOT=${point} + done <<< "$(get_mounts)" + else + export CDROM_MATCH=$(_cdrom_glob_match "${CDROM_ROOT}" "${f}") + fi + + if [[ -n ${CDROM_MATCH} ]] ; then + export CDROM_ABSMATCH=${CDROM_ROOT}/${CDROM_MATCH} + export CDROM_SET=${i} + break 2 + fi + done + + # If we get here then we were unable to locate a match. If + # CDROM_ROOT is non-empty then this implies that a CD_ROOT + # variable was given and we should therefore abort immediately. + if [[ -n ${CDROM_ROOT} ]] ; then + die "unable to locate CD #${CDROM_CURRENT_CD} root at ${CDROM_ROOT}" + fi + + if [[ ${showedmsg} -eq 0 ]] ; then + if [[ ${#CDROM_CHECKS[@]} -eq 1 ]] ; then + einfo "Please insert+mount the ${CDROM_NAME:-CD for ${PN}} now !" + else + local var="CDROM_NAME_${CDROM_CURRENT_CD}" + if [[ -z ${!var} ]] ; then + einfo "Please insert+mount CD #${CDROM_CURRENT_CD} for ${PN} now !" + else + einfo "Please insert+mount the ${!var} now !" + fi + fi + showedmsg=1 + fi + + einfo "Press return to scan for the CD again" + einfo "or hit CTRL+C to abort the emerge." + + if [[ ${showjolietmsg} -eq 0 ]] ; then + showjolietmsg=1 + else + echo + ewarn "If you are having trouble with the detection" + ewarn "of your CD, it is possible that you do not have" + ewarn "Joliet support enabled in your kernel. Please" + ewarn "check that CONFIG_JOLIET is enabled in your kernel." + fi + read || die "something is screwed with your system" + done + + einfo "Found CD #${CDROM_CURRENT_CD} root at ${CDROM_ROOT}" +} + +# @FUNCTION: _cdrom_glob_match +# @USAGE: <root directory> <path> +# @INTERNAL +# @DESCRIPTION: +# Locates the given path ($2) within the given root directory ($1) +# case-insensitively and returns the first actual matching path. This +# eclass previously used "find -iname" but it only checked the file +# case-insensitively and not the directories. There is "find -ipath" +# but this does not intelligently skip non-matching paths, making it +# slow. Case-insensitive matching can only be applied to patterns so +# extended globbing is used to turn regular strings into patterns. All +# special characters are escaped so don't worry about breaking this. +_cdrom_glob_match() { + # The following line turns this: + # foo*foo/bar bar/baz/file.zip + # + # Into this: + # ?(foo\*foo)/?(bar\ bar)/?(baz)/?(file\.zip) + # + # This turns every path component into an escaped extended glob + # pattern to allow case-insensitive matching. Globs cannot span + # directories so each component becomes an individual pattern. + local p=\?\($(sed -e 's:[^A-Za-z0-9/]:\\\0:g' -e 's:/:)/?(:g' <<< "$2" || die)\) + ( + cd "$1" 2>/dev/null || return + shopt -s extglob nocaseglob nullglob || die + # The first person to make this work without an eval wins a + # cookie. It breaks without it when spaces are present. + eval "ARRAY=( ${p%\?()} )" + echo ${ARRAY[0]} + ) +} + +# @FUNCTION: _cdrom_set_names +# @INTERNAL +# @DESCRIPTION: +# Populate CDROM_NAME_# variables with the CDROM_NAMES array. +_cdrom_set_names() { + if [[ -n ${CDROM_NAMES} ]] ; then + local i + for i in $(seq ${#CDROM_NAMES[@]}); do + export CDROM_NAME_${i}="${CDROM_NAMES[$((${i} - 1))]}" + done + fi +} + +fi |