152 lines
4.3 KiB
Bash
Executable File
152 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Copyright (C) 2018 Luke Shumaker
|
|
# Copyright (C) 2023-2024 Umorpha Systems
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
declare -r NAME=osi-mount
|
|
declare -r VERSION=20231023
|
|
|
|
set -euE
|
|
|
|
install_prefix="$(realpath --logical --canonicalize-missing -- "${BASH_SOURCE[0]}/../..")"
|
|
readonly install_prefix
|
|
|
|
source "${install_prefix}/lib/osi.sh"
|
|
source "${install_prefix}/lib/argparse.sh"
|
|
|
|
main() {
|
|
opt_specs+=(': --inside : : internal-use only')
|
|
opt_visit:inside() { argparse:setmode inside; }
|
|
opt_final:inside() { :; }
|
|
|
|
opt_specs+=(': --user : : mount the image RO, run COMMAND as SUDO_USER')
|
|
opt_specs+=(': --root : : mount the image RW, run COMMAND as root, protect the rest of the system')
|
|
local arg_user=
|
|
opt_visit:user() { set_arg_user 'user'; }
|
|
opt_visit:root() { set_arg_user 'root'; }
|
|
set_arg_user() {
|
|
if [[ -n $arg_user ]]; then
|
|
argparse:error "Multiple --user/--root flags given"
|
|
fi
|
|
arg_user=$1
|
|
}
|
|
opt_final:user() { :; }
|
|
opt_final:root() {
|
|
if [[ -z $arg_user ]]; then
|
|
argparse:error "Must specify either --root or --user"
|
|
fi
|
|
}
|
|
|
|
opt_specs+=(': --rwdir : DIR : keep DIR read+write, even when --root')
|
|
local arg_rwdirs=()
|
|
opt_visit:rwdir() { arg_rwdirs+=("$1"); }
|
|
opt_final:rwdir() { :; }
|
|
|
|
opt_specs+=('-o : --options : LIST : comma-separated list of mount options')
|
|
opt_visit:options() { arg_mountopt="$1"; }
|
|
opt_final:options() { :; }
|
|
|
|
local arg_device=
|
|
local arg_mountopt=
|
|
local arg_cmd=()
|
|
opt_positional() {
|
|
case "$#" in
|
|
0) osi.sh:error 0 "Must specify an image, mountpoint, and a command"; return;;
|
|
1) osi.sh:error 0 "Must specify a mountpoint and a command"; return;;
|
|
2) osi.sh:error 0 "Must specify a command"; return;;
|
|
esac
|
|
arg_device=$(realpath -- "$1")
|
|
arg_mountpoint=$2
|
|
arg_cmd=("${@:3}")
|
|
}
|
|
|
|
local arg_orig=("$@")
|
|
argparse outside "$@"
|
|
|
|
case "$opt_mode" in
|
|
error)
|
|
osi.sh:print "Try '%q --help' for more information" "${0##*/}" >&2
|
|
return $EXIT_INVALIDARGUMENT
|
|
;;
|
|
version)
|
|
osi.sh:print "%s (osi-tools) %s" "$NAME" "$VERSION"
|
|
return $EXIT_SUCCESS
|
|
;;
|
|
help)
|
|
osi.sh:print 'Usage: sudo %s [OPTIONS] --<user|root> FILENAME.img MOUNTPOINT COMMAND...' "${0##*/}"
|
|
osi.sh:print 'Operating System Image: Mount'
|
|
echo
|
|
# 000000000011111111112222222222333333333344444444445555555555666666666677777777778
|
|
# 012345678901234567890123456789012345678901234567890123456789012345678901234567890
|
|
osi.sh:print 'Run COMMAND with FILENAME.img mounted at MOUNTPOINT.'
|
|
echo
|
|
osi.sh:print 'OPTIONS:'
|
|
echo "${opt_flaghelp}" | grep -v -e --inside
|
|
return $EXIT_SUCCESS
|
|
;;
|
|
|
|
outside)
|
|
osi.sh:needs_root
|
|
osi.sh:needs_sudo
|
|
if out="$(losetup --associated "$arg_device")" && [[ -n $out ]]; then
|
|
osi.sh:error 0 "Seems to already be mounted somewhere: %s" "$arg_device"
|
|
printf '%s\n' "$out"
|
|
exit $EXIT_FAILURE
|
|
fi
|
|
|
|
# I feel guilty pulling in the hugeness of systemd-run
|
|
# just for `--property=ProtectSystem=strict`, but I
|
|
# don't want to implement my own protection system.
|
|
flags=(
|
|
--pty --same-dir --wait --collect --quiet
|
|
--setenv=SUDO_UID="$SUDO_UID"
|
|
${SOURCE_DATE_EPOCH:+"--setenv=SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"}
|
|
${TMPDIR:+"--setenv=TMPDIR=$TMPDIR"}
|
|
--property=PrivateMounts=true
|
|
)
|
|
case "$arg_user" in
|
|
user) :;;
|
|
root)
|
|
flags+=(
|
|
--property=ProtectSystem=strict
|
|
--property=ProtectHome=read-only
|
|
--property=ReadWritePaths="$arg_device"
|
|
"${arg_rwdirs[@]/#/--property=ReadWritePaths=}"
|
|
)
|
|
;;
|
|
esac
|
|
systemd-run "${flags[@]}" -- "${BASH_SOURCE[0]}" --inside "${arg_orig[@]}"
|
|
|
|
if out="$(losetup --associated "$arg_device")" && [[ -n $out ]]; then
|
|
osi.sh:error 0 "umount'ed, but file is still associated with a loop device: %s" "$arg_device"
|
|
printf '%s\n' "$out"
|
|
exit $EXIT_FAILURE
|
|
fi
|
|
;;
|
|
inside)
|
|
osi.sh:needs_root
|
|
osi.sh:needs_sudo
|
|
|
|
case "$arg_user" in
|
|
user) arg_mountopt+="${arg_mountopt:+,}ro";;
|
|
root) arg_mountopt+="${arg_mountopt:+,}rw,discard";;
|
|
esac
|
|
|
|
cmd=(mount --no-mtab -o "$arg_mountopt" -- "$arg_device" "$arg_mountpoint")
|
|
r=0
|
|
out=$("${cmd[@]}" 2>&1) || r=$?
|
|
if (( r != 0 )); then
|
|
osi.sh:error $r '%s: %s' "${cmd[*]@Q}" "$out"
|
|
fi
|
|
|
|
case "$arg_user" in
|
|
user) osi.sh:sudo -u "#${SUDO_UID}" -- "${arg_cmd[@]}";;
|
|
root) command -- "${arg_cmd[@]}";;
|
|
esac
|
|
;;
|
|
|
|
*) osi.sh:bug 'unknown opt_mode: %s' "$opt_mode";;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|