From d7325b15a0949cef77707be5fd0bf864d6338c57 Mon Sep 17 00:00:00 2001 From: P. J. McDermott Date: Tue, 07 Oct 2014 20:30:51 -0400 Subject: Merge branch 'feature/sessions' --- 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 @@ # . 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 @@ # . 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 @@ # . 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 +# . + +[ "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 +# . + +[ "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 +} diff --git a/locale/en_US.sh b/locale/en_US.sh index 06f3250..75cc1d1 100644 --- a/locale/en_US.sh +++ b/locale/en_US.sh @@ -43,6 +43,11 @@ msg_prokit_control_found_contination='found continuation line where expected '\ msg_prokit_list_item_separator=', ' msg_prokit_control_missing_fields='missing fields: %s' +# lib/session.sh +msg_prokit_cant_lock_sessions='Cannot acquire sessions state lock' +msg_prokit_install_running='Another instance of prokit is installing this '\ +'system' + # lib/rand.sh msg_prokit_rand_bad_x='Invalid random number generator seed value' -- cgit v0.9.1