index.subr revision 294864
1155131Srwatsonif [ ! "$_PACKAGES_INDEX_SUBR" ]; then _PACKAGES_INDEX_SUBR=1
2196031Srwatson#
3157137Srwatson# Copyright (c) 2013-2016 Devin Teske
4155131Srwatson# All rights reserved.
5185573Srwatson#
6173146Srwatson# Redistribution and use in source and binary forms, with or without
7173146Srwatson# modification, are permitted provided that the following conditions
8173146Srwatson# are met:
9173146Srwatson# 1. Redistributions of source code must retain the above copyright
10173146Srwatson#    notice, this list of conditions and the following disclaimer.
11186647Srwatson# 2. Redistributions in binary form must reproduce the above copyright
12186647Srwatson#    notice, this list of conditions and the following disclaimer in the
13186647Srwatson#    documentation and/or other materials provided with the distribution.
14186647Srwatson#
15186647Srwatson# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16186647Srwatson# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17186647Srwatson# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18186647Srwatson# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19186647Srwatson# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20186647Srwatson# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21186647Srwatson# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22186647Srwatson# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23186647Srwatson# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24186647Srwatson# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25155131Srwatson# SUCH DAMAGE.
26155131Srwatson#
27155131Srwatson# $FreeBSD: head/usr.sbin/bsdconfig/share/packages/index.subr 294864 2016-01-27 00:08:23Z dteske $
28155131Srwatson#
29155131Srwatson############################################################ INCLUDES
30155131Srwatson
31155131SrwatsonBSDCFG_SHARE="/usr/share/bsdconfig"
32155131Srwatson. $BSDCFG_SHARE/common.subr || exit 1
33155131Srwatsonf_dprintf "%s: loading includes..." packages/index.subr
34155131Srwatsonf_include $BSDCFG_SHARE/device.subr
35155131Srwatsonf_include $BSDCFG_SHARE/media/common.subr
36155131Srwatsonf_include $BSDCFG_SHARE/packages/musthavepkg.subr
37155131Srwatsonf_include $BSDCFG_SHARE/strings.subr
38155131Srwatson
39155131SrwatsonBSDCFG_LIBE="/usr/libexec/bsdconfig"
40155131Srwatsonf_include_lang $BSDCFG_LIBE/include/messages.subr
41155131Srwatson
42155131Srwatson############################################################ GLOBALS
43155131Srwatson
44155131SrwatsonPACKAGE_INDEX=
45155131Srwatson_INDEX_INITTED=
46155131Srwatson
47155131Srwatson#
48155131Srwatson# Default path to pkg(8) repo-packagesite.sqlite database
49155131Srwatson#
50155131SrwatsonSQLITE_REPO="/var/db/pkg/repo-FreeBSD.sqlite"
51155131Srwatson
52155131Srwatson#
53155131Srwatson# Default path to on-disk cache INDEX file
54155131Srwatson#
55155131SrwatsonPACKAGES_INDEX_CACHEFILE="/var/run/bsdconfig/packages_INDEX.cache"
56155131Srwatson
57155131Srwatson############################################################ FUNCTIONS
58155131Srwatson
59155131Srwatson# f_index_initialize [$var_to_set]
60155131Srwatson#
61155131Srwatson# Read and initialize the global index. Returns success unless media cannot be
62155131Srwatson# initialized for any reason (e.g. user cancels media selection dialog or an
63155131Srwatson# error occurs). The index is sorted before being loaded into $var_to_set.
64155131Srwatson#
65155131Srwatson# NOTE: The index is processed with f_index_read() [below] after being loaded.
66155131Srwatson#
67155131Srwatsonf_index_initialize()
68155131Srwatson{
69155131Srwatson	local __funcname=f_index_initialize
70155131Srwatson	local __var_to_set="${1:-PACKAGE_INDEX}"
71155131Srwatson
72155131Srwatson	[ "$_INDEX_INITTED" ] && return $SUCCESS
73155131Srwatson
74155131Srwatson	# Got any media?
75155131Srwatson	f_media_verify || return $FAILURE
76155131Srwatson
77155131Srwatson	# Make sure we have a usable pkg(8) with $PKG_ABI
78155131Srwatson	f_musthavepkg_init
79155131Srwatson
80155131Srwatson	# Does it move when you kick it?
81155131Srwatson	f_device_init device_media || return $FAILURE
82155131Srwatson
83155131Srwatson	f_show_info "$msg_attempting_to_update_repository_catalogue"
84155131Srwatson
85155131Srwatson	#
86155131Srwatson	# Generate $PACKAGESITE variable for pkg(8) based on media type
87155131Srwatson	#
88155131Srwatson	local __type __data __site
89155131Srwatson	device_media get type __type
90155131Srwatson	device_media get private __data
91155131Srwatson	case "$__type" in
92155131Srwatson	$DEVICE_TYPE_DIRECTORY)
93155131Srwatson		__site="file://$__data/packages/$PKG_ABI" ;;
94155131Srwatson	$DEVICE_TYPE_FLOPPY)
95155131Srwatson		__site="file://${__data:-$MOUNTPOINT}/packages/$PKG_ABI" ;;
96155131Srwatson	$DEVICE_TYPE_FTP)
97155131Srwatson		f_getvar $VAR_FTP_PATH __site
98155131Srwatson		__site="$__site/packages/$PKG_ABI" ;;
99155131Srwatson	$DEVICE_TYPE_HTTP)
100155131Srwatson		f_getvar $VAR_HTTP_PATH __site
101155131Srwatson		__site="$__site/$PKG_ABI/latest" ;;
102155131Srwatson	$DEVICE_TYPE_HTTP_PROXY)
103155131Srwatson		f_getvar $VAR_HTTP_PROXY_PATH __site
104155131Srwatson		__site="$__site/packages/$PKG_ABI" ;;
105155131Srwatson	$DEVICE_TYPE_CDROM)
106155131Srwatson		__site="file://$MOUNTPOINT/packages/$PKG_ABI"
107155131Srwatson		export REPOS_DIR="$MOUNTPOINT/packages/repos" ;;
108155131Srwatson	*) # UFS, DISK, CDROM, USB, DOS, NFS, etc.
109155131Srwatson		__site="file://$MOUNTPOINT/packages/$PKG_ABI"
110155131Srwatson	esac
111155131Srwatson
112155131Srwatson	f_dprintf "PACKAGESITE=[%s]" "$__site"
113155131Srwatson	if ! f_eval_catch $__funcname pkg \
114155131Srwatson		'PACKAGESITE="%s" pkg update' "$__site"
115155131Srwatson	then
116155131Srwatson		f_show_err "$msg_unable_to_update_pkg_from_selected_media"
117155131Srwatson		f_device_shutdown device_media
118155131Srwatson		return $FAILURE
119155131Srwatson	fi
120155131Srwatson
121155131Srwatson	#
122155131Srwatson	# Try to get contents from validated on-disk cache
123155131Srwatson	#
124155131Srwatson
125155131Srwatson	#
126155131Srwatson	# Calculate digest used to determine if the on-disk persistant cache
127155131Srwatson	# INDEX (containing this digest on the first line) is valid and can be
128155131Srwatson	# used to quickly populate the environment.
129155131Srwatson	#
130155131Srwatson	local __sqlite_digest
131155131Srwatson	if ! __sqlite_digest=$( md5 < "$SQLITE_REPO" 2> /dev/null ); then
132155131Srwatson		f_show_err "$msg_no_pkg_database_found"
133155131Srwatson		f_device_shutdown device_media
134155131Srwatson		return $FAILURE
135155131Srwatson	fi
136155131Srwatson
137155131Srwatson	#
138155131Srwatson	# Check to see if the persistant cache INDEX file exists
139155131Srwatson	#
140155131Srwatson	if [ -f "$PACKAGES_INDEX_CACHEFILE" ]; then
141162506Srwatson		#
142162506Srwatson		# Attempt to populate the environment with the (soon to be)
143155131Srwatson		# validated on-disk cache. If validation fails, fall-back to
144155131Srwatson		# generating a fresh cache.
145155131Srwatson		#
146155131Srwatson		if eval $__var_to_set='$(
147155131Srwatson			(	# Get digest as the first word on first line
148155131Srwatson				read digest rest_ignored
149155131Srwatson
150155131Srwatson				#
151155131Srwatson				# If the stored digest matches the calculated-
152155131Srwatson				# one populate the environment from the on-disk
153155131Srwatson				# cache and provide success exit status.
154155131Srwatson				#
155155131Srwatson				if [ "$digest" = "$__sqlite_digest" ]; then
156155131Srwatson					cat
157155131Srwatson					exit $SUCCESS
158155131Srwatson				else
159155131Srwatson					# Otherwise, return the current value
160155131Srwatson					eval echo \"\$__var_to_set\"
161155131Srwatson					exit $FAILURE
162155131Srwatson				fi
163155131Srwatson			) < "$PACKAGES_INDEX_CACHEFILE" 2> /dev/null
164171540Srwatson		)'; then
165171540Srwatson			f_show_info \
166155131Srwatson			  "$msg_located_index_now_reading_package_data_from_it"
167155131Srwatson			if ! f_index_read "$__var_to_set"; then
168171540Srwatson				f_show_err \
169171540Srwatson					"$msg_io_or_format_error_on_index_file"
170171540Srwatson				return $FAILURE
171155131Srwatson			fi
172155131Srwatson			_INDEX_INITTED=1
173155131Srwatson			return $SUCCESS
174155131Srwatson		fi
175155131Srwatson		# Otherwise, fall-thru to create a fresh cache from scratch
176155131Srwatson	fi
177155131Srwatson
178155131Srwatson	#
179155131Srwatson	# If we reach this point, we need to generate the data from scratch
180155131Srwatson	#
181155131Srwatson
182155131Srwatson	f_show_info "$msg_generating_index_from_pkg_database"
183155131Srwatson	eval "$__var_to_set"='$( pkg rquery -I | sort )'
184155131Srwatson
185155131Srwatson	#
186155131Srwatson	# Attempt to create the persistant on-disk cache
187155131Srwatson	#
188155131Srwatson
189155131Srwatson	# Create a new temporary file to write to
190155131Srwatson	local __tmpfile
191155131Srwatson	if f_eval_catch -dk __tmpfile $__funcname mktemp \
192155131Srwatson		'mktemp -t "%s"' "$pgm"
193155131Srwatson	then
194171540Srwatson		# Write the temporary file contents
195155131Srwatson		echo "$__sqlite_digest" > "$__tmpfile"
196155131Srwatson		debug= f_getvar "$__var_to_set" >> "$__tmpfile"
197155131Srwatson
198155131Srwatson		# Finally, move the temporary file into place
199171540Srwatson		case "$PACKAGES_INDEX_CACHEFILE" in
200155131Srwatson		*/*) f_eval_catch -d $__funcname mkdir \
201155131Srwatson			'mkdir -p "%s"' "${PACKAGES_INDEX_CACHEFILE%/*}"
202155131Srwatson		esac
203186647Srwatson		f_eval_catch -d $__funcname mv 'mv -f "%s" "%s"' \
204155131Srwatson			"$__tmpfile" "$PACKAGES_INDEX_CACHEFILE"
205155131Srwatson	fi
206155131Srwatson
207171540Srwatson	f_show_info "$msg_located_index_now_reading_package_data_from_it"
208155131Srwatson	if ! f_index_read "$__var_to_set"; then
209155131Srwatson		f_show_err "$msg_io_or_format_error_on_index_file"
210155131Srwatson		return $FAILURE
211155131Srwatson	fi
212155131Srwatson
213155131Srwatson	_INDEX_INITTED=1
214155131Srwatson	return $SUCCESS
215155131Srwatson}
216155131Srwatson
217155131Srwatson# f_index_read [$var_to_get]
218155131Srwatson#
219155131Srwatson# Process the INDEX file (contents contained in $var_to_get) and...
220155131Srwatson#
221155131Srwatson# 1. create a list ($CATEGORY_MENU_LIST) of categories with package counts
222155131Srwatson# 2. For convenience, create $_npkgs holding the total number of all packages
223155131Srwatson# 3. extract associative categories for each package into $_categories_$varpkg
224155131Srwatson# 4. extract runtime dependencies for each package into $_rundeps_$varpkg
225155131Srwatson# 5. extract a [sorted] list of categories into $PACKAGE_CATEGORIES
226155131Srwatson# 6. create $_npkgs_$varcat holding the total number of packages in category
227155131Srwatson#
228155131Srwatson# NOTE: $varpkg is the product of f_str2varname $package varpkg
229155131Srwatson# NOTE: $package is the name as it appears in the INDEX (no archive suffix)
230155131Srwatson# NOTE: We only show categories for which there are at least one package.
231171540Srwatson# NOTE: $varcat is the product of f_str2varname $category varcat
232155131Srwatson#
233155131Srwatsonf_index_read()
234155131Srwatson{
235155131Srwatson	local var_to_get="${1:-PACKAGE_INDEX}"
236155131Srwatson
237155131Srwatson	# Export variables required by awk(1) below
238155131Srwatson	export msg_no_description_provided
239155131Srwatson	export msg_all msg_all_desc
240155131Srwatson	export VALID_VARNAME_CHARS
241155131Srwatson	export msg_packages
242155131Srwatson
243155131Srwatson	eval "$( debug= f_getvar "$var_to_get" | awk -F'|' '
244155131Srwatson	function _asorti(src, dest)
245155131Srwatson	{
246155131Srwatson		k = nitems = 0
247155131Srwatson
248155131Srwatson		# Copy src indices to dest and calculate array length
249155131Srwatson		for (i in src) dest[++nitems] = i
250155131Srwatson
251155131Srwatson		# Sort the array of indices (dest) using insertion sort method
252155131Srwatson		for (i = 1; i <= nitems; k = i++)
253155131Srwatson		{
254155131Srwatson			idx = dest[i]
255155131Srwatson			while ((k > 0) && (dest[k] > idx))
256155131Srwatson			{
257155131Srwatson				dest[k+1] = dest[k]
258155131Srwatson				k--
259155131Srwatson			}
260171540Srwatson			dest[k+1] = idx
261155131Srwatson		}
262155131Srwatson
263171540Srwatson		return nitems
264171540Srwatson	}
265171540Srwatson	function print_category(category, npkgs, desc)
266171540Srwatson	{
267171540Srwatson		cat = category
268171540Srwatson		# Accent the category if the first page has been
269171540Srwatson		# cached (also acting as a visitation indicator)
270171540Srwatson		if ( ENVIRON["_index_page_" varcat "_1"] )
271171540Srwatson			cat = cat "*"
272171540Srwatson		printf "'\''%s'\'' '\''%s " packages "'\'' '\''%s'\''\n",
273171540Srwatson		       cat, npkgs, desc
274171540Srwatson	}
275196031Srwatson	BEGIN {
276196031Srwatson		valid_chars = ENVIRON["VALID_VARNAME_CHARS"]
277171540Srwatson		default_desc = ENVIRON["msg_no_description_provided"]
278171540Srwatson		packages = ENVIRON["msg_packages"]
279171540Srwatson		tpkgs = 0
280171540Srwatson		prefix = ""
281171540Srwatson	}
282171540Srwatson	{
283171540Srwatson		tpkgs++
284171540Srwatson		varpkg = $1
285171540Srwatson		gsub("[^" valid_chars "]", "_", varpkg)
286171540Srwatson		print "_categories_" varpkg "=\"" $7 "\""
287171540Srwatson		split($7, pkg_categories, /[[:space:]]+/)
288171540Srwatson		for (pkg_category in pkg_categories)
289171540Srwatson			categories[pkg_categories[pkg_category]]++
290171540Srwatson		print "_rundeps_" varpkg "=\"" $9 "\""
291171540Srwatson	}
292171540Srwatson	END {
293171540Srwatson		print "_npkgs=" tpkgs # For convenience, total package count
294171540Srwatson
295171540Srwatson		n = _asorti(categories, categories_sorted)
296185573Srwatson
297161633Srwatson		# Produce package counts for each category
298185573Srwatson		for (i = 1; i <= n; i++)
299185573Srwatson		{
300161633Srwatson			cat = varcat = categories_sorted[i]
301161633Srwatson			npkgs = categories[cat]
302161633Srwatson			gsub("[^" valid_chars "]", "_", varcat)
303161633Srwatson			print "_npkgs_" varcat "=\"" npkgs "\""
304161633Srwatson		}
305161633Srwatson
306161633Srwatson		# Create menu list and generate list of categories at same time
307161633Srwatson		print "CATEGORY_MENU_LIST=\""
308155131Srwatson		print_category(ENVIRON["msg_all"], tpkgs,
309161633Srwatson		               ENVIRON["msg_all_desc"])
310161633Srwatson		category_list = ""
311161633Srwatson		for (i = 1; i <= n; i++)
312155131Srwatson		{
313155131Srwatson			cat = varcat = categories_sorted[i]
314155131Srwatson			npkgs = categories[cat]
315161633Srwatson			cur_prefix = tolower(substr(cat, 1, 1))
316161633Srwatson			if ( prefix != cur_prefix )
317155131Srwatson				prefix = cur_prefix
318161633Srwatson			else
319161633Srwatson				cat = " " cat
320161633Srwatson			gsub("[^" valid_chars "]", "_", varcat)
321155131Srwatson			desc = ENVIRON["_category_" varcat]
322155131Srwatson			if ( ! desc ) desc = default_desc
323155131Srwatson			print_category(cat, npkgs, desc)
324191273Srwatson			category_list = category_list " " cat
325191273Srwatson		}
326161633Srwatson		print "\""
327161633Srwatson
328162506Srwatson		# Produce the list of categories (calculated in above block)
329155131Srwatson		sub(/^ /, "", category_list)
330155131Srwatson		print "PACKAGE_CATEGORIES=\"" category_list "\""
331161633Srwatson
332161633Srwatson	}' )" # End-Quote
333161633Srwatson}
334161633Srwatson
335161633Srwatson# f_index_extract_pages $var_to_get $var_basename $pagesize [$category]
336161633Srwatson#
337161633Srwatson# Extracts the package INDEX ($PACKAGE_INDEX by default if/when $var_to_get is
338161633Srwatson# NULL; but should not be missing) into a series of sequential variables
339161633Srwatson# corresponding to "pages" containing up to $pagesize packages. The package
340161633Srwatson# INDEX data must be contained in the variable $var_to_get. The extracted pages
341161633Srwatson# are stored in variables ${var_basename}_# -- where "#" is a the page number.
342161633Srwatson# If $category is set, only packages for that category are extracted.
343161633Srwatson# Otherwise, if $category is "All", missing, or NULL, all packages are
344161633Srwatson# extracted and no filtering is done.
345161633Srwatson#
346161633Srwatsonf_index_extract_pages()
347161633Srwatson{
348161633Srwatson	local var_to_get="${1:-PACKAGE_INDEX}" var_basename="$2" pagesize="$3"
349161633Srwatson	local category="$4" # Optional
350161633Srwatson
351161633Srwatson	eval "$(
352161633Srwatson		debug= f_getvar "$var_to_get" | awk -F'|' \
353161633Srwatson			-v cat="$category" \
354161633Srwatson			-v pagesize="$pagesize" \
355171540Srwatson			-v var_basename="$var_basename" \
356161633Srwatson			-v i18n_all="$msg_all" '
357161633Srwatson		BEGIN { n = page = 0 }
358161633Srwatson		/'\''/{ gsub(/'\''/, "'\''\\'\'\''") }
359161633Srwatson		{
360161633Srwatson			if ( cat !~ "(^$|^" i18n_all "$)" && $7 !~ \
361161633Srwatson			     "(^|[[:space:]])" cat "([[:space:]]|$)" ) next
362161633Srwatson			starting_new_page = (n++ == (pagesize * page))
363161633Srwatson			if ( starting_new_page )
364161633Srwatson				printf "%s%s", ( n > 1 ? "'\''\n" : "" ),
365161633Srwatson				       var_basename "_" ++page "='\''"
366161633Srwatson			printf "%s%s", ( starting_new_page ? "" : "\n" ), $0
367161633Srwatson		}
368161633Srwatson		END { if ( n > 0 ) print "'\''" }'
369161633Srwatson	)"
370161633Srwatson}
371161633Srwatson
372171540Srwatson# f_index_search $var_to_get $name [$var_to_set]
373161633Srwatson#
374161633Srwatson# Search the package INDEX ($PACKAGE_INDEX by default if/when $var_to_get is
375161633Srwatson# NULL; but should not be missing) for $name, returning the first match.
376161633Srwatson# Matches are strict (not regular expressions) and must match the beginning
377161633Srwatson# portion of the package name to be considered a match. If $var_to_set is
378161633Srwatson# missing or NULL, output is sent to standard output. If a match is found,
379161633Srwatson# returns success; otherwise failure.
380191273Srwatson#
381191273Srwatsonf_index_search()
382171540Srwatson{
383161633Srwatson	local __var_to_get="${1:-PACKAGE_INDEX}" __pkg_basename="$2"
384162506Srwatson	local __var_to_set="$3"
385162506Srwatson
386161633Srwatson	f_dprintf "f_index_search: Searching package data (in %s) for %s" \
387161633Srwatson	          "$__var_to_get" "$__pkg_basename"
388161633Srwatson
389161633Srwatson	local __pkg=
390161633Srwatson	__pkg=$( debug= f_getvar "$__var_to_get" |
391161633Srwatson			awk -F'|' -v basename="$__pkg_basename" '
392161633Srwatson		BEGIN { n = length(basename) }
393161633Srwatson		substr($1, 0, n) == basename { print $1; exit }
394161633Srwatson	' )
395161633Srwatson	if [ ! "$__pkg" ]; then
396161633Srwatson		f_dprintf "f_index_search: No packages matching %s found" \
397161633Srwatson		          "$__pkg_basename"
398161633Srwatson		return $FAILURE
399161633Srwatson	fi
400161633Srwatson
401161633Srwatson	f_dprintf "f_index_search: Found package %s" "$__pkg"
402161633Srwatson	if [ "$__var_to_set" ]; then
403161633Srwatson		setvar "$__var_to_set" "$__pkg"
404161633Srwatson	else
405161633Srwatson		echo "$__pkg"
406161633Srwatson	fi
407161633Srwatson	return $SUCCESS
408161633Srwatson}
409171540Srwatson
410161633Srwatson############################################################ MAIN
411161633Srwatson
412161633Srwatsonf_dprintf "%s: Successfully loaded." packages/index.subr
413161633Srwatson
414161633Srwatsonfi # ! $_PACKAGES_INDEX_SUBR
415171540Srwatson