@@ -22,6 +22,12 @@ TRY_VERSION="0.1.0"
22
22
try () {
23
23
START_DIR=" $PWD "
24
24
25
+ if ! command -v findmnt > /dev/null
26
+ then
27
+ printf " %s: findmnt not found, please install util-linux\n" " $( basename $0 ) " >&2
28
+ exit 1
29
+ fi
30
+
25
31
if [ " $SANDBOX_DIR " ]
26
32
then
27
33
# # If the name of a sandbox is given then we need to exit prematurely if its directory doesn't exist
@@ -45,11 +51,21 @@ try() {
45
51
46
52
# we will overlay-mount each root directory separately (instead of all at once) because some directories cannot be overlayed
47
53
# so we set up the mount points now
48
- for top_dir in /*
54
+ #
55
+ # KK 2023-06-29 This approach (of mounting each root directory separately) was necessary because we could not mount `/` in an overlay.
56
+ # However, this might be solveable using mergerfs/unionfs, allowing us to mount an overlay on a unionfs of the `/` once.
57
+ #
58
+ # findmnt
59
+ # --real: only list real filesystems
60
+ # -n: no header
61
+ # -r: raw output
62
+ # -o target: only print the mount target
63
+ # then we want to exclude the root partition "/"
64
+ for mountpoint in /* $( findmnt --real -r -o target -n | grep -v " ^/$" )
49
65
do
50
66
# # Only make the directory if the original is a directory too
51
- if [ -d " $top_dir " ]; then
52
- mkdir -p " $SANDBOX_DIR " /upperdir/" $top_dir " " $SANDBOX_DIR " /workdir" /$top_dir " " $SANDBOX_DIR " /temproot/" $top_dir "
67
+ if [ -d " $mountpoint " ]; then
68
+ mkdir -p " $SANDBOX_DIR " /upperdir/" $mountpoint " " $SANDBOX_DIR " /workdir" /$mountpoint " " $SANDBOX_DIR " /temproot/" $mountpoint "
53
69
fi
54
70
done
55
71
@@ -60,48 +76,123 @@ try() {
60
76
cat > " $mount_and_execute " << "EOF "
61
77
#!/bin/sh
62
78
79
+ ## A wrapper of `mount -t overlay` to have cleaner looking code
80
+ make_overlay() {
81
+ sandbox_dir="$1"
82
+ lowerdir="$2"
83
+ mountpoint="$3"
84
+ mount -t overlay overlay -o "lowerdir=$lowerdir,upperdir=$sandbox_dir/upperdir/$mountpoint,workdir=$sandbox_dir/workdir/$mountpoint" "$sandbox_dir/temproot/$mountpoint"
85
+ }
86
+
87
+ devices_to_mount="tty null zero full random urandom"
88
+
89
+ ## Mounts and unmounts a few select devices instead of the whole `/dev`
90
+ mount_devices() {
91
+ sandbox_dir="$1"
92
+ for dev in $devices_to_mount
93
+ do
94
+ touch "$sandbox_dir/temproot/dev/$dev"
95
+ mount -o bind /dev/$dev "$sandbox_dir/temproot/dev/$dev"
96
+ done
97
+ }
98
+
99
+ unmount_devices() {
100
+ sandbox_dir="$1"
101
+ for dev in $devices_to_mount
102
+ do
103
+ umount "$sandbox_dir/temproot/dev/$dev" 2>>"$try_mount_log"
104
+ rm -f "$sandbox_dir/temproot/dev/$dev"
105
+ done
106
+ }
107
+
108
+ ## Try to autodetect union helper: {mergerfs | unionfs}
109
+ ## Returns an empty string if no union helper is found
110
+ autodetect_union_helper() {
111
+ if command -v mergerfs > /dev/null; then
112
+ echo mergerfs
113
+ elif command -v unionfs > /dev/null; then
114
+ echo unionfs
115
+ fi
116
+ }
117
+
63
118
# actually mount the overlays
64
- for top_dir in /*
119
+ for mountpoint in /* $(findmnt --real -r -o target -n)
65
120
do
66
- ## If the directory is not a mountpoint
67
- if [ -d "$top_dir" ] && ! mountpoint -q "$top_dir"; then
68
- ## TODO: The
69
- 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
121
+ ## We are not interested in mounts that are not directories
122
+ if [ ! -d "$mountpoint" ]
123
+ then
124
+ continue
70
125
fi
71
- done
72
126
73
- # Now we will handle custom mounts, e.g., mounts on /home
74
- # findmnt
75
- # --real: only list real filesystems
76
- # -n: no header
77
- # -r: raw output
78
- # -o target: only print the mount target
79
- # then we want to exclude the root partition "/"
80
- for mount_dir in $(findmnt --real -r -o target -n | grep -v "^/$")
81
- do
82
- 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
127
+ ## Don't do anything for the root
128
+ ## and skip if it is /dev or /proc, we will mount it later
129
+ if [ "$mountpoint" = "/" ] ||
130
+ [ "$mountpoint" = "/dev" ] || [ "$mountpoint" = "/proc" ]
131
+ then
132
+ continue
133
+ fi
134
+
135
+ # Try mounting everything normally
136
+ make_overlay "$SANDBOX_DIR" "/$mountpoint" "$mountpoint" 2>> "$try_mount_log"
137
+ # If mounting everything normally fails, we try using either using mergerfs or unionfs to mount them.
138
+ if [ "$?" -ne 0 ]
139
+ then
140
+ # Detect if union_helper is set, if not, we try to autodetect them
141
+ if [ -z ${union_helper+x} ]
142
+ then
143
+ union_helper="$(autodetect_union_helper)"
144
+ if [ -z "$union_helper" ]
145
+ then
146
+ printf "%s: Failed to mount overlayfs normally, mergerfs or unionfs not found for $mountpoint, see $try_mount_log\n" "$(basename $0)" >&2
147
+ exit 1
148
+ fi
149
+ fi
150
+
151
+
152
+ ## If the overlay failed, it means that there is a nested mount inside the target mount, e.g., both `/home` and `/home/user/mnt` are mounts.
153
+ ## To address this, we use unionfs/mergerfs (they support the same functionality) to show all mounts under the target mount as normal directories.
154
+ ## Then we can normally make the overlay on the new union directory.
155
+ ##
156
+ ## KK 2023-06-29 Since this uses findmnt, it performs the union+overlay for both the outside and the inside mount.
157
+ ## In the best case scenario this is only causing extra work (the internal mount is already shown through the unionfs),
158
+ ## but in the worst case this could lead to bugs due to the extra complexity (e.g., because we are doing mounts on top of each other).
159
+ ## We should try to investigate either:
160
+ ## 1. Not doing another overlay if we have done it for a parent directory (we can keep around a list of overlays and skip if we are in a child)
161
+ ## 2. Do one unionfs+overlay at the root `/` once and be done with it!
162
+ merger_dir=$(mktemp -d)
163
+
164
+ ## Create a union directory
165
+ "$union_helper" $mountpoint $merger_dir 2>> "$try_mount_log" ||
166
+ printf "%s: Warning: Failed to mount $mountpoint via $union_helper, see \"$try_mount_log\"\n" "$(basename $0)" >&2
167
+
168
+ ## Make the overlay on the union directory which works as a lowerdir for overlay
169
+ make_overlay "$SANDBOX_DIR" "$merger_dir" "$mountpoint" 2>> "$try_mount_log" ||
170
+ printf "%s: Warning: Failed mounting $mountpoint as an overlay via $union_helper, see \"$try_mount_log\"\n" "$(basename $0)" >&2
171
+ fi
83
172
done
84
173
85
- ## Bind the udev mount so that the containerized process has access to /dev
86
- ## KK 2023-05-06 Are there any security/safety implications by binding the whole /dev?
87
- ## Maybe we just want to bind a few files in it like /dev/null, /dev/zero?
88
- mount --rbind /dev "$SANDBOX_DIR/temproot/dev"
89
- ## KK 2023-06-20 Redirecting to /dev/null to suppress a yet uninvestigated but
90
- ## seemingly not impactful warning.
91
- mount --rbind --read-only /run "$SANDBOX_DIR/temproot/run" 2> /dev/null
174
+ ## Mount a few select devices in /dev
175
+ mount_devices "$SANDBOX_DIR"
92
176
93
177
## Check if chroot_executable exists, #29
94
- if ! [ -f "$SANDBOX_DIR/temproot/$chroot_executable" ]; then
178
+ if ! [ -f "$SANDBOX_DIR/temproot/$chroot_executable" ]
179
+ then
95
180
cp $chroot_executable "$SANDBOX_DIR/temproot/$chroot_executable"
96
181
fi
97
182
98
-
99
183
unshare --root="$SANDBOX_DIR/temproot" /bin/bash "$chroot_executable"
184
+ exitcode="$?"
185
+
186
+ # unmount the devices
187
+ sync
188
+ unmount_devices "$SANDBOX_DIR"
189
+
190
+ exit $exitcode
100
191
EOF
101
192
102
193
cat > " $chroot_executable " << EOF
103
194
#!/bin/sh
104
-
195
+
105
196
mount -t proc proc /proc &&
106
197
cd $START_DIR &&
107
198
source "$script_to_execute "
@@ -165,7 +256,7 @@ summary() {
165
256
changed_files=$( find_upperdir_changes " $SANDBOX_DIR " )
166
257
summary_output=$( process_changes " $SANDBOX_DIR " " $changed_files " )
167
258
168
- if [ -z " $summary_output " ];
259
+ if [ -z " $summary_output " ]
169
260
then
170
261
return 1
171
262
fi
@@ -248,6 +339,7 @@ find_upperdir_changes() {
248
339
find " $sandbox_dir /upperdir/" -type f -o \( -type c -size 0 \) -o -type d | ignore_changes
249
340
}
250
341
342
+
251
343
# # Processes upperdir changes to an internal format that can be processed by summary and commit
252
344
# # Format:
253
345
# # XX PATH
@@ -323,11 +415,12 @@ sandbox_valid_or_empty() {
323
415
usage () {
324
416
cmd=" $( basename $0 ) "
325
417
cat >&2 << EOF
326
- Usage: $cmd [-nvhy] [-D DIR] CMD [ARG ...]
418
+ Usage: $cmd [-nvhy] [-D DIR] [-U PATH] CMD [ARG ...]
327
419
328
420
-n don't prompt for commit
329
421
-y assume yes to all prompts (implies -n is not used)
330
422
-D DIR work in DIR (implies -n)
423
+ -U PATH path to unionfs helper (e.g., mergerfs, unionfs-fuse)
331
424
332
425
-v show version information (and exit)
333
426
-h show this usage message (and exit)
346
439
# "commit" - commit the result directory automatically when we're done
347
440
NO_COMMIT=" interactive"
348
441
349
- while getopts " :yvnD:" opt
442
+ while getopts " :yvnD:U: " opt
350
443
do
351
444
case " $opt " in
352
445
(y) NO_COMMIT=" commit" ;;
360
453
NO_COMMIT=" quiet"
361
454
;;
362
455
(v) printf " %s version $TRY_VERSION \n" " $( basename $0 ) " >&2 ; exit 0;;
456
+ (U) if ! [ -x " $OPTARG " ]
457
+ then
458
+ printf " %s: no such executable $OPTARG \n" " $( basename $0 ) " >&2
459
+ exit 2
460
+ fi
461
+ union_helper=" $OPTARG "
462
+ ;;
363
463
(h|* ) usage; exit 0;;
364
464
esac
365
465
done
0 commit comments