#!/usr/bin/env bash # 2018 Luke Shumaker declare -r NAME=osi-mount declare -r VERSION=20180812 set -euE source "${BASH_SOURCE[0]%/*}/lib/osi.sh" main() { needs_root local arg_mode=outside local arg_user= local args if ! args="$(getopt -n "${0##*/}" -o hV -l inside:,user,root,help,version -- "$@")"; then arg_mode=error else eval "args=($args)" set -- "${args[@]}" while true; do case "$1" in --inside) shift; arg_mode=$1; shift;; --user|--root) if [[ -n $arg_user ]]; then error 0 "Multiple --user/--root flags given" arg_mode=error fi arg_user=${1#--} shift ;; -V|--version) shift; arg_mode=version;; -h|--help) shift; arg_mode=usage;; --) shift; break;; *) error 1 'Internal error. The programmer writing this tool screwed up.';; esac done case "$arg_mode" in outside) case "$#" in 0) error 0 "Must specify an image, mountpoint, and a command"; arg_mode=error;; 1) error 0 "Must specify a mountpoint and a command"; arg_mode=error;; 2) error 0 "Must specify a command"; arg_mode=error;; esac if [[ -z $arg_user ]]; then error 0 "Must specify either --root or --user" arg_mode=error fi ;; esac fi arg_device=$1 arg_mountpoint=$2 arg_cmd=("${@:3}") case "$arg_mode" in error) print "Try '%q --help' for more information" "${0##*/}" >&2; return 2;; version) print "%s (notsystemd) %s" "$NAME" "$VERSION" return 0 ;; usage) print 'Usage: %s [OPTIONS] -- FILENAME.img COMMAND...' "${0##*/}" print 'Operating System Image: Mount' echo print 'OPTIONS:' # --inside is internal-only; undocumented print ' --user mount the image RO, run COMMAND as SUDO_USER' print ' --root mount the image RW, run COMMAND as root, protect the rest of the system' echo print ' -h, --help display this help' print ' -V, --version output version information' return 0 ;; outside) unshare --mount "${BASH_SOURCE[0]}" --inside="$arg_user" "${args[@]}" if out="$(losetup --associated "$arg_device")" && [[ -n $out ]]; then error 0 "umount'ed, but file is still associated with a loop device: %s" "$arg_device" printf '%s\n' "$out" exit 1 fi ;; user|root) needs_sudo mount --make-rslave / case "$arg_mode" in user) mount_opt=ro losetup_flags=(--read-only) ;; root) mount_opt=rw losetup_flags=() # TODO: make the rest of the system RO ;; esac if out="$(losetup --associated "$arg_device")" && [[ -n $out ]]; then error 0 "Seems to already be mounted somewhere: %s" "$arg_device" printf '%s\n' "$out" exit 1 fi # Use manual losetup creation/desctruction # because the "autoclear" flag is lazy, and we # need to be eager. loopdev=$(losetup --find --show "${losetup_flags[@]}" "$arg_device") trap "losetup --detach ${loopdev@Q}" EXIT ( cmd=(mount --no-mtab -o "${mount_opt},sync,discard" -- "$loopdev" "$arg_mountpoint") r=0 out=$("${cmd[@]}" 2>&1) || r=$? if (( r != 0)); then error $r '%s: %s' "${cmd[*]@Q}" "$out" fi trap "umount --no-mtab -- ${arg_mountpoint@Q}" EXIT case "$arg_mode" in user) sudo -u "#${SUDO_UID}" -- "${arg_cmd[@]}";; root) command -- "${arg_cmd[@]}";; esac ) ;; *) error 1 'Internal error. The programmer writing this tool screwed up.';; esac } main "$@"