Improve the name-version regex
[gentoo.git] / eclass / cdrom.eclass
1 # Copyright 1999-2019 Gentoo Authors
2 # Distributed under the terms of the GNU General Public License v2
3
4 # @ECLASS: cdrom.eclass
5 # @MAINTAINER:
6 # games@gentoo.org
7 # @BLURB: Functions for CD-ROM handling
8 # @DESCRIPTION:
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!
11 #
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.
16
17 if [[ -z ${_CDROM_ECLASS} ]]; then
18 _CDROM_ECLASS=1
19
20 inherit portability
21
22 # @ECLASS-VARIABLE: CDROM_OPTIONAL
23 # @DEFAULT_UNSET
24 # @DESCRIPTION:
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
30         IUSE="cdinstall"
31         PROPERTIES+=" cdinstall? ( interactive )"
32 else
33         PROPERTIES+=" interactive"
34 fi
35
36 # @FUNCTION: cdrom_get_cds
37 # @USAGE: <cd1 file>[:alt cd1 file] [cd2 file[:alt cd2 file]] [...]
38 # @DESCRIPTION:
39 # Attempt to locate a CD based upon a file that is on the CD.
40 #
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.
44 #
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
51 # subsequent discs.
52 #
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.
57 #
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.
62 #
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.
68 #
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.
72 cdrom_get_cds() {
73         unset CDROM_SET
74         export CDROM_CURRENT_CD=0
75     export CDROM_NUM_CDS="${#}"
76     local i
77     for i in $(seq ${#}); do
78         export CDROM_CHECK_${i}="${!i}"
79     done
80
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
84                 :
85
86         # Single disc info.
87         elif [[ ${#} -eq 1 ]] ; then
88                 einfo "This ebuild will need the ${CDROM_NAME:-CD for ${PN}}"
89                 echo
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."
94                 echo
95                 einfo "For example:"
96                 einfo "export CD_ROOT=/mnt/cdrom"
97                 echo
98
99         # Multi disc info.
100         else
101                 _cdrom_set_names
102                 einfo "This package may need access to ${#} CDs."
103                 local cdcnt
104                 for cdcnt in $(seq ${#}); do
105                         local var=CDROM_NAME_${cdcnt}
106                         [[ ! -z ${!var} ]] && einfo " CD ${cdcnt}: ${!var}"
107                 done
108                 echo
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 ${#}))
113                 echo
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."
118                 echo
119                 einfo "For example:"
120                 einfo "export CD_ROOT=/mnt/cdrom"
121                 echo
122         fi
123
124         # Scan for the first disc.
125         cdrom_load_next_cd
126 }
127
128 # @FUNCTION: cdrom_load_next_cd
129 # @DESCRIPTION:
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.
133 #
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.
139 #
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
145 # in this instance.
146 #
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.
149 #
150 # Regardless of which scanning method is used, several variables are set
151 # by this function for you to use:
152 #
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.
157 #
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,,}.
164 #
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.
172 #
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.
179 #
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.
182 #
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
188
189         unset CDROM_ROOT
190         ((++CDROM_CURRENT_CD))
191
192         _cdrom_set_names
193
194         while true ; do
195                 local i cdset
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}"
200
201                 for i in $(seq ${CDROM_SET:-0} ${CDROM_SET:-$((${#cdset[@]} - 1))}); do
202                         local f=${cdset[${i}]} point= node= fs= opts=
203
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)"
212                         else
213                                 export CDROM_MATCH=$(_cdrom_glob_match "${CDROM_ROOT}" "${f}")
214                         fi
215
216                         if [[ -n ${CDROM_MATCH} ]] ; then
217                                 export CDROM_ABSMATCH=${CDROM_ROOT}/${CDROM_MATCH}
218                                 export CDROM_SET=${i}
219                                 break 2
220                         fi
221                 done
222
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}"
228                 fi
229
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 !"
233                         else
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 !"
237                                 else
238                                         einfo "Please insert+mount the ${!var} now !"
239                                 fi
240                         fi
241                         showedmsg=1
242                 fi
243
244                 einfo "Press return to scan for the CD again"
245                 einfo "or hit CTRL+C to abort the emerge."
246
247                 if [[ ${showjolietmsg} -eq 0 ]] ; then
248                         showjolietmsg=1
249                 else
250                         echo
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."
255                 fi
256                 read || die "something is screwed with your system"
257         done
258
259         einfo "Found CD #${CDROM_CURRENT_CD} root at ${CDROM_ROOT}"
260 }
261
262 # @FUNCTION: _cdrom_glob_match
263 # @USAGE: <root directory> <path>
264 # @INTERNAL
265 # @DESCRIPTION:
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
277         #
278         # Into this:
279         #  ?(foo\*foo)/?(bar\ bar)/?(baz)/?(file\.zip)
280         #
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)\)
285         (
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%\?()} )"
291                 echo ${ARRAY[0]}
292         )
293 }
294
295 # @FUNCTION: _cdrom_set_names
296 # @INTERNAL
297 # @DESCRIPTION:
298 # Populate CDROM_NAME_# variables with the CDROM_NAMES array.
299 _cdrom_set_names() {
300         if [[ -n ${CDROM_NAMES} ]] ; then
301                 local i
302                 for i in $(seq ${#CDROM_NAMES[@]}); do
303                         export CDROM_NAME_${i}="${CDROM_NAMES[$((${i} - 1))]}"
304                 done
305         fi
306 }
307
308 fi