1 # Copyright 1999-2017 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
4 # @ECLASS: cdrom.eclass
7 # @BLURB: Functions for CD-ROM handling
9 # Acquire CD(s) for those lovely CD-based emerges. Yes, this violates
10 # the whole "non-interactive" policy, but damnit I want CD support!
12 # Do not call these functions in pkg_* phases like pkg_setup as they
13 # should not be used for binary packages. Most packages using this
14 # eclass will require RESTRICT="bindist" but the point still stands.
15 # The functions are generally called in src_unpack.
17 if [[ -z ${_CDROM_ECLASS} ]]; then
22 # @ECLASS-VARIABLE: CDROM_OPTIONAL
25 # By default, the eclass sets PROPERTIES="interactive" on the assumption
26 # that people will be using these. If your package optionally supports
27 # disc-based installs then set this to "yes" and we'll set things
28 # conditionally based on USE="cdinstall".
29 if [[ ${CDROM_OPTIONAL} == "yes" ]] ; then
31 PROPERTIES="cdinstall? ( interactive )"
33 PROPERTIES="interactive"
36 # @FUNCTION: cdrom_get_cds
37 # @USAGE: <cd1 file>[:alt cd1 file] [cd2 file[:alt cd2 file]] [...]
39 # Attempt to locate a CD based upon a file that is on the CD.
41 # If the data spans multiple discs then additional arguments can be
42 # given to check for more files. Call cdrom_load_next_cd() to scan for
43 # the next disc in the set.
45 # Sometimes it is necessary to support alternative CD "sets" where the
46 # contents differ. Alternative files for each disc can be appended to
47 # each argument, separated by the : character. This feature is
48 # frequently used to support installing from an existing installation.
49 # Note that after the first disc is detected, the set is locked so
50 # cdrom_load_next_cd() will only scan for files in that specific set on
53 # The given files can be within named subdirectories. It is not
54 # necessary to specify different casings of the same filename as
55 # matching is done case-insensitively. Filenames can include special
56 # characters such as spaces. Only : is not allowed.
58 # If you don't want each disc to be referred to as "CD #1", "CD #2",
59 # etc. then you can optionally provide your own names. Set CDROM_NAME
60 # for a single disc, CDROM_NAMES as an array for multiple discs, or
61 # individual CDROM_NAME_# variables for each disc starting from 1.
63 # Despite what you may have seen in older ebuilds, it has never been
64 # possible to provide per-set disc names. This would not make sense as
65 # all the names are initially displayed before the first disc has been
66 # detected. As a workaround, you can redefine the name variable(s)
67 # after the first disc has been detected.
69 # This function ends with a cdrom_load_next_cd() call to scan for the
70 # first disc. For more details about variables read and written by this
71 # eclass, see that function's description.
74 export CDROM_CURRENT_CD=0
75 export CDROM_NUM_CDS="${#}"
77 for i in $(seq ${#}); do
78 export CDROM_CHECK_${i}="${!i}"
81 # If the user has set CD_ROOT or CD_ROOT_1, don't bother informing
82 # them about which discs are needed as they presumably already know.
83 if [[ -n ${CD_ROOT}${CD_ROOT_1} ]] ; then
87 elif [[ ${#} -eq 1 ]] ; then
88 einfo "This ebuild will need the ${CDROM_NAME:-CD for ${PN}}"
90 einfo "If you do not have the CD, but have the data files"
91 einfo "mounted somewhere on your filesystem, just export"
92 einfo "the variable CD_ROOT so that it points to the"
93 einfo "directory containing the files."
96 einfo "export CD_ROOT=/mnt/cdrom"
102 einfo "This package may need access to ${#} CDs."
104 for cdcnt in $(seq ${#}); do
105 local var=CDROM_NAME_${cdcnt}
106 [[ ! -z ${!var} ]] && einfo " CD ${cdcnt}: ${!var}"
109 einfo "If you do not have the CDs, but have the data files"
110 einfo "mounted somewhere on your filesystem, just export"
111 einfo "the following variables so they point to the right place:"
112 einfo $(printf "CD_ROOT_%d " $(seq ${#}))
114 einfo "Or, if you have all the files in the same place, or"
115 einfo "you only have one CD, you can export CD_ROOT"
116 einfo "and that place will be used as the same data source"
117 einfo "for all the CDs."
120 einfo "export CD_ROOT=/mnt/cdrom"
124 # Scan for the first disc.
128 # @FUNCTION: cdrom_load_next_cd
130 # If multiple arguments were given to cdrom_get_cds() then you can call
131 # this function to scan for the next disc. This function is also called
132 # implicitly to scan for the first disc.
134 # The file(s) given to cdrom_get_cds() are scanned for on any mounted
135 # filesystem that resembles optical media. If no match is found then
136 # the user is prompted to insert and mount the disc and press enter to
137 # rescan. This will loop continuously until a match is found or the
138 # user aborts with Ctrl+C.
140 # The user can override the scan location by setting CD_ROOT for a
141 # single disc, CD_ROOT if multiple discs are merged into the same
142 # directory tree (useful for existing installations), or individual
143 # CD_ROOT_# variables for each disc starting from 1. If no match is
144 # found then the function dies with an error as a rescan will not help
147 # Users wanting to set CD_ROOT or CD_ROOT_# for specific packages
148 # persistently can do so using Portage's /etc/portage/env feature.
150 # Regardless of which scanning method is used, several variables are set
151 # by this function for you to use:
153 # CDROM_ROOT: Root path of the detected disc.
154 # CDROM_MATCH: Path of the matched file, relative to CDROM_ROOT.
155 # CDROM_ABSMATCH: Absolute path of the matched file.
156 # CDROM_SET: The matching set number, starting from 0.
158 # The casing of CDROM_MATCH may not be the same as the argument given to
159 # cdrom_get_cds() as matching is done case-insensitively. You should
160 # therefore use this variable (or CDROM_ABSMATCH) when performing file
161 # operations to ensure the file is found. Use newins rather than doins
162 # to keep the final result consistent and take advantage of Bash
163 # case-conversion features like ${FOO,,}.
165 # Chances are that you'll need more than just the matched file from each
166 # disc though. You should not assume the casing of these files either
167 # but dealing with this goes beyond the scope of this ebuild. For a
168 # good example, see games-action/descent2-data, which combines advanced
169 # globbing with advanced tar features to concisely deal with
170 # case-insensitive matching, case conversion, file moves, and
171 # conditional exclusion.
173 # Copying directly from a mounted disc using doins/newins will remove
174 # any read-only permissions but be aware of these when copying to an
175 # intermediate directory first. Attempting to clean a build directory
176 # containing read-only files as a non-root user will result in an error.
177 # If you're using tar as suggested above then you can easily work around
178 # this with --mode=u+w.
180 # Note that you can only go forwards in the disc list, so make sure you
181 # only call this function when you're done using the current disc.
183 # If you cd to any location within CDROM_ROOT then remember to leave the
184 # directory before calling this function again, otherwise the user won't
185 # be able to unmount the current disc.
186 cdrom_load_next_cd() {
187 local showedmsg=0 showjolietmsg=0
190 ((++CDROM_CURRENT_CD))
196 : CD_ROOT_${CDROM_CURRENT_CD}
197 export CDROM_ROOT=${CD_ROOT:-${!_}}
198 local var="CDROM_CHECK_${CDROM_CURRENT_CD}"
199 IFS=: read -r -a cdset -d "" <<< "${!var}"
201 for i in $(seq ${CDROM_SET:-0} ${CDROM_SET:-$((${#cdset[@]} - 1))}); do
202 local f=${cdset[${i}]} point= node= fs= opts=
204 if [[ -z ${CDROM_ROOT} ]] ; then
205 while read point node fs opts ; do
206 has "${fs}" cd9660 iso9660 udf || continue
207 point=${point//\040/ }
208 export CDROM_MATCH=$(_cdrom_glob_match "${point}" "${f}")
209 [[ -z ${CDROM_MATCH} ]] && continue
210 export CDROM_ROOT=${point}
211 done <<< "$(get_mounts)"
213 export CDROM_MATCH=$(_cdrom_glob_match "${CDROM_ROOT}" "${f}")
216 if [[ -n ${CDROM_MATCH} ]] ; then
217 export CDROM_ABSMATCH=${CDROM_ROOT}/${CDROM_MATCH}
218 export CDROM_SET=${i}
223 # If we get here then we were unable to locate a match. If
224 # CDROM_ROOT is non-empty then this implies that a CD_ROOT
225 # variable was given and we should therefore abort immediately.
226 if [[ -n ${CDROM_ROOT} ]] ; then
227 die "unable to locate CD #${CDROM_CURRENT_CD} root at ${CDROM_ROOT}"
230 if [[ ${showedmsg} -eq 0 ]] ; then
231 if [[ ${CDROM_NUM_CDS} -eq 1 ]] ; then
232 einfo "Please insert+mount the ${CDROM_NAME:-CD for ${PN}} now !"
234 local var="CDROM_NAME_${CDROM_CURRENT_CD}"
235 if [[ -z ${!var} ]] ; then
236 einfo "Please insert+mount CD #${CDROM_CURRENT_CD} for ${PN} now !"
238 einfo "Please insert+mount the ${!var} now !"
244 einfo "Press return to scan for the CD again"
245 einfo "or hit CTRL+C to abort the emerge."
247 if [[ ${showjolietmsg} -eq 0 ]] ; then
251 ewarn "If you are having trouble with the detection"
252 ewarn "of your CD, it is possible that you do not have"
253 ewarn "Joliet support enabled in your kernel. Please"
254 ewarn "check that CONFIG_JOLIET is enabled in your kernel."
256 read || die "something is screwed with your system"
259 einfo "Found CD #${CDROM_CURRENT_CD} root at ${CDROM_ROOT}"
262 # @FUNCTION: _cdrom_glob_match
263 # @USAGE: <root directory> <path>
266 # Locates the given path ($2) within the given root directory ($1)
267 # case-insensitively and returns the first actual matching path. This
268 # eclass previously used "find -iname" but it only checked the file
269 # case-insensitively and not the directories. There is "find -ipath"
270 # but this does not intelligently skip non-matching paths, making it
271 # slow. Case-insensitive matching can only be applied to patterns so
272 # extended globbing is used to turn regular strings into patterns. All
273 # special characters are escaped so don't worry about breaking this.
274 _cdrom_glob_match() {
275 # The following line turns this:
276 # foo*foo/bar bar/baz/file.zip
279 # ?(foo\*foo)/?(bar\ bar)/?(baz)/?(file\.zip)
281 # This turns every path component into an escaped extended glob
282 # pattern to allow case-insensitive matching. Globs cannot span
283 # directories so each component becomes an individual pattern.
284 local p=\?\($(sed -e 's:[^A-Za-z0-9/]:\\\0:g' -e 's:/:)/?(:g' <<< "$2" || die)\)
286 cd "$1" 2>/dev/null || return
287 shopt -s extglob nocaseglob nullglob || die
288 # The first person to make this work without an eval wins a
289 # cookie. It breaks without it when spaces are present.
290 eval "ARRAY=( ${p%\?()} )"
295 # @FUNCTION: _cdrom_set_names
298 # Populate CDROM_NAME_# variables with the CDROM_NAMES array.
300 if [[ -n ${CDROM_NAMES} ]] ; then
302 for i in $(seq ${#CDROM_NAMES[@]}); do
303 export CDROM_NAME_${i}="${CDROM_NAMES[$((${i} - 1))]}"