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

Implement Multi-Lower-Layer Overlay Support through Merging #122

Merged
merged 21 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
16 changes: 16 additions & 0 deletions README.md
ezrizhu marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ $ try commit rustup-sandbox
You can also run `try explore` to open your current shell in try, or `/try
explore /tmp/tmp.X6OQb5tJwr` to explore an existing sandbox.

To specify multiple lower directories for overlay (by merging them together), you can use the `-L` (implies `-n`) flag followed by a colon-separated list of directories. The directories on the left have higher precedence and can overwrite the directories on the right:

```ShellSession
$ try -D /tmp/sandbox1 "echo 'File 1 Contents - sandbox1' > file1.txt"
$ try -D /tmp/sandbox2 "echo 'File 2 Contents - sandbox2' > file2.txt"
$ try -D /tmp/sandbox3 "echo 'File 2 Contents - sandbox3' > file2.txt"

# Now use the -L flag to merge both sandbox directories together, with sandbox3 having precedence over sandbox2
$ try -L "/tmp/sandbox3:/tmp/sandbox2:/tmp/sandbox1" "cat file1.txt file2.txt"
File 1 Contents - sandbox1
File 2 Contents - sandbox3
```

In this example, `try` will merge `/sandbox1`, `/sandbox2` and `/sandbox3` together before mounting the overlay. This way, you can combine the contents of multiple `try` sandboxes.


## Known Issues
Any command that interacts with other users/groups will fail since only the
current user's UID/GID are mapped. However, the [future
Expand Down
5 changes: 4 additions & 1 deletion completions/try.bash
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ _try() {

case "${cmd}" in
(try)
opts="-n -y -v -h -x -i -D -U summary commit explore"
opts="-n -y -v -h -x -i -D -U -L summary commit explore"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]]
then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
(-L)
COMPREPLY=($(compgen -d "${cur}"))
return 0;;
(-D)
COMPREPLY=($(compgen -d "${cur}"))
return 0;;
Expand Down
13 changes: 12 additions & 1 deletion docs/try.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
try - run a command in an overlay

# SYNOPSIS
| try [-ny] [-i PATTERN] [-D DIR] [-U PATH] CMD [ARG ...]
| try [-ny] [-i PATTERN] [-D DIR] [-U PATH] [-L LOWER_DIRS] CMD [ARG ...]
| try summary [DIR]
| try commit [DIR]
| try explore
Expand Down Expand Up @@ -57,6 +57,11 @@ While using *try* you can choose to commit the result to the filesystem or compl
: 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.

-L *LOWER_DIRS*

: Specify a colon-separated list of directories to be used as lower directories for the overlay, formatted as "dir1:dir2:...:dirn" (implies -n).


## Subcommands

try summary *DIR*
Expand Down Expand Up @@ -128,6 +133,12 @@ Alternatively, you can specify your own overlay directory as follows (note that
try -D try_dir gunzip file.txt.gz
```

To use multiple lower directories for overlay (by merging them), you can use the `-L` flag followed by a colon-separated list of directories. The directories on the left have higher precedence and can overwrite the directories on the right:

```
try -L /lowerdir1:/lowerdir2:/lowerdir3 gunzip file.txt.gz
```

You can inspect the changes made inside a given overlay directory:

```
Expand Down
71 changes: 71 additions & 0 deletions test/merge_multiple_dirs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/bin/sh

TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree)}"
TRY="$TRY_TOP/try"

cleanup() {
cd /

if [ -d "$try_workspace" ]
then
rm -rf "$try_workspace" >/dev/null 2>&1
fi

if [ -f "$try_example_dir1" ]
then
rm "$try_example_dir1"
fi

if [ -f "$try_example_dir2" ]
then
rm "$try_example_dir2"
fi

if [ -f "$try_example_dir3" ]
then
rm "$try_example_dir3"
fi

if [ -f "$expected1" ]
then
rm "$expected1"
fi

if [ -f "$expected2" ]
then
rm "$expected2"
fi
if [ -f "$expected3" ]
then
rm "$expected3"
fi
}

trap 'cleanup' EXIT

try_workspace="$(mktemp -d -p .)"
cp "$TRY_TOP/test/resources/file.txt.gz" "$try_workspace/"
cd "$try_workspace" || return 1

try_example_dir1="$(mktemp -d)"
try_example_dir2="$(mktemp -d)"
try_example_dir3="$(mktemp -d)"

expected1="$(mktemp)"
expected2="$(mktemp)"
expected3="$(mktemp)"

touch "$expected1"
echo "test2" > "$expected2"
echo "test3" > "$expected3"

"$TRY" -D "$try_example_dir1" "touch file_1.txt; echo test > file_2.txt; rm file.txt.gz" || return 2
"$TRY" -D "$try_example_dir2" "echo test2 > file_2.txt" || return 3
"$TRY" -D "$try_example_dir3" "echo test3 > file_3.txt" || return 4
"$TRY" -L "$try_example_dir3:$try_example_dir2:$try_example_dir1" -y "cat file_1.txt > out1; cat file_2.txt > out2; cat file_3.txt > out3"|| return 5

diff -q "$expected1" out1 || return 6
diff -q "$expected2" out2 || return 7
diff -q "$expected3" out3 || return 8

! [ -f out4 ] || return 9
74 changes: 53 additions & 21 deletions try
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ try() {
fi

## Make any directories that don't already exist, this is OK to do here
## because we have already checked if it valid.
## because we have already checked if it valid.
export SANDBOX_DIR
mkdir -p "$SANDBOX_DIR/upperdir" "$SANDBOX_DIR/workdir" "$SANDBOX_DIR/temproot"

Expand All @@ -56,6 +56,34 @@ try() {
findmnt --real -r -o target -n >>"$DIRS_AND_MOUNTS"
sort -u -o "$DIRS_AND_MOUNTS" "$DIRS_AND_MOUNTS"

# Calculate UPDATED_DIRS_AND_MOUNTS that contains the merge arguments in LOWER_DIRS
UPDATED_DIRS_AND_MOUNTS="$(mktemp)"
export UPDATED_DIRS_AND_MOUNTS
while IFS="" read -r mountpoint
do
new_mountpoint=""
OLDIFS=$IFS
IFS=":"
gliargovas marked this conversation as resolved.
Show resolved Hide resolved

for lower_dir in $LOWER_DIRS
do
temp_mountpoint="$lower_dir/upperdir$mountpoint"
if [ -n "$new_mountpoint" ]
then
# If new_mountpoint is not empty, append : and the temp_mountpoint
new_mountpoint="$new_mountpoint:$temp_mountpoint"
else
# If new_mountpoint is empty, just set it to temp_mountpoint
new_mountpoint="$temp_mountpoint"
fi
done
IFS=$OLDIFS
# Add the original mountpoint at the end
new_mountpoint="${new_mountpoint:+$new_mountpoint:}$mountpoint"
echo "$new_mountpoint" >> "$UPDATED_DIRS_AND_MOUNTS"
done <"$DIRS_AND_MOUNTS"


# 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
#
Expand Down Expand Up @@ -94,11 +122,12 @@ TRY_COMMAND="$TRY_COMMAND($0)"
## 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 userxattr -o "lowerdir=$lowerdir,upperdir=$sandbox_dir/upperdir/$mountpoint,workdir=$sandbox_dir/workdir/$mountpoint" "$sandbox_dir/temproot/$mountpoint"
lowerdirs="$2"
overlay_mountpoint="$3"
mount -t overlay overlay -o userxattr -o "lowerdir=$lowerdirs,upperdir=$sandbox_dir/upperdir/$overlay_mountpoint,workdir=$sandbox_dir/workdir/$overlay_mountpoint" "$sandbox_dir/temproot/$overlay_mountpoint"
}


devices_to_mount="tty null zero full random urandom"

## Mounts and unmounts a few select devices instead of the whole `/dev`
Expand Down Expand Up @@ -138,24 +167,23 @@ then
fi

# actually mount the overlays
for mountpoint in $(cat "$DIRS_AND_MOUNTS")
for mountpoint in $(cat "$UPDATED_DIRS_AND_MOUNTS")
do
pure_mountpoint=${mountpoint##*:}
gliargovas marked this conversation as resolved.
Show resolved Hide resolved

## We are not interested in mounts that are not directories
if ! [ -d "$mountpoint" ]
if ! [ -d "$pure_mountpoint" ]
then
continue
fi

## 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
## Don't do anything for the root and skip if it is /dev or /proc, we will mount it later
case "$pure_mountpoint" in
(/|/dev|/proc) continue;;
esac

# Try mounting everything normally
make_overlay "$SANDBOX_DIR" "/$mountpoint" "$mountpoint" 2>>"$try_mount_log"
make_overlay "$SANDBOX_DIR" "$mountpoint" "$pure_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
Expand All @@ -180,9 +208,7 @@ do
## Create a union directory
"$UNION_HELPER" $mountpoint $merger_dir 2>>"$try_mount_log" ||
printf "%s: Warning: Failed mounting $mountpoint via $UNION_HELPER, see \"$try_mount_log\"\n" "$TRY_COMMAND" >&2

## Make the overlay on the union directory which works as a lowerdir for overlay
make_overlay "$SANDBOX_DIR" "$merger_dir" "$mountpoint" 2>>"$try_mount_log" ||
make_overlay "$SANDBOX_DIR" "$merger_dir" "$pure_mountpoint" 2>>"$try_mount_log" ||
printf "%s: Warning: Failed mounting $mountpoint as an overlay via $UNION_HELPER, see \"$try_mount_log\"\n" "$TRY_COMMAND" >&2
fi
fi
Expand Down Expand Up @@ -477,19 +503,18 @@ error() {

usage() {
cat >&2 <<EOF
Usage: $TRY_COMMAND [-nvhyx] [-i PATTERN] [-D DIR] [-U PATH] CMD [ARG ...]
Usage: $TRY_COMMAND [-nvhyx] [-i PATTERN] [-D DIR] [-U PATH] [-L dir1:dir2:...] CMD [ARG ...]

-n don't commit or prompt for commit (overrides -y)
ezrizhu marked this conversation as resolved.
Show resolved Hide resolved
-y assume yes to all prompts (overrides -n)
-x prevent network access (by unsharing the network namespace)
-i PATTERN ignore paths that match PATTERN on summary and commit
-D DIR work in DIR (implies -n)
-U PATH path to unionfs helper (e.g., mergerfs, unionfs-fuse)

-L dir1:dir2:... specify multiple lower directories to merge (colon-separated, implies -n)
-v show version information (and exit)
-h show this usage message (and exit)


Subcommands:
$TRY_COMMAND summary DIR show the summary for the overlay in DIR
$TRY_COMMAND commit DIR commit the overlay in DIR
Expand All @@ -510,7 +535,8 @@ NO_COMMIT="interactive"
# Includes all patterns given using the `-i` flag; will be used with `grep -f`
IGNORE_FILE="$(mktemp)"

while getopts ":yvnhxi:D:U:" opt
while getopts ":yvnhxi:D:U:L:" opt

do
case "$opt" in
(y) NO_COMMIT="commit";;
gliargovas marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -522,6 +548,12 @@ do
fi
SANDBOX_DIR="$OPTARG"
NO_COMMIT="quiet";;
(L) if [ -n "$LOWER_DIRS" ]
then
error "the -L option has been specified multiple times" 2
fi
LOWER_DIRS="$OPTARG"
NO_COMMIT="quiet";;
(v) echo "$TRY_COMMAND version $TRY_VERSION" >&2
exit 0;;
(U) if ! [ -x "$OPTARG" ]
Expand Down
Loading