#!/bin/sh
# TazPkg - Tiny autonomous zone packages manager, hg.slitaz.org/tazpkg
# install - TazPkg module
# Install packages


# Connect function libraries
. /lib/libtaz.sh
. /usr/lib/slitaz/libpkg.sh


# Get TazPkg working environment
. @@MODULES@@/getenv
# $CACHE_DIR will change, it based on unchanged value of $SAVE_CACHE_DIR
SAVE_CACHE_DIR="$CACHE_DIR"


. @@MODULES@@/find-depends




# Log TazPkg activity

log_pkg() {
	debug "\nlog_pkg('$1')\n  PACKAGE='$PACKAGE'\n  VERSION='$VERSION'\n  EXTRAVERSION='$EXTRAVERSION'"

	local extra

	[ "$1" = 'Installed' ] && \
	extra=" - $(fgrep " $PACKAGE-$VERSION" "$PKGS_DB/installed.$SUM" | awk '{print $1}')"
	debug "  extra='$extra'"

	[ -w "$LOG" ] &&
	echo "$(date +'%F %T') - $1 - $PACKAGE ($VERSION$EXTRAVERSION)$extra" >> $LOG
}


# get an already installed package from packages.equiv

equivalent_pkg() {
	# input: $1 = dependency package name (like "nano");
	#        $2 = package path/name for which dependency tested
	local i rep rules rule out

	if [ -n "$local" ]; then
		# Search for virtual packages
		if [ -n "$cookmode" ]; then
			pi="/home/slitaz/packages/packages-$SLITAZ_ARCH.info"
			[ -e "$pi" ] || pi='/home/slitaz/packages/packages.info'
		else
			pi="$(dirname "$2")/packages.info"
		fi

		[ -f "$pi" ] &&
		out=$(awk -F$'\t' -vpkg="$1" '{
			# if package name or provided package name matched
			if ($1 == pkg || index(" " $10 " ", " " pkg " ")) { print $1 }
		}' "$pi")
		for i in $out; do
			# If package installed
			[ -f "$PKGS_DB/installed/$i/receipt" ] && out="$i" && break
			unset out
		done
	else
		rules=$(for rep in $PRIORITY; do
				grep -hs "^$(reesc "$1")=" "$rep/packages.equiv"
			done | sed "s|^$(reesc "$1")=||")
		debug "  >rules='$rules'"

		for rule in $rules; do
			debug "  >rule='$rule'"
			case $rule in
				*:*)
					debug '-- x:x'
					# format 'alternative:newname'
					# if alternative is installed then substitute newname
					out="${rule#*:}"
					awk -F$'\t' -vp="${rule%:*}" '$1==p{exit 1}' "$PKGS_DB/installed.info" || break
					debug '-- x:x /'
					;;
				*)
					debug '-- x'
					# unconditional substitution
					out="$rule"
					awk -F$'\t' -vp="$rule" '$1==p{exit 1}' "$PKGS_DB/installed.info" || break
					debug '-- x /'
					;;
			esac
			unset out
		done
	fi
	debug '--'
	# if not found in packages.equiv then no substitution
	echo "${out:-$1}"
}


# Check and install all missing deps.
# Auto install or ask user then install all missing deps from local dir, CD-ROM,
# media or from the mirror.

install_all_deps() {
	# input: $1 = package file to check/install missing dependencies
	# ROOT READY
	# dep: equivalent_pkg.

	debug "\ninstall_all_deps('$1')"

	local TMP_DIR DEPENDS num missing_packages equiv pkg answer dir found pkgfile recopts

	# Check for missing deps listed in a receipt packages.

	# Get the receipt's variable DEPENDS
	DEPENDS=$(
		# Skip silently if caller passed no package file.
		[ -z "$1" ] && exit 0
		# Callers (e.g. cookutils) may pass a relative path after
		# `cd $PKGS`. The mktemp+cd below would invalidate the
		# redirection, so absolutize the package path first.
		f="$1"; case "$f" in /*) ;; *) f="$PWD/$f" ;; esac
		[ -f "$f" ] || exit 0
		TMP_DIR=$(mktemp -d); cd "$TMP_DIR"
		cpio --quiet -i receipt < "$f" >/dev/null 2>&1
		[ -f ./receipt ] && . ./receipt && echo $DEPENDS
		rm -rf "$TMP_DIR"
	)

	unset num missing_packages
	for depend in $DEPENDS; do
		debug "  depend='$depend'"
		equiv=$(equivalent_pkg $depend "$1")
		debug "  equiv='$equiv'\n"
		if [ ! -d "$INSTALLED/$equiv" ]; then
			missing_packages="$missing_packages $equiv"
			num=$((num+1))
		elif [ ! -f "$INSTALLED/$equiv/receipt" ]; then
			[ -z "$quiet" ] && _ 'WARNING! Dependency loop between "%s" and "%s".' "$PACKAGE" "$equiv"
		fi
	done

	# Nothing to install, exit function
	[ -z "$num" ] && return


	title "$(_ 'Tracking dependencies for package "%s"' "$PACKAGE")"

	# Individual messages for each missing package
	[ -z "$quiet" ] && \
	for pkg in $missing_packages; do
		_ 'Missing package "%s"' "$pkg"
	done

	footer "$(_p \
		'%s missing package to install.' \
		'%s missing packages to install.' "$num" \
		"$num")"


	if [ "$AUTO_INSTALL_DEPS" = 'yes' ] || [ -n "$quiet" ]; then
		# Quietly not displaying anything. Assume 'yes' unless '--noconfirm' is provided
		answer=0
		[ -n "$noconfirm" ] && answer=1
	else
		# Display question; wait for answer or print auto-answer
		newline
		confirm "$(_ 'Install all missing dependencies? (y/N)')"
		answer=$?
		newline
	fi
	debug "  answer='$answer'"

	dir="$(dirname "$1")"
	debug "  dir='$dir'"

	# We can install packages from /home/boot/packages at boot time
	# Also we can prefer local packages over mirrored/cached using '--local' option
	[ "$dir" = '/home/boot/packages' ] && local='yes'
	debug "  local='$local'"

	# "--nodeps" option prevents to install dependencies
	if [ "$answer" -eq 0 -a -z "$nodeps" ]; then
		debug "  let's install missing packages"
		for pkg in $missing_packages; do
			debug "  pkg='$pkg'"
			[ -d "$INSTALLED/$pkg" ] && continue
			# Package not installed

			found='0'; namever=''; pkgfile=''
			# Prefer local packages
			if [ -n "$local" ]; then
				[ -z "$quiet" ] && _ 'Checking if package "%s" exists in local list...' "$pkg"
				[ -n "$cookmode" ] && dir='/home/slitaz/packages'

				pi="$dir/packages-$SLITAZ_ARCH.info"
				[ -e "$pi" ] || pi="$dir/packages.info"

				# Find local package
				if [ -f "$pi" ]; then
					# Packages database exists (should be everfresh!)

					# Find local package
					namever=$(awk -F$'\t' -vpkg="$pkg" '{
						if ($1 == pkg) { printf("%s-%s", $1, $2); exit; }
					}' "$pi")		# <namever> = <package_name>-<package_version>

					# Find local provided package
					[ -n "$namever" ] ||
					namever=$(awk -F$'\t' -vpkg="$pkg" '{
						if (index(" " $10 " ", " " pkg " ")) { printf("%s-%s", $1, $2); exit; }
					}' "$pi")		# <namever> = <package_name>-<package_version>

					# Package file may be in form <namever>-<arch>.tazpkg or <namever>-any.tazpkg, so find one
					if [ -n "$namever" ]; then
						pkgfile=$(find "$dir" -name "$namever-$SLITAZ_ARCH.tazpkg")
						[ -n "$pkgfile" ] ||
						pkgfile=$(find "$dir" -name "$namever-any.tazpkg")
						# i486 arch packages carry NO arch suffix (legacy quirk):
						# the file is just <namever>.tazpkg. Without this fallback
						# every i486 arch dep misses the local repo and drops to
						# the mirror get-install path -> "Checksum error" /
						# "md5sum: can't open" noise for a pkg we already have.
						[ -n "$pkgfile" ] ||
						pkgfile=$(find "$dir" -name "$namever.tazpkg")
					fi

					# packages.info is incremental: a pkg seeded into $dir
					# without a local cook has no index entry, so $namever is
					# empty above and the dep would silently vanish. Last
					# resort: glob the dir directly (digit-anchored version so
					# "nettle" doesn't match "nettle-dev").
					[ -n "$pkgfile" ] ||
					pkgfile=$(ls "$dir/$pkg"-[0-9]*.tazpkg 2>/dev/null | head -n1)

					[ -n "$pkgfile" ] && found='1'
				else
					# Packages DB missing, proceed to sniff packages
					tempd="$(mktemp -d)"; cd "$tempd"
					for pkgfile in $dir/$pkg-*-$SLITAZ_ARCH.tazpkg $dir/$pkg-*-any.tazpkg; do
						[ -e "$pkgfile" ] || continue
						# Extract receipt from each matched package
						cpio -F "$pkgfile" -i receipt >/dev/null 2>&1
						name=$(. ./receipt; echo $PACKAGE)
						[ "$name" = "$pkg" ] && found='1' && break
						# Install the first matched package: normally there is only one package
						# with the $PACKAGE matched in the receipt
						rm receipt
					done
					rm -r "$tempd"
				fi
			fi
			debug "    found='$found'"

			# Propagate the caller's resolution flags into the recursive
			# dep install, otherwise the dependency TREE silently leaves
			# local/cookmode mode: under TAZPKG_OFFLINE (cook's local bdep
			# install) a 2nd-level dep then can't be fetched and its files
			# (e.g. nettle/libtasn1/p11-kit .pc needed by `pkg-config gnutls`)
			# go missing -> curl & co. configure with SSL disabled. Keep the
			# whole tree local-first, quiet and on the right root.
			recopts=''
			[ -n "$local" ]    && recopts="$recopts --local"
			[ -n "$cookmode" ] && recopts="$recopts --cookmode"
			[ -n "$forced" ]   && recopts="$recopts --forced"
			[ -n "$quiet" ]    && recopts="$recopts --quiet"
			[ -n "$root" ]     && recopts="$recopts --root=$root"

			if [ "$found" -eq 1 ]
			then tazpkg install "$pkgfile" $recopts
			else tazpkg get-install "$pkg" $recopts
			fi
		done
	else
		# Answered 'No' to install dependencies, or '--nodeps' option given
		newline
		_ 'Leaving dependencies for package "%s" unresolved.' "$PACKAGE"
		_ 'The package will be installed but will probably not work.'
		newline
	fi
}


# Extract a package with cpio and gzip/lzma.

extract_package() {
	# input: $1 - path to package to be extracted; package should be in the current dir
	# ROOT INDEPENDENT
	action 'Extracting package...'

	# Extract "outer layer": cpio; remove the original package file
	cpio -idm --quiet < "$1" && rm -f "$1"

	# "Inner layer" may vary
	if [ -f fs.cpio.lzma ]; then
		# "Plain" cpio.lzma
		unlzma < fs.cpio.lzma | cpio -idm --quiet && rm fs.cpio.lzma
	elif [ -f fs.cpio.gz ]; then
		# "Fast" cpio.gz (used to pack-then-install process in most of get-packages)
		zcat fs.cpio.gz | cpio -idm --quiet && rm fs.cpio.gz
	fi

	status
}


# Print short package description

print_short_description() {
	# TODO: undigest repo support? priority...
	# ROOT READY
	local short_desc=''

	# Try to find localized short description
	for LC in $LANG ${LANG%_*}; do
		[ -e "$PKGS_DB/packages-desc.$LC" ] &&
			short_desc=$(awk -F$'\t' -vp="$1" '$1==p{print $2; exit}' "$PKGS_DB/packages-desc.$LC")
	done

	# Try to find short description for mirrored package
	if [ -z "$short_desc" ]; then
		pi="$PKGS_DB/packages-$SLITAZ_ARCH.info"
		[ -e "$pi" ] || pi="$PKGS_DB/packages.info"

		short_desc=$(awk -F$'\t' -vp="$1" '$1==p{print $4; exit}' "$pi")
	fi

	[ -z "$short_desc" ] && short_desc="$SHORT_DESC"

	longline "$short_desc"
}


grepesc() {
	sed 's/\[/\\[/g'
}




#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*

# Block of receipt function callers
# Why? "Bad" receipt sourcing can redefine some vital TazPkg variables.
# Few receipts functions should be patched now.

# Input: $1 = path to the receipt to be processed

# Pre-install commands
call_pre_install() {
	local tmp
	if grep -q '^pre_install()' "$1"; then
		action 'Execute pre-install commands...'
		tmp="$(mktemp)"
		cp "$1" "$tmp"
		sed -i 's|$1/*$INSTALLED|$INSTALLED|g' "$tmp"
		( . "$tmp"; pre_install "$root" )
		status
		rm "$tmp"
	fi

}
# Post-install commands
call_post_install() {
	local tmp
	if grep -q '^post_install()' "$1"; then
		action 'Execute post-install commands...'
		tmp="$(mktemp)"
		cp "$1" "$tmp"
		sed -i 's|$1/*$INSTALLED|$INSTALLED|g' "$tmp"
		( . "$tmp"; post_install "$root" )
		status
		rm "$tmp"
	fi
}


#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*


# This function installs a package in the rootfs.

install_package() {
	# input: $1 = path to package to be installed
	# dep: install_all_deps, print_short_description, extract_package, grepesc.

	debug "\ninstall_package('$1')"
	local dir

	PACKAGE_FILE="$1"
	# Bail out if invoked with no package file (e.g. an upstream lookup
	# returned empty); the caller will handle the empty result.
	[ -z "$PACKAGE_FILE" ] && return 1
	# Absolutize the path before the cd into TMP_DIR (callers may pass
	# a relative path after their own `cd`).
	case "$PACKAGE_FILE" in /*) ;; *) PACKAGE_FILE="$PWD/$PACKAGE_FILE" ;; esac
	[ -f "$PACKAGE_FILE" ] || { _ 'Package file "%s" not found.' "$PACKAGE_FILE" >&2; return 1; }
	TMP_DIR="$(mktemp -d)"

	# Get receipt's variables and functions
	{ cd "$TMP_DIR"; cpio --quiet -i receipt < "$PACKAGE_FILE" >/dev/null 2>&1; }
	# Why next code? "Bad" receipt sourcing can redefine some vital TazPkg variables.
	(
		. "$TMP_DIR/receipt"
		cat > "$TMP_DIR/receipt.var" <<EOT
PACKAGE="$PACKAGE"
VERSION="$VERSION"
EXTRAVERSION="$EXTRAVERSION"
CATEGORY="$CATEGORY"
SHORT_DESC="${SHORT_DESC//\"/\"}"
WEB_SITE="$WEB_SITE"
TAGS="$TAGS"
DEPENDS="$DEPENDS"
CONFIG_FILES="$CONFIG_FILES"
PACKED_SIZE="$PACKED_SIZE"
UNPACKED_SIZE="$UNPACKED_SIZE"
EOT
		rm "$TMP_DIR/receipt"
	)
	. "$TMP_DIR/receipt.var"


	# Make sure folder exists on new installs or upgrades
	mkdir -p "$INSTALLED/$PACKAGE"

	# Keep "modifiers" and "files.list" on upgrade
	find "$INSTALLED/$PACKAGE" -type f \( ! -name modifiers ! -name files.list \) -delete

	# Update "installed.md5"
	# TODO: discontinue using 'installed.md5'
	touch "$PKGS_DB/installed.$SUM"
	sed -i "/ $(reesc "$(basename "$PACKAGE_FILE")")$/d" "$PKGS_DB/installed.$SUM" 2>/dev/null
	cd "$(dirname "$PACKAGE_FILE")"
	$CHECKSUM "$(basename "$PACKAGE_FILE")" >> "$PKGS_DB/installed.$SUM"

	# Resolve package dependencies before package installation
	install_all_deps "$PACKAGE_FILE"


	# TODO: why this list-processed in the $PKGS_DB?
	#[ -n "$INSTALL_LIST" ] && echo "$PACKAGE_FILE" >> "$PKGS_DB/$INSTALL_LIST-processed"

	# Special mode for using in cookutils: clearly show whether freshly used package or cached one
	if [ -n "$cookmode" ]; then
		f=${PACKAGE_FILE%/*}; f=${f%/*}; f=${f##*/}
		if [ "$f" = "$(cat /etc/slitaz-release)" ]; then
			_ 'Installing (web/cache): %s' "$(basename $PACKAGE_FILE .tazpkg)"
		else
			_ 'Installing (pkg/local): %s' "$(basename $PACKAGE_FILE .tazpkg)"
		fi
	fi

	if [ -n "$sequence" ]; then
		title 'Installation of package "%s" (%s)' "$PACKAGE" "$sequence"
	else
		title 'Installation of package "%s"' "$PACKAGE"
	fi

	if [ -z "$quiet" ]; then
		print_short_description "$PACKAGE"
		separator '-'
	fi

	action 'Copying package...'
	cp "$PACKAGE_FILE" "$TMP_DIR"
	status

	cd "$TMP_DIR"
	extract_package "$(basename "$PACKAGE_FILE")"

	# Include temporary receipt to get the right variables
	. "$TMP_DIR/receipt.var"

	cd "$INSTALLED"


	# Get files to remove if upgrading
	# IFS here modified temporarily for processing filenames with spaces
	IFS=$'\n'
	if [ -f "$PACKAGE/files.list" ]; then
		while read file; do
			grep -q "^$(echo "$file" | grepesc)$" "$TMP_DIR/files.list" && continue
			for i in $(cat "$PACKAGE/modifiers" 2>/dev/null;
				fgrep -sl "$PACKAGE" */modifiers | cut -d/ -f1); do
				grep -qs "^$(echo "$file" | grepesc)$" "$i/files.list" && continue 2
			done
			echo "$file"
		done < "$PACKAGE/files.list" > "$TMP_DIR/files2remove.list"
	fi
	unset IFS


	# Remember modified packages
	action 'Remember modified packages...'
	{
		check=false
		# TODO: why '[' the special?
		# FIXME: we have files with spaces in our packages!
		for i in $(fgrep -v [ $TMP_DIR/files.list); do
			[ -e "$root$i" ] || continue
			[ -d "$root$i" ] && continue
			echo "- $i"
			check=true
		done ;
		$check && \
		for i in *; do
			[ "$i" = "$PACKAGE" ] && continue
			[ -s "$i/files.list" ] || continue
			awk "{ printf \"$i %s\\n\",\$1 }" < "$i/files.list"
		done;
	} | awk '
{
	if ($1 == "-" || file[$2] != "") {
		file[$2] = file[$2] " " $1
		if ($1 != "-") {
			if (pkg[$1] == "") all = all " " $1
			pkg[$1] = pkg[$1] " " $2
		}
	}
}
END {
	for (i = split(all, p, " "); i > 0; i--)
		for (j = split(pkg[p[i]], f, " "); j > 0; j--)
			printf "%s %s\n",p[i],f[j];
}
		' | while read dir file; do
		if grep -qs "^$(reesc "$dir")$" "$PACKAGE/modifiers"; then
			# Do not overload an overloaded file !
			rm "$TMP_DIR/$file" 2>/dev/null
			continue
		fi
		grep -qs "^$(reesc "$PACKAGE")$" "$dir/modifiers" && continue
		if [ -s "$dir/volatile.cpio.gz" ]; then
			# We can modify backed up files without notice
			zcat "$dir/volatile.cpio.gz" | cpio -t --quiet | \
				grep -q "^${file#/}$" && continue
		fi
		echo "$PACKAGE" >> "$dir/modifiers"
	done
	status


	cd "$TMP_DIR"
	# Copy receipt, etc.
	for file in receipt files.list description.txt $CHECKSUM; do
		[ -f "$file" ] && cp "$file" "$INSTALLED/$PACKAGE"
	done


	# Pre-install commands
	call_pre_install "$INSTALLED/$PACKAGE/receipt"


	if [ -n "$CONFIG_FILES" ]; then
		# Save "official" configuration files
		action 'Saving configuration files...'
		debug "\n"

		cd fs
		local config_file
		for config_file in $CONFIG_FILES; do
			debug "  config_file: '$config_file'"
			find ${config_file#/} -type f 2>/dev/null
		done | cpio -o -H newc --quiet | gzip -9 > "$INSTALLED/$PACKAGE/volatile.cpio.gz"
		cd ..

		if [ -z "$newconf" ]; then
			debug "  no '--newconf': clean official config files"
			# Keep user configuration files: remove "official" from fs tree
			for config_file in $CONFIG_FILES; do
				for config_file_official in $(find "fs$config_file" ! -type d 2>/dev/null | sed 's|^fs||'); do
					if [ -e "$root$config_file_official" ]; then
						debug "    official '$config_file_official' will be skipped"
						rm "fs$config_file_official"
					else
						debug "    official '$config_file_official' will be written"
					fi
				done
			done
		fi
		# always '[ Done ]' status, unless '--newconf' is passed or not
		:; status
	fi


	if [ -n "$(ls fs/* 2>/dev/null)" ]; then
		action 'Installing package...'

		debug '\n  resolving destination links in source'
		IFS=$'\n'
		for dir in $(find fs -type d | sed 's|^fs||;/^$/d'); do
			if ldir=$(readlink -n $root$dir); then
				debug "  * mv 'fs$dir'\n    -> 'fs${dir%/*}/$ldir'"
				mkdir -p "fs${dir%/*}/${ldir%/*}"
				mv "fs$dir" "fs${dir%/*}/$ldir"
			fi
		done
		unset IFS

		debug '  copying folders and files to destination'
		cp -af fs/* "$root/"
		status
	fi


	if [ -s files2remove.list ]; then
		action 'Removing old files...'
		while read file; do
			dir="$root$file"
			# Remove specified file
			rm -f "$dir"
			# Recursive remove non-empty up-dirs
			while [ "$dir" != "$root/" ]; do
				dir=$(dirname "$dir")
				rmdir "$dir" 2>/dev/null || break
			done
		done < files2remove.list
		:; status
	fi


	# Remove the temporary random directory.
	action "Removing all tmp files..."
	cd ..; rm -rf "$TMP_DIR"
	status


	# Post install commands
	call_post_install "$INSTALLED/$PACKAGE/receipt"




	# Update system databases
	# Updating DBs is important process, so not to hide such errors (localized):
	# chroot: can't execute '/usr/bin/***': No such file or directory

	local fl="$INSTALLED/$PACKAGE/files.list" upd=0 udesk umime uicon uschm ukrnl ukrnlfs

	fgrep    /usr/share/applications/    "$fl" | fgrep -q .desktop && udesk='yes'
	fgrep -q /usr/share/mime             "$fl" && umime='yes'
	fgrep -q /usr/share/icon/hicolor     "$fl" && uicon='yes'
	fgrep    /usr/share/glib-2.0/schemas "$fl" | fgrep -q .xml && uschm='yes'
	fgrep    /usr/lib/gdk-pixbuf         "$fl" | fgrep -q .so && upixb='yes'
	if fgrep -q /lib/modules             "$fl"; then
		ukrnl='yes'
		if fgrep -q /kernel/fs/ "$fl"; then
			ukrnlfs='yes'
		fi
	fi

	if [ -n "$udesk$umime$uicon$uschm$upixb$ukrnl" ]; then
		action 'Update system databases...'
		upd=1
	fi

	# package 'desktop-file-utils'
	[ -n "$udesk" ] && chroot "$root/" /usr/bin/update-desktop-database /usr/share/applications 2>/dev/null
	# package 'shared-mime-info'
	[ -n "$umime" ] && chroot "$root/" /usr/bin/update-mime-database /usr/share/mime
	# packages 'gtk+', 'gtk+3'
	[ -n "$uicon" ] && chroot "$root/" /usr/bin/gtk-update-icon-cache /usr/share/icons/hicolor
	# package 'glib'
	# hide messages like next because they are unresolved (we may to patch glib to hide them, almost the same)
	# warning: Schema '*' has path '*'.  Paths starting with '/apps/', '/desktop/' or '/system/' are deprecated.
	[ -n "$uschm" ] && chroot "$root/" /usr/bin/glib-compile-schemas /usr/share/glib-2.0/schemas 2>&1 | fgrep -v '/apps/'
	# package 'gdk-pixbuf'
	[ -n "$upixb" ] && chroot "$root/" /usr/bin/gdk-pixbuf-query-loaders --update-cache

	if [ -n "$ukrnlfs" ]; then
		for i in $(awk -F/ '{if($6=="fs" && $8~$7)print $7}' "$fl" | sort -u); do
			touch "$root/etc/filesystems"
			grep -q "^$i\$" "$root/etc/filesystems" || echo "$i" >> "$root/etc/filesystems"
		done
	fi
	# packages 'busybox', 'kmod', 'depmod'
	[ -n "$ukrnl" ] && grep '/lib/modules' "$fl" | cut -d'/' -f4 | uniq | xargs chroot "$root/" /sbin/depmod -a

	[ "$upd" -eq 1 ] && status




	# Update installed.info ----------------------------------------------------
	SIZES=$(echo $PACKED_SIZE $UNPACKED_SIZE | sed 's|\.0||g')

	# Remove newlines from some receipts
	DEPENDS=$(echo $DEPENDS)
	PKG_SUM="$(fgrep " $PACKAGE-$VERSION$EXTRAVERSION.tazpkg" "$PKGS_DB/installed.$SUM" | cut -d' ' -f1)"
	
	# Calculate "release checksum": md5sum of file containing md5sums of:
	# a) all files, b) receipt, and c) description.txt.
	rsumf=$(mktemp)
	cp $INSTALLED/$PACKAGE/md5sum $rsumf
	md5sum $INSTALLED/$PACKAGE/receipt | sed 's| [^ ]*/| |' >> $rsumf
	[ -e "$INSTALLED/$PACKAGE/description.txt" ] &&
		md5sum $INSTALLED/$PACKAGE/description.txt | sed 's| [^ ]*/| |' >> $rsumf
	RSUM=$(md5sum $rsumf | awk '{print $1}')
	rm $rsumf

	ii="$PKGS_DB/installed.info"

	# Remove old entry
	sed -i "/^$(reesc "$PACKAGE")	/d" "$ii"

	cat >> "$ii" <<EOT
$PACKAGE	$VERSION$EXTRAVERSION	$CATEGORY	$SHORT_DESC	$WEB_SITE	$TAGS	$SIZES	$DEPENDS	$PKG_SUM
EOT

	TEMP_FILE="$(mktemp)"
	sort "$ii" > "$TEMP_FILE"; mv -f "$TEMP_FILE" "$ii"; chmod a+r "$ii"; unset ii
	# --------------------------------------------------------------------------

	cd "$CUR_DIR"
	footer "$(_ 'Package "%s" (%s) is installed.' "$PACKAGE" "$VERSION$EXTRAVERSION")"

	# Log this activity
	log_pkg Installed

	# Remove package from upgrade list
	[ -s "$UP_LIST" ] && sed -i "/^$(reesc "$PACKAGE")\$/d" "$UP_LIST"
}




#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*


PACKAGE=$(
	# Skip silently if invoked with no package file.
	[ -z "$1" ] && exit 0
	# Absolutize the package path: cookutils invokes `tazpkg install` with
	# a bare filename after `cd $PKGS`, but the mktemp+cd below would
	# break the redirection that follows.
	f="$1"; case "$f" in /*) ;; *) f="$PWD/$f" ;; esac
	[ -f "$f" ] || exit 0
	tmp_dir=$(mktemp -d); cd "$tmp_dir"
	cpio --quiet -i receipt < "$f" >/dev/null 2>&1
	[ -f ./receipt ] && . ./receipt && echo $PACKAGE
	rm -rf "$tmp_dir"
)

if grep -qs "^$(reesc "$PACKAGE")$" "$BLOCKED"; then
	_ 'Package "%s" blocked.' "$PACKAGE"
	exit 1
fi

if [ -z "$forced" ]; then
	# Check if a package is already installed
	debug "\ncheck for installed package '$PACKAGE'"

	awk -F$'\t' -vpv="$PACKAGE" '$1==pv { exit 1 }' "$PKGS_DB/installed.info"

	if [ "$?" -eq 1 ]; then
		if [ -z "$quiet" ]; then
			newline
			_ '"%s" package is already installed.' "$(colorize 34 "$PACKAGE")"
			longline "$(_ 'You can use the --forced option to force installation.')"
			newline
		fi
		exit 1
	fi
fi

if [ -f /var/lock/tazpkgup.lock ] && [ "$PACKAGE" != "tazpkg" ]; then
	newline
	emsg "<n><c 31>$(_ 'You need upgrade tazpkg first !')</c><n>"
	emsg "$(_ "run <c 32>tazpkg -gi tazpkg --forced</c> then relaunch <c 32>tazpkg -gi %s</c>" "$PACKAGE")<n>"
	newline
else
	install_package "$(realpath "$1")"
fi
