summaryrefslogtreecommitdiff
blob: c4d7d59beadbfeb88ddfd5dca383d19af31a6bfd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# Copyright 1999-2024 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

# @ECLASS: cdrom.eclass
# @MAINTAINER:
# games@gentoo.org
# @SUPPORTED_EAPIS: 7 8
# @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

case ${EAPI} in
	7|8) ;;
	*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
esac

inherit portability

# @ECLASS_VARIABLE: CDROM_OPTIONAL
# @DEFAULT_UNSET
# @PRE_INHERIT
# @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
	export CDROM_NUM_CDS="${#}"
	local i
	for i in $(seq ${#}); do
		export CDROM_CHECK_${i}="${!i}"
	done

	# 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:-${!_}}
		local var="CDROM_CHECK_${CDROM_CURRENT_CD}"
		IFS=: read -r -a cdset -d "" <<< "${!var}"

		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_NUM_CDS} -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