Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested mount support #67

Merged
merged 30 commits into from
Jun 29, 2023
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
cab88e2
Allow for nested mount by using mergerfs
Jun 25, 2023
4e2a113
Remove debug bash
ezrizhu Jun 25, 2023
73b9b51
Remove debug echo
ezrizhu Jun 25, 2023
092a460
mount /run with merger aswell
ezrizhu Jun 25, 2023
b95f2e4
refactor and use overlayfs via mergerfs if regular overlayfs fails
ezrizhu Jun 26, 2023
1a4ba00
Only mount /dev/{tty null zero full random urandom}
ezrizhu Jun 26, 2023
83f8e5d
improve docs, refactor from top_dir to mountpoint
ezrizhu Jun 26, 2023
6b46967
Fix mergerfs failing not showing mount log path
ezrizhu Jun 27, 2023
9c929fe
Add support for unionfs, allow user to specify unionfs helper path
ezrizhu Jun 27, 2023
74d0894
Write mountpoint on unionhelper not found message
ezrizhu Jun 27, 2023
db5539f
Merge branch 'main' into nested-mount
ezrizhu Jun 27, 2023
f2cb092
exit if findmnt not installed
ezrizhu Jun 27, 2023
4f3b766
nested mount docs
ezrizhu Jun 27, 2023
ed98863
add newlines to readme
ezrizhu Jun 27, 2023
a8427e2
grammar fix
ezrizhu Jun 27, 2023
38a34b5
Add -U option description to manpages
gliargovas Jun 28, 2023
1a29602
Add shell completion for -U option
gliargovas Jun 28, 2023
29d47f3
Change -U flag autocompletion to only suggest executables
gliargovas Jun 28, 2023
c216d0e
Install mergerfs in ci
ezrizhu Jun 28, 2023
68e5965
Try reading from /run directory before testing
gliargovas Jun 29, 2023
e69c535
Merge remote-tracking branch 'origin/main' into nested-mount
angelhof Jun 29, 2023
7d661e9
refactor
angelhof Jun 29, 2023
45f8e0b
Refactor
angelhof Jun 29, 2023
a71f715
more refactoring
angelhof Jun 29, 2023
2f5d40f
Refactor some more and unmount devices for tests to pass
angelhof Jun 29, 2023
7de1645
Add a device test
angelhof Jun 29, 2023
2fcfb17
copyedit
mgree Jun 29, 2023
57b9b8c
copyedit
mgree Jun 29, 2023
36a7b6c
Some comments and redirect a test to /dev/null
angelhof Jun 29, 2023
77f60f7
Merge branch 'nested-mount' of github.com:binpash/try into nested-mount
angelhof Jun 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
run: |
uname -a
sudo apt-get update
sudo apt-get install strace expect
sudo apt-get install expect mergerfs

- name: Checkout
uses: actions/checkout@v2
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ disks.

### Dependencies

Try relies on the following dependencies

`util-linux`

In cases where overlayfs doesn't work on nested mounts, you will need either
[mergerfs](https://github.com/trapexit/mergerfs) or [unionfs](https://github.com/rpodgorny/unionfs-fuse). Try should be able to autodetect them, but you can specify the path to mergerfs or unionfs with -U (e.g. `try -U ~/.local/bin/unionfs`)

Has been tested on the following distributions:
* `Ubuntu 20.04 LTS` or later
* `Debian 12`
Expand Down
7 changes: 5 additions & 2 deletions completions/try.bash
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ _try() {

case "${cmd}" in
try)
opts="-n -y -v -h -D summary commit explore"
opts="-n -y -v -h -D -U summary commit explore"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
Expand All @@ -31,7 +31,10 @@ _try() {
COMPREPLY=($(compgen -d "${cur}"))
return 0
;;

-U)
COMPREPLY=($(compgen -c "${cur}"))
return 0
;;
commit)
COMPREPLY=($(compgen -d "${cur}"))
return 0
Expand Down
6 changes: 5 additions & 1 deletion docs/try.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,13 @@ While using *try* you can choose to commit the result to the filesystem or compl

: Specifies DIR as the overlay directory (implies -n). The use of -D also implies that *DIR* already exists.

-U *PATH*

: Use the unionfs helper implementation defined in the *PATH* (e.g., mergerfs, unionfs-fuse) instead of the default.
This option is recommended in case OverlayFS fails.
## Subcommands

try summary *DIR*
try summary *DIR*

: Show the summary for the overlay in DIR

Expand Down
14 changes: 13 additions & 1 deletion test/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,27 @@ cleanup()
mkdir "$try_workspace"
}

test_read_from_run_dir()
{
ls /run/systemd > /dev/null
if [ $? -ne 0 ]; then
echo "Cannot read from /run/systemd."
return 1
fi
}

run_test()
{
cleanup
local test=$1

if [ "$(type -t $test)" != "function" ]; then
echo "$test is not a function! FAIL"
return 1
fi

# Check if we can read from /run dir
test_read_from_run_dir

echo -n "Running $test..."

Expand Down
148 changes: 116 additions & 32 deletions try
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ TRY_VERSION="0.1.0"
try() {
START_DIR="$PWD"

if ! command -v findmnt > /dev/null
then
printf "%s: findmnt not found, please install util-linux\n" "$(basename $0)" >&2
exit 1
fi

if [ "$SANDBOX_DIR" ]
then
## If the name of a sandbox is given then we need to exit prematurely if its directory doesn't exist
Expand All @@ -45,11 +51,18 @@ try() {

# we will overlay-mount each root directory separately (instead of all at once) because some directories cannot be overlayed
# so we set up the mount points now
for top_dir in /*
#
# findmnt
# --real: only list real filesystems
# -n: no header
# -r: raw output
# -o target: only print the mount target
# then we want to exclude the root partition "/"
for mountpoint in /* $(findmnt --real -r -o target -n | grep -v "^/$")
do
## Only make the directory if the original is a directory too
if [ -d "$top_dir" ]; then
mkdir -p "$SANDBOX_DIR"/upperdir/"$top_dir" "$SANDBOX_DIR"/workdir"/$top_dir" "$SANDBOX_DIR"/temproot/"$top_dir"
if [ -d "$mountpoint" ]; then
mkdir -p "$SANDBOX_DIR"/upperdir/"$mountpoint" "$SANDBOX_DIR"/workdir"/$mountpoint" "$SANDBOX_DIR"/temproot/"$mountpoint"
fi
done

Expand All @@ -60,48 +73,110 @@ try() {
cat >"$mount_and_execute" <<"EOF"
#!/bin/sh

## A wrapper of `mount -t overlay` to have cleaner looking code
make_overlay() {
sandbox_dir="$1"
lowerdir="$2"
mountpoint="$3"
mount -t overlay overlay -o "lowerdir=$lowerdir,upperdir=$sandbox_dir/upperdir/$mountpoint,workdir=$sandbox_dir/workdir/$mountpoint" "$sandbox_dir/temproot/$mountpoint"
}

devices_to_mount="tty null zero full random urandom"

## Mounts and unmounts a few select devices instead of the whole `/dev`
mount_devices() {
sandbox_dir="$1"
for dev in $devices_to_mount
do
touch "$sandbox_dir/temproot/dev/$dev"
mount -o bind /dev/$dev "$sandbox_dir/temproot/dev/$dev"
done
}

unmount_devices() {
sandbox_dir="$1"
for dev in $devices_to_mount
do
umount "$sandbox_dir/temproot/dev/$dev" 2>>"$try_mount_log"
rm -f "$sandbox_dir/temproot/dev/$dev"
done
}

## Try to autodetect union helper: {mergerfs | unionfs}
## Returns an empty string if no union helper is found
autodetect_union_helper() {
if command -v mergerfs > /dev/null; then
echo mergerfs
elif command -v unionfs > /dev/null; then
echo unionfs
fi
}

# actually mount the overlays
for top_dir in /*
for mountpoint in /* $(findmnt --real -r -o target -n)
do
## If the directory is not a mountpoint
if [ -d "$top_dir" ] && ! mountpoint -q "$top_dir"; then
## TODO: The
mount -t overlay overlay -o lowerdir=/"$top_dir",upperdir="$SANDBOX_DIR"/upperdir/"$top_dir",workdir="$SANDBOX_DIR"/workdir/"$top_dir" "$SANDBOX_DIR"/temproot/"$top_dir" 2>> "$try_mount_log" || echo "Warning: Failed mounting $top_dir as an overlay, see "$try_mount_log"" 1>&2
## We are not interested in mounts that are not directories
if [ ! -d "$mountpoint" ]
then
continue
fi
done

# Now we will handle custom mounts, e.g., mounts on /home
# findmnt
# --real: only list real filesystems
# -n: no header
# -r: raw output
# -o target: only print the mount target
# then we want to exclude the root partition "/"
for mount_dir in $(findmnt --real -r -o target -n | grep -v "^/$")
do
mount -t overlay overlay -o lowerdir="$mount_dir",upperdir="$SANDBOX_DIR"/upperdir"$mount_dir",workdir="$SANDBOX_DIR"/workdir"$mount_dir" "$SANDBOX_DIR"/temproot"$mount_dir" 2>> "$try_mount_log" || echo "Warning: Failed mounting $mount_dir as an overlay, see "$try_mount_log"" 1>&2
## Don't do anything for the root
## and skip if it is /dev or /proc, we will mount it later
if [ "$mountpoint" = "/" ] ||
[ "$mountpoint" = "/dev" ] || [ "$mountpoint" = "/proc" ]
then
continue
fi

# Try mounting everything normally
make_overlay "$SANDBOX_DIR" "/$mountpoint" "$mountpoint" 2>> "$try_mount_log"
# If mounting everything normally fails, we try using either using mergerfs or unionfs to mount them.
if [ "$?" -ne 0 ]
then
# Detect if union_helper is set, if not, we try to autodetect them
if [ -z ${union_helper+x} ]
then
union_helper="$(autodetect_union_helper)"
if [ -z "$union_helper" ]
then
printf "%s: Failed to mount overlayfs normally, mergerfs or unionfs not found for $mountpoint, see $try_mount_log\n" "$(basename $0)" >&2
exit 1
fi
fi

# Here we use mergerfs/unionfs to be mounted into a single file system. Used to fix nested mounts #19
merger_dir=$(mktemp -d)
"$union_helper" $mountpoint $merger_dir 2>> "$try_mount_log" ||
printf "%s: Warning: Failed to mount $mountpoint via $union_helper, see \"$try_mount_log\"\n" "$(basename $0)" >&2

make_overlay "$SANDBOX_DIR" "$merger_dir" "$mountpoint" 2>> "$try_mount_log" ||
printf "%s: Warning: Failed mounting $mountpoint as an overlay via $union_helper, see \"$try_mount_log\"\n" "$(basename $0)" >&2
fi
done

## Bind the udev mount so that the containerized process has access to /dev
## KK 2023-05-06 Are there any security/safety implications by binding the whole /dev?
## Maybe we just want to bind a few files in it like /dev/null, /dev/zero?
mount --rbind /dev "$SANDBOX_DIR/temproot/dev"
## KK 2023-06-20 Redirecting to /dev/null to suppress a yet uninvestigated but
## seemingly not impactful warning.
mount --rbind --read-only /run "$SANDBOX_DIR/temproot/run" 2> /dev/null
## Mount a few select devices in /dev
mount_devices "$SANDBOX_DIR"

## Check if chroot_executable exists, #29
if ! [ -f "$SANDBOX_DIR/temproot/$chroot_executable" ]; then
if ! [ -f "$SANDBOX_DIR/temproot/$chroot_executable" ]
then
cp $chroot_executable "$SANDBOX_DIR/temproot/$chroot_executable"
fi


unshare --root="$SANDBOX_DIR/temproot" /bin/bash "$chroot_executable"
exitcode="$?"

# unmount the devices
sync
unmount_devices "$SANDBOX_DIR"

exit $exitcode
EOF

cat >"$chroot_executable" <<EOF
#!/bin/sh

mount -t proc proc /proc &&
cd $START_DIR &&
source "$script_to_execute"
Expand Down Expand Up @@ -165,7 +240,7 @@ summary() {
changed_files=$(find_upperdir_changes "$SANDBOX_DIR")
summary_output=$(process_changes "$SANDBOX_DIR" "$changed_files")

if [ -z "$summary_output" ];
if [ -z "$summary_output" ]
then
return 1
fi
Expand Down Expand Up @@ -248,6 +323,7 @@ find_upperdir_changes() {
find "$sandbox_dir/upperdir/" -type f -o \( -type c -size 0 \) -o -type d | ignore_changes
}


## Processes upperdir changes to an internal format that can be processed by summary and commit
## Format:
## XX PATH
Expand Down Expand Up @@ -323,11 +399,12 @@ sandbox_valid_or_empty() {
usage() {
cmd="$(basename $0)"
cat >&2 <<EOF
Usage: $cmd [-nvhy] [-D DIR] CMD [ARG ...]
Usage: $cmd [-nvhy] [-D DIR] [-U PATH] CMD [ARG ...]

-n don't prompt for commit
-y assume yes to all prompts (implies -n is not used)
-D DIR work in DIR (implies -n)
-U PATH path to unionfs helper (e.g., mergerfs, unionfs-fuse)

-v show version information (and exit)
-h show this usage message (and exit)
Expand All @@ -346,7 +423,7 @@ EOF
# "commit" - commit the result directory automatically when we're done
NO_COMMIT="interactive"

while getopts ":yvnD:" opt
while getopts ":yvnD:U:" opt
do
case "$opt" in
(y) NO_COMMIT="commit";;
Expand All @@ -360,6 +437,13 @@ do
NO_COMMIT="quiet"
;;
(v) printf "%s version $TRY_VERSION\n" "$(basename $0)" >&2; exit 0;;
(U) if ! [ -x "$OPTARG" ]
then
printf "%s: no such executable $OPTARG\n" "$(basename $0)" >&2
exit 2
fi
union_helper="$OPTARG"
;;
(h|*) usage; exit 0;;
esac
done
Expand Down