From d7325b15a0949cef77707be5fd0bf864d6338c57 Mon Sep 17 00:00:00 2001
From: P. J. McDermott <pj@pehjota.net>
Date: Tue, 07 Oct 2014 20:30:51 -0400
Subject: Merge branch 'feature/sessions'

---
(limited to 'lib')

diff --git a/lib/cmd/build.sh b/lib/cmd/build.sh
index 140ef4a..32dca26 100644
--- a/lib/cmd/build.sh
+++ b/lib/cmd/build.sh
@@ -19,11 +19,15 @@
 # <http://www.gnu.org/licenses/>.
 
 use profile
-use chroot
+use session
 use rand
 use package
 use control
 
+cmd_build_root=
+cmd_build_pkg_dir=
+cmd_build_build_deps=
+
 cmd_build_main()
 {
 	local root=
@@ -31,12 +35,7 @@ cmd_build_main()
 	local arch=
 	local plat=
 	local first_arg=
-	local prev_arg=
 	local arg=
-	local dir=
-	local uname_s=
-	local build_deps=
-	local f=
 
 	if [ ${#} -lt 2 ]; then
 		print_cmd_usage 'build' >&2
@@ -44,6 +43,7 @@ cmd_build_main()
 	fi
 
 	root="${1}"
+	cmd_build_root="${root}"
 	shift 1
 
 	opkbuild_optstring="$(cat "${root}/usr/share/opkbuild/optstring")"
@@ -51,98 +51,90 @@ cmd_build_main()
 	plat="$(cat "${root}/etc/proteanos_plat")"
 	while getopts "${opkbuild_optstring}" opt 2>/dev/null; do
 		case "${opt}" in
-			a)
-				arch="${OPTARG}"
-				;;
-			p)
-				plat="${OPTARG}"
-				;;
+			a) arch="${OPTARG}";;
+			p) plat="${OPTARG}";;
 		esac
 	done
 
 	first_arg=true
-	prev_arg=
+	cmd_build_pkg_dir=''
 	for arg in "${@}"; do
 		if ${first_arg}; then
 			set --
 			first_arg=false
 		else
-			set -- "${@}" "${prev_arg}"
+			set -- "${@}" "${cmd_build_pkg_dir}"
 		fi
-		prev_arg="${arg}"
+		cmd_build_pkg_dir="${arg}"
 	done
 
 	. "${root}/etc/os-release"
 	profile_set "${ID}"
 
-	if ! [ -d "${prev_arg}" ]; then
-		error 2 "$(get_msg 'cmd_build_not_a_dir')" "${prev_arg}"
+	if ! [ -d "${cmd_build_pkg_dir}" ]; then
+		error 2 "$(get_msg 'cmd_build_not_a_dir')" \
+			"${cmd_build_pkg_dir}"
 	fi
-	package_init "${prev_arg}"
+	package_init "${cmd_build_pkg_dir}"
 	package_set_substvars "${arch}" "${plat}"
 
-	rand
-	dir="/prokit/build.${rand_x}"
-	uname_s="$( (uname -s) 2>/dev/null)" || uname_s='unknown'
-	mkdir -p "${root}${dir}/pkg"
-	case "${uname_s}" in
-		'Linux')
-			mount -o bind "${prev_arg}" "${root}${dir}/pkg"
-			;;
-	esac
-
-	build_deps="$(package_get_build_deps "${arch}" "${plat}")"
-	if [ "x${build_deps}" != 'x' ]; then
-		mkdir -p "${root}${dir}/builddeps/control" \
-		"${root}${dir}/builddeps/data"
-		cmd_build_make_deps_pkg "${root}${dir}" "${rand_x}" \
-			"${build_deps}"
+	session_begin "${root}" "${cmd_build_pkg_dir}" cmd_build_fini false
+
+	cmd_build_build_deps="$(package_get_build_deps "${arch}" "${plat}")"
+	if [ "x${cmd_build_build_deps}" != 'x' ]; then
+		cmd_build_make_deps_pkg
+		session_exec opkg install ../builddeps.opk
 	fi
 
-	chroot_exec "${root}" sh <<-EOF
-		cd '${dir}/pkg'
-		if [ 'x${build_deps}' != 'x' ]; then
-			opkg install ../builddeps.opk
-		fi
-		opkbuild ${@}
-		if [ 'x${build_deps}' != 'x' ]; then
-			opkg --autoremove remove prokit-builddeps-${rand_x}
-		fi
-		EOF
+	session_exec opkbuild "${@}"
 
-	case "${uname_s}" in
-		'Linux')
-			umount "${root}${dir}/pkg"
-			;;
-	esac
-	rmdir "${root}${dir}/pkg"
-	rm -f "${root}${dir}/builddeps.opk"
-	for f in "${root}${dir}/"*; do
-		[ -e "${f}" ] || continue
-		mv "${f}" "${prev_arg}/.."
-	done
-	rmdir "${root}${dir}"
+	cmd_build_fini
+
+	session_end
 }
 
 cmd_build_make_deps_pkg()
 {
-	local build_dir="${1}"
-	local build_id="${2}"
-	local build_deps="${3}"
+	local pkg_dir=
 
-	printf '2.0\n' >"${build_dir}/builddeps/debian-binary"
-	cat >"${build_dir}/builddeps/control/control" <<-EOF
-		Package: prokit-builddeps-${build_id}
+	pkg_dir="${cmd_build_root}$(session_dir)/builddeps"
+	mkdir -p "${pkg_dir}/control" "${pkg_dir}/data"
+	printf '2.0\n' >"${pkg_dir}/debian-binary"
+	cat >"${pkg_dir}/control/control" <<-EOF
+		Package: prokit-builddeps-$(session_id)
 		Source: prokit
 		Version: 1.0
 		Architecture: all
 		Platform: all
-		Depends: ${build_deps}
+		Depends: ${cmd_build_build_deps}
 		Description: Build dependencies metapackage generated by prokit
 		EOF
-	(cd "${build_dir}/builddeps/data"; tar -czf ../data.tar.gz .)
-	(cd "${build_dir}/builddeps/control"; tar -czf ../control.tar.gz .)
-	(cd "${build_dir}/builddeps"; tar -czf ../builddeps.opk \
+	(cd "${pkg_dir}/data"; tar -czf ../data.tar.gz .)
+	(cd "${pkg_dir}/control"; tar -czf ../control.tar.gz .)
+	(cd "${pkg_dir}"; tar -czf ../builddeps.opk \
 		'debian-binary' 'data.tar.gz' 'control.tar.gz')
-	rm -Rf "${build_dir}/builddeps"
+	rm -Rf "${pkg_dir}"
+}
+
+cmd_build_fini()
+{
+	local session_dir=
+	local f=
+
+	session_dir="$(session_dir)"
+
+	if [ "x${cmd_build_build_deps}" != 'x' ]; then
+		session_exec opkg --autoremove remove \
+			prokit-builddeps-$(session_id)
+		rm "${cmd_build_root}${session_dir}/builddeps.opk"
+		# Hack to avoid this code branch if the function is called again
+		# in response to a signal.
+		cmd_build_build_deps=''
+	fi
+
+	for f in "${cmd_build_root}${session_dir}/"*; do
+		[ -e "${f}" ] || continue
+		[ "x${f##*/}" = 'xwd' ] && continue
+		mv "${f}" "${cmd_build_pkg_dir}/.."
+	done
 }
diff --git a/lib/cmd/install.sh b/lib/cmd/install.sh
index 6c1e6ce..529382d 100644
--- a/lib/cmd/install.sh
+++ b/lib/cmd/install.sh
@@ -100,9 +100,10 @@ cmd_install_main()
 	if [ -d "${chroot}" ]; then
 		error 2 "$(get_msg 'cmd_install_chroot_dir_exists')" "${chroot}"
 	fi
-	if ! mkdir -p "${chroot}/.prokit"; then
+	if ! mkdir -p "${chroot}/.prokit" "${chroot}/prokit"; then
 		error 2 "$(get_msg 'cmd_install_mkdir_chroot_fail')" "${chroot}"
 	fi
+	>"${chroot}/prokit/installing"
 
 	info "$(get_msg 'cmd_install_find_pkgs')"
 	cmd_install_find_pkgs "${mirror}" "${suite}" "${arch}" "${plat}" \
@@ -115,6 +116,8 @@ cmd_install_main()
 	else
 		profile_configure_system_foreign "${chroot}"
 	fi
+
+	rm "${chroot}/prokit/installing"
 }
 
 cmd_install_find_pkgs()
diff --git a/lib/cmd/opkg.sh b/lib/cmd/opkg.sh
index 3016773..9f0c0de 100644
--- a/lib/cmd/opkg.sh
+++ b/lib/cmd/opkg.sh
@@ -19,15 +19,16 @@
 # <http://www.gnu.org/licenses/>.
 
 use profile
-use chroot
+use session
 use rand
 
+cmd_opkg_opks=
+
 cmd_opkg_main()
 {
 	local root=
 	local first_arg=
 	local install_cmd=
-	local opks=
 	local arg=
 	local new_fname=
 
@@ -41,7 +42,7 @@ cmd_opkg_main()
 
 	first_arg=true
 	install_cmd=false
-	opks=''
+	cmd_opkg_opks=''
 	for arg in "${@}"; do
 		if ${first_arg}; then
 			set --
@@ -56,7 +57,9 @@ cmd_opkg_main()
 					fi
 					rand
 					new_fname="/tmp/prokit.${rand_x}.opk"
-					opks="${opks} ${root}${new_fname}"
+					cmd_opkg_opks="$(printf '%s %s\n' \
+						"${cmd_opkg_opks}" \
+						"${root}${new_fname}")"
 					cp "${arg}" "${root}${new_fname}"
 					set -- "${@}" "${new_fname}"
 					;;
@@ -75,11 +78,22 @@ cmd_opkg_main()
 	. "${root}/etc/os-release"
 	profile_set "${ID}"
 
-	chroot_exec "${root}" opkg "${@}"
+	session_begin "${root}" . cmd_opkg_fini false
+
+	session_exec opkg "${@}"
 
-	case "${opks}" in
+	cmd_opkg_fini
+	session_end
+}
+
+cmd_opkg_fini()
+{
+	case "${cmd_opkg_opks}" in
 		*[!\ ]*)
-			rm -f ${opks}
+			rm -f ${cmd_opkg_opks}
+			# Hack to avoid this code branch if the function is
+			# called again in response to a signal.
+			cmd_opkg_opks=''
 			;;
 	esac
 }
diff --git a/lib/cmd/shell.sh b/lib/cmd/shell.sh
index 5c85d7e..dfff07f 100644
--- a/lib/cmd/shell.sh
+++ b/lib/cmd/shell.sh
@@ -19,12 +19,11 @@
 # <http://www.gnu.org/licenses/>.
 
 use profile
-use chroot
+use session
 
 cmd_shell_main()
 {
 	local root=
-	local uname_s=
 
 	if [ ${#} -lt 1 ]; then
 		print_cmd_usage 'shell' >&2
@@ -37,28 +36,13 @@ cmd_shell_main()
 	. "${root}/etc/os-release"
 	profile_set "${ID}"
 
-	uname_s="$( (uname -s) 2>/dev/null)" || uname_s='unknown'
-	mkdir -p "${root}/prokit/wd/${$}"
-	case "${uname_s}" in
-		'Linux')
-			mount -o bind . "${root}/prokit/wd/${$}"
-			;;
-	esac
-
-	chroot_mount "${root}"
+	session_begin "${root}" . : false
 	if [ ${#} -eq 0 ]; then
-		chroot "${root}" /bin/sh -c "cd '/prokit/wd/${$}'; /bin/sh"
+		session_exec /bin/sh
 		printf '\n'
 		info "$(get_msg 'cmd_shell_exiting')"
 	else
-		chroot "${root}" /bin/sh -c "cd '/prokit/wd/${$}'; ${*}"
+		session_exec "${@}"
 	fi
-	chroot_umount "${root}"
-
-	case "${uname_s}" in
-		'Linux')
-			umount "${root}/prokit/wd/${$}"
-			;;
-	esac
-	rmdir "${root}/prokit/wd/${$}"
+	session_end
 }
diff --git a/lib/local.mk b/lib/local.mk
index d593289..66d203f 100644
--- a/lib/local.mk
+++ b/lib/local.mk
@@ -9,6 +9,8 @@ pkgdata_sources = \
 	lib/control.sh \
 	lib/feed.sh \
 	lib/pkg.sh \
+	lib/mutex.sh \
+	lib/session.sh \
 	lib/chroot.sh \
 	lib/opkg.sh \
 	lib/rand.sh \
diff --git a/lib/mutex.sh b/lib/mutex.sh
new file mode 100644
index 0000000..fb96e66
--- /dev/null
+++ b/lib/mutex.sh
@@ -0,0 +1,57 @@
+# Functions for locking and unlocking mutex objects
+#
+# Copyright (C) 2014  Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit is free software: you can redistribute
+# it and/or modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# The ProteanOS Development Kit is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with the ProteanOS Development Kit.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+[ "x${_MUTEX_SM+set}" = 'xset' ] && return 0
+_MUTEX_SM=1
+
+mutex_trylock()
+{
+	local mutex="${1}"
+
+	(set -C; printf '%d\n' "${$}" >"${mutex}") 2>/dev/null
+}
+
+mutex_timedlock()
+{
+	local mutex="${1}"
+	local timeout="${2}"
+
+	while ! mutex_trylock "${mutex}"; do
+		[ ${timeout} -eq 0 ] && return 1
+		timeout=$(($timeout - 1))
+		sleep 1
+	done
+
+	return 0
+}
+
+mutex_unlock()
+{
+	local mutex="${1}"
+
+	rm "${mutex}"
+}
+
+mutex_is_unlockable()
+{
+	local mutex="${1}"
+
+	[ "x$(cat "${mutex}" 2>/dev/null)" = "x${$}" ]
+}
diff --git a/lib/opkg.sh b/lib/opkg.sh
index 1966ebc..2b942d3 100644
--- a/lib/opkg.sh
+++ b/lib/opkg.sh
@@ -21,12 +21,14 @@
 [ "x${_OPKG_SM+set}" = 'xset' ] && return 0
 _OPKG_SM=1
 
-use chroot
+use session
 
 opkg_install_all()
 {
 	local root="${1}"
 
-	chroot_exec "${root}" /bin/sh -c \
+	session_begin "${root}" . : true
+	session_exec /bin/sh -c \
 		'opkg install $(opkg list-installed | cut -d " " -f 1)'
+	session_end
 }
diff --git a/lib/output.sh b/lib/output.sh
index ffe982d..877c7fc 100644
--- a/lib/output.sh
+++ b/lib/output.sh
@@ -30,7 +30,10 @@ error()
 	printf '%s: Error: ' "${0##*/}" >&2
 	printf "${fmt}\n" "${@}" >&2
 
-	exit ${status}
+	# In a subshell, this will have no effect, so the shell's exit status
+	# will be 128+SIGINT.  Meh.
+	exit_status=${status}
+	kill -s INT ${$}
 }
 
 warn()
diff --git a/lib/profile.sh b/lib/profile.sh
index caa1160..5ec2309 100644
--- a/lib/profile.sh
+++ b/lib/profile.sh
@@ -124,6 +124,37 @@ profile_get_fstab()
 	"prof_${profile}_get_fstab" "${arch}" "${plat}"
 }
 
+profile_file_systems_mounted()
+{
+	local root="${1}"
+	local arch="${1}"
+	local plat="${2}"
+
+	"prof_${profile}_file_systems_mounted" "${root}" "${arch}" "${plat}"
+}
+
+profile_bind_mount()
+{
+	local arch="${1}"
+	local plat="${2}"
+	local olddir="${3}"
+	local newdir="${4}"
+
+	"prof_${profile}_bind_mount" "${arch}" "${plat}" \
+		"${olddir}" "${newdir}"
+}
+
+profile_bind_umount()
+{
+	local arch="${1}"
+	local plat="${2}"
+	local olddir="${3}"
+	local newdir="${4}"
+
+	"prof_${profile}_bind_umount" "${arch}" "${plat}" \
+		"${olddir}" "${newdir}"
+}
+
 profile_configure_system_native()
 {
 	local root="${1}"
diff --git a/lib/profile/proteanos.sh b/lib/profile/proteanos.sh
index 2e0739c..ee3627d 100644
--- a/lib/profile/proteanos.sh
+++ b/lib/profile/proteanos.sh
@@ -175,6 +175,43 @@ prof_proteanos_get_fstab()
 	esac
 }
 
+prof_proteanos_file_systems_mounted()
+{
+	local root="${1}"
+	local arch="${2}"
+	local plat="${3}"
+
+	[ -e "${root}/dev/null" ]
+}
+
+prof_proteanos_bind_mount()
+{
+	local arch="${1}"
+	local plat="${2}"
+	local olddir="${3}"
+	local newdir="${4}"
+
+	case "${arch}" in
+		*-linux-*)
+			mount -o bind "${olddir}" "${newdir}"
+			;;
+	esac
+}
+
+prof_proteanos_bind_umount()
+{
+	local arch="${1}"
+	local plat="${2}"
+	local olddir="${3}"
+	local newdir="${4}"
+
+	case "${arch}" in
+		*-linux-*)
+			umount "${newdir}"
+			;;
+	esac
+}
+
 prof_proteanos_configure_system_native()
 {
 	local root="${1}"
diff --git a/lib/session.sh b/lib/session.sh
new file mode 100644
index 0000000..05fecc4
--- /dev/null
+++ b/lib/session.sh
@@ -0,0 +1,219 @@
+# Functions for managing prokit sessions
+#
+# Copyright (C) 2014  Patrick "P. J." McDermott
+#
+# This file is part of the ProteanOS Development Kit.
+#
+# The ProteanOS Development Kit is free software: you can redistribute
+# it and/or modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# The ProteanOS Development Kit is distributed in the hope that it
+# will be useful, but WITHOUT ANY WARRANTY; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with the ProteanOS Development Kit.  If not, see
+# <http://www.gnu.org/licenses/>.
+
+[ "x${_SESSION_SM+set}" = 'xset' ] && return 0
+_SESSION_SM=1
+
+use rand
+use mutex
+use output
+use locale
+use profile
+
+session_id=
+session_root=
+session_arch=
+session_plat=
+session_mountdir=
+session_atexit=
+session_sigs=
+
+session_begin()
+{
+	local root="${1}"
+	local mountdir="${2}"
+	local atexit="${3}"
+	local installing="${4}"
+
+	rand
+	session_id=${rand_x}
+	session_root="${root}"
+	session_arch="$(cat "${root}/etc/proteanos_arch")"
+	session_plat="$(cat "${root}/etc/proteanos_plat")"
+	session_mountdir="${mountdir}"
+	session_atexit="${atexit}"
+
+	[ -d "${session_root}/prokit" ] || mkdir "${session_root}/prokit"
+	if [ -f "${session_root}/prokit/installing" ] && ! ${installing}; then
+		error 2 "$(get_msg 'install_running')"
+	fi
+
+	session_set_sigs
+
+	if ! mutex_timedlock "${session_root}/prokit/sessions.lock" 5; then
+		error 2 "$(get_msg 'cant_lock_sessions')"
+	fi
+
+	# Check for a sessions pool.
+	if [ -d "${session_root}/prokit/sessions" ]; then
+		if ! profile_file_systems_mounted "${session_root}" \
+				"${session_arch}" "${session_plat}"; then
+			# If a sessions pool exists but the file systems aren't
+			# mounted, clean up the old sessions and mount the file
+			# systems.
+			rmdir "${session_root}/prokit/sessions/"*
+			session_mount
+		fi
+	else
+		# If the sessions pool doesn't exist, create it and mount the
+		# file systems.
+		mkdir "${session_root}/prokit/sessions"
+		session_mount
+	fi
+
+	# Register the session.
+	mkdir "${session_root}/prokit/sessions/${session_id}"
+	mkdir "${session_root}/prokit/sessions/${session_id}/wd"
+	profile_bind_mount "${session_arch}" "${session_plat}" \
+		"${session_mountdir}" \
+		"${session_root}/prokit/sessions/${session_id}/wd"
+
+	mutex_unlock "${session_root}/prokit/sessions.lock"
+}
+
+session_end()
+{
+	trap : ${session_sigs}
+
+	${session_atexit}
+
+	# Unregister the session.
+	profile_bind_umount "${session_arch}" "${session_plat}" \
+		"${session_mountdir}" \
+		"${session_root}/prokit/sessions/${session_id}/wd"
+	rmdir "${session_root}/prokit/sessions/${session_id}/wd"
+	rmdir "${session_root}/prokit/sessions/${session_id}"
+
+	if ! mutex_is_unlockable "${session_root}/prokit/sessions.lock"; then
+		if ! mutex_timedlock "${session_root}/prokit/sessions.lock" 5
+		then
+			error 2 "$(get_msg 'cant_lock_sessions')"
+		fi
+	fi
+
+	# Clear the sessions pool.  If there are no more sessions, unmount the
+	# file systems.
+	if rmdir "${session_root}/prokit/sessions" 2>/dev/null; then
+		session_umount
+	fi
+
+	mutex_unlock "${session_root}/prokit/sessions.lock"
+	
+	trap - ${session_sigs}
+}
+
+session_id()
+{
+	printf '%d\n' ${session_id}
+	return 0
+}
+
+session_dir()
+{
+	printf '/prokit/sessions/%d\n' ${session_id}
+	return 0
+}
+
+session_exec()
+{
+	local args=
+	local session_dir=
+
+	args=''
+	for arg in "${@}"; do
+		arg="'$(printf '%s\n' "${arg}" | sed "s/'/'\\\\''/g")'"
+		args="${args} ${arg}"
+	done
+	session_dir="/prokit/sessions/${session_id}/wd"
+	chroot "${session_root}" /bin/sh -c "cd ${session_dir}; ${args}"
+}
+
+session_mount()
+{
+	local fs=
+	local dir=
+	local fstype=
+	local options=
+
+	while read fs dir fstype options; do
+		[ "x${dir}" = 'x' ] && continue
+		mount -t "${fstype}" -o "${options}" "${fs}" \
+			"${session_root}/${dir}"
+	done <<-EOF
+		$(profile_get_fstab "${session_arch}" "${session_plat}")
+		EOF
+}
+
+session_umount()
+{
+	local fs=
+	local dir=
+	local fstype=
+	local options=
+
+	while read fs dir fstype options; do
+		[ "x${dir}" = 'x' ] && continue
+		# umount sometimes complains that the /dev file system is busy.
+		# Here's a kludge to try to handle that.  We better make sure
+		# bind mounts get unmounted; otherwise, `rm -Rf ${root}` can be
+		# painful.
+		while ! umount "${session_root}/${dir}"; do
+			sleep 1
+		done
+	done <<-EOF
+		$(profile_get_fstab "${session_arch}" "${session_plat}" | \
+			sed -n '1!G;h;$p')
+		EOF
+}
+
+session_set_sigs()
+{
+	local i=
+	local sig=
+
+	# We need the signal *number* in the signal handler.  The only portable
+	# and easy way to get the number of a named signal is to search for it
+	# as in the following loop hack.
+	i=0
+	session_sigs=''
+	while [ ${i} -lt 127 ]; do
+		i=$(($i + 1))
+		sig="$(kill -l ${i} 2>/dev/null)" || continue
+		case "${sig}" in
+			'HUP' | 'INT' | 'QUIT' | 'ABRT' | 'ALRM' | 'TERM')
+				session_sigs="${session_sigs} ${i}"
+				trap "session_handle_sig ${i}" ${i}
+				;;
+		esac
+	done
+}
+
+session_handle_sig()
+{
+	local sig="${1}"
+
+	session_end
+
+	if [ "x${exit_status:+set}" = 'xset' ]; then
+		exit ${exit_status}
+	else
+		exit $((128 + $sig))
+	fi
+}
--
cgit v0.9.1