From f47658d0804d4f2c05cfa54ac8886e0babec27f4 Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Fri, 5 Sep 2025 18:26:27 -0400 Subject: [PATCH 01/10] Resolved some dmesg errors (#104) --- try | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/try b/try index de6ef4a3..47755271 100755 --- a/try +++ b/try @@ -159,7 +159,7 @@ make_overlay() { sandbox_dir="$1" 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" + mount -t overlay overlay -o userxattr -o "lowerdir=$lowerdirs,upperdir=$sandbox_dir/upperdir/$overlay_mountpoint,workdir=$sandbox_dir/workdir/$overlay_mountpoint,index=off,xino=off" "$sandbox_dir/temproot/$overlay_mountpoint" } From bdc354ce52b998602836909a832330d26ef67f29 Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Thu, 6 Nov 2025 01:33:55 -0500 Subject: [PATCH 02/10] Added check for fstypes --- mount_notes.txt | 33 +++ temptry | 706 ++++++++++++++++++++++++++++++++++++++++++++ test/check_shell.sh | 5 +- test/print-shell.sh | 3 + try | 26 +- 5 files changed, 769 insertions(+), 4 deletions(-) create mode 100644 mount_notes.txt create mode 100644 temptry create mode 100755 test/print-shell.sh diff --git a/mount_notes.txt b/mount_notes.txt new file mode 100644 index 00000000..d2dd7f68 --- /dev/null +++ b/mount_notes.txt @@ -0,0 +1,33 @@ + + +Idea: + +1) Create partitions of various file systems +2) Mount the fs on / and check try to see if overlay failed on that specific file + +Results: +ext3: no errors +ext2: no errors +ext4: no errors (duh) +btrfs: no errors +bcachefs: no errors +exfat: failed +f2fs: no errors +fat16 (msdos): failed +fat32 (msdos): failed +hfs: failed +hfs+: failed +jfs: no errors +swap: failure to mount swap partition (no fault of overlayfs) +LVM2: failure to mount LVM2 partition (no fault of overlayfs) +minux: no errors +nilfs2: no errors +ntfs: no errors (however when doing stat -f it shows the fs type as "UNKNOWN" followed by an address) +resierfs: failure to mount resierfs partition (no fault of overlayfs) +udf: no errors +xtfs: no errors + +Solution: +Now that we know what fs types fail we can check before the overlay call and test if the dir is a type that fails +(maybe using stat -f). If it does lets just ignore the over call and go straight to the mergerfs call + diff --git a/temptry b/temptry new file mode 100644 index 00000000..940902f3 --- /dev/null +++ b/temptry @@ -0,0 +1,706 @@ +#!/bin/sh + +# Copyright (c) 2023 The PaSh Authors. +# +# Usage of this source code is governed by the MIT license, you can find the +# LICENSE file in the root directory of this project. +# +# https://github.com/binpash/try + +TRY_VERSION="0.2.0" +TRY_COMMAND="${0##*/}" +EXECID="$(date +%s%3N)" +export EXECID +export TRY_COMMAND + +# exit status invariants +# +# 0 -- command ran +# 1 -- consistency error/failure +# 2 -- input error + +################################################################################ +# Tries to detect a setting for TRY_SHELL +################################################################################ + +set_TRY_SHELL() { + # case: TRY_SHELL set to an executable + [ -x "$TRY_SHELL" ] && return + + # use SHELL (if it's not fish) + if [ -x "$SHELL" ] && [ "${SHELL##*/}" != "fish" ]; then + TRY_SHELL="$SHELL" + + else + login_shell=$(grep -e "^$LOGNAME" /etc/passwd | cut -d: -f7) + + # use login shell (if it's not fish) + if [ -x "$login_shell" ] && [ "${login_shell##*/}" != "fish" ]; then + TRY_SHELL="$login_shell" + else + TRY_SHELL="/bin/sh" + fi + fi + + export TRY_SHELL +} + + +################################################################################ +# Run a command (in `$@`) in an overlay (in `$SANDBOX_DIR`) +################################################################################ + +try() { + set_TRY_SHELL + START_DIR="$PWD" + + if [ "$SANDBOX_DIR" ] + then + ## If the name of a sandbox is given then we need to exit prematurely if its directory doesn't exist + [ -d "$SANDBOX_DIR" ] || error "could not find sandbox directory $SANDBOX_DIR" 2 + # Force absolute path + SANDBOX_DIR="$(cd "$SANDBOX_DIR" && pwd)" + + # shellcheck disable=SC2181 + [ "$?" -eq 0 ] || error "could not find sandbox directory $SANDBOX_DIR (could not cd in)" 2 + else + ## Create a new sandbox if one was not given + SANDBOX_DIR="$(mktemp -d --suffix ".try-$EXECID")" + fi + OLDPWD="a" + export "OLDPWD"="$OLDPWD" + + ## If the sandbox is not valid we exit early + if ! sandbox_valid_or_empty "$SANDBOX_DIR" + then + error "given sandbox '$SANDBOX_DIR' is invalid" 1 + fi + + ## Make any directories that don't already exist, this is OK to do here + ## because we have already checked if it valid. + export SANDBOX_DIR + + # We created "$IGNORE_FILE" up front, but now we can stash it in the sandbox. + mv "$IGNORE_FILE" "$SANDBOX_DIR"/ignore + IGNORE_FILE="$SANDBOX_DIR"/ignore + + try_mount_log="$SANDBOX_DIR"/mount.log + export try_mount_log + + # If we're in a docker container, we want to mount tmpfs on sandbox_dir, #136 + # tail -n +2 to ignore the first line with the column name + tmpfstype=$(df --output=fstype "$SANDBOX_DIR" | tail -n +2) + if [ "$tmpfstype" = "overlay" ] && [ "$(id -u)" -eq "0" ] + then + echo "mounting sandbox '$SANDBOX_DIR' as tmpfs (underlying fs is overlayfs)" >> "$try_mount_log" + echo "consider docker volumes if you want persistence" >> "$try_mount_log" + mount -t tmpfs tmpfs "$SANDBOX_DIR" + fi + + mkdir -p "$SANDBOX_DIR/upperdir" "$SANDBOX_DIR/workdir" "$SANDBOX_DIR/temproot" + + ## Find all the directories and mounts that need to be mounted + DIRS_AND_MOUNTS="$SANDBOX_DIR"/mounts + export DIRS_AND_MOUNTS + find / -maxdepth 1 >"$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="$SANDBOX_DIR"/mounts.updated + export UPDATED_DIRS_AND_MOUNTS + while IFS="" read -r mountpoint + do + new_mountpoint="" + OLDIFS=$IFS + IFS=":" + + for lower_dir in $LOWER_DIRS + do + temp_mountpoint="$lower_dir/upperdir$mountpoint" + # Make sure we put : between, but not at the beginning + new_mountpoint="${new_mountpoint:+$new_mountpoint:}$temp_mountpoint" + 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 + # + # KK 2023-06-29 This approach (of mounting each root directory separately) was necessary because we could not mount `/` in an overlay. + # However, this might be solvable using mergerfs/unionfs, allowing us to mount an overlay on a unionfs of the `/` once. + while IFS="" read -r mountpoint + do + ## Only make the directory if the original is a directory too + if [ -d "$mountpoint" ] && ! [ -L "$mountpoint" ] + then + # shellcheck disable=SC2174 # warning acknowledged, "When used with -p, -m only applies to the deepest directory." + mkdir -m "$(stat -c %a "$mountpoint")" -p "${SANDBOX_DIR}/upperdir/${mountpoint}" "${SANDBOX_DIR}/workdir/${mountpoint}" "${SANDBOX_DIR}/temproot/${mountpoint}" + fi + done <"$DIRS_AND_MOUNTS" + + chmod "$(stat -c %a /)" "$SANDBOX_DIR/temproot" + + mount_and_execute="$SANDBOX_DIR"/mount_and_execute.sh + chroot_executable="$SANDBOX_DIR"/chroot_executable.sh + script_to_execute="$SANDBOX_DIR"/script_to_execute.sh + + export chroot_executable + export script_to_execute + + cat >"$mount_and_execute" <<"EOF" +#!/bin/sh + +TRY_COMMAND="$TRY_COMMAND($0)" + +## A wrapper of `mount -t overlay` to have cleaner looking code +make_overlay() { + sandbox_dir="$1" + 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` +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 + UNION_HELPER=mergerfs + elif command -v unionfs >/dev/null; then + UNION_HELPER=unionfs + fi +} + +# Detect if union_helper is set, if not, we try to autodetect them +if [ -z "$UNION_HELPER" ] +then + ## Try to detect the union_helper (the variable could still be empty afterwards). + autodetect_union_helper +fi + +# actually mount the overlays +for mountpoint in $(cat "$UPDATED_DIRS_AND_MOUNTS") +do + pure_mountpoint=${mountpoint##*:} + + ## We are not interested in mounts that are not directories + if ! [ -d "$pure_mountpoint" ] + then + continue + fi + + ## Symlinks + if [ -L "$pure_mountpoint" ] + then + ln -s $(readlink "$pure_mountpoint") "$SANDBOX_DIR/temproot/$pure_mountpoint" + 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" "$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 + ## 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. + ## To address this, we use unionfs/mergerfs (they support the same functionality) to show all mounts under the target mount as normal directories. + ## Then we can normally make the overlay on the new union directory. + ## + ## MMG 2025-01-27 + ## There used to be more complicated logic here using `findmnt`, but we currently + ## just build unions for every mount in the root. + + if [ -z "$UNION_HELPER" ] + then + ## We can ignore this mountpoint, if the user program tries to use it, it will crash, but if not we can run normally + printf "%s: Warning: Failed mounting $mountpoint as an overlay and mergerfs or unionfs not set and could not be found, see \"$try_mount_log\"\n" "$TRY_COMMAND" >&2 + else + merger_dir="$SANDBOX_DIR"/mergerdir"$(echo "$pure_mountpoint" | tr '/' '.')" + mkdir "$merger_dir" + + ## Create a union directory + ## NB $mountpoint is the local directory to mount + ## $merger_dir is where we'll put its merger + "$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_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 +done + +## 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 + cp $chroot_executable "$SANDBOX_DIR/temproot/$chroot_executable" +fi + +unshare --root="$SANDBOX_DIR/temproot" "$TRY_SHELL" "$chroot_executable" +exitcode="$?" + +# unmount the devices +rm "$sandbox_dir/temproot/dev/stdin" +rm "$sandbox_dir/temproot/dev/stdout" +rm "$sandbox_dir/temproot/dev/stderr" + +unmount_devices "$SANDBOX_DIR" + +exit $exitcode +EOF + + # NB we substitute in the heredoc, so the early unsets are okay! + cat >"$chroot_executable" <"$script_to_execute" + + # `$script_to_execute` need not be +x to be sourced + chmod +x "$mount_and_execute" "$chroot_executable" + + # enable job control so interactive commands will play nicely with try asking for user input later(for committing). #5 + [ -t 0 ] && set -m + + # --mount: mounting and unmounting filesystems will not affect the rest of the system outside the unshare + # --map-root-user: map to the superuser UID and GID in the newly created user namespace. + # --user: the process will have a distinct set of UIDs, GIDs and capabilities. + # --pid: create a new process namespace (needed fr procfs to work right) + # --fork: necessary if we do --pid + # "Creation of a persistent PID namespace will fail if the --fork option is not also specified." + # shellcheck disable=SC2086 # we want field splitting! + unshare --mount --map-root-user --user --pid --fork $EXTRA_NS "$mount_and_execute" + TRY_EXIT_STATUS=$? + + OLDPWD="a" + + #echo "$OLDPWD" + #cd $TEMP_OLDPWD + #cd $TEMP_PWD + #echo "$OLDPWD" + + # remove symlink + # first set temproot to be writible, rhel derivatives defaults / to r-xr-xr-x + chmod 755 "${SANDBOX_DIR}/temproot" + while IFS="" read -r mountpoint + do + pure_mountpoint=${mountpoint##*:} + if [ -L "$pure_mountpoint" ] + then + rm "${SANDBOX_DIR}/temproot/${mountpoint}" + fi + done <"$DIRS_AND_MOUNTS" + + ################################################################################ + # commit? + + case "$NO_COMMIT" in + (quiet) ;; + (show) echo "$SANDBOX_DIR";; + (commit) commit;; + (interactive) summary >&2 + # shellcheck disable=SC2181 + if [ "$?" -eq 0 ] + then + printf "\nCommit these changes? [y/N] " >&2 + echo "$OLDPWD" + read -r DO_COMMIT + case "$DO_COMMIT" in + (y|Y|yes|YES) commit;; + (*) echo "Not committing." >&2 + echo "$SANDBOX_DIR";; + esac + fi;; + esac +} + +################################################################################ +# Summarize the overlay in `$SANDBOX_DIR` +################################################################################ + +if type try-summary >/dev/null 2>&1 +then + summary() { + try-summary -i "$IGNORE_FILE" "$SANDBOX_DIR" || return 1 + TRY_EXIT_STATUS=0 + } +else + summary() { + if ! [ -d "$SANDBOX_DIR" ] + then + error "could not find directory $SANDBOX_DIR" 2 + elif ! [ -d "$SANDBOX_DIR/upperdir" ] + then + error "could not find directory $SANDBOX_DIR/upperdir" 1 + fi + + ## Finds all potential changes + changed_files=$(find_upperdir_changes "$SANDBOX_DIR" "$IGNORE_FILE") + summary_output=$(process_changes "$SANDBOX_DIR" "$changed_files") + if [ -z "$summary_output" ] + then + return 1 + fi + + echo + echo "Changes detected in the following files:" + echo + + echo "$summary_output" | while IFS= read -r summary_line + do + local_file="$(echo "$summary_line" | cut -c 4-)" + case "$summary_line" in + (ln*) echo "$local_file (symlink)";; + (rd*) echo "$local_file (replaced with dir)";; + (md*) echo "$local_file (created dir)";; + (de*) echo "$local_file (deleted)";; + (mo*) echo "$local_file (modified)";; + (ad*) echo "$local_file (added)";; + esac + done + + TRY_EXIT_STATUS=0 + } +fi + +################################################################################ +# Commit the results of an overlay in `$SANDBOX_DIR` +################################################################################ + +if type try-commit >/dev/null 2>&1 +then + commit() { + try-commit -i "$IGNORE_FILE" "$SANDBOX_DIR" + TRY_EXIT_STATUS=$? + } +else + commit() { + if ! [ -d "$SANDBOX_DIR" ] + then + error "could not find directory $SANDBOX_DIR" "$TRY_COMMAND" 2 + elif ! [ -d "$SANDBOX_DIR/upperdir" ] + then + error "could not find directory $SANDBOX_DIR/upperdir" 1 + fi + + changed_files=$(find_upperdir_changes "$SANDBOX_DIR" "$IGNORE_FILE") + summary_output=$(process_changes "$SANDBOX_DIR" "$changed_files") + + TRY_EXIT_STATUS=0 + echo "$summary_output" | while IFS= read -r summary_line; do + local_file="$(echo "$summary_line" | cut -c 4-)" + changed_file="$SANDBOX_DIR/upperdir$local_file" + case $summary_line in + (ln*) rm -rf "$local_file"; ln -s "$(readlink "$changed_file")" "$local_file";; + (rd*) rm -rf "$local_file"; mkdir "$local_file";; + (md*) mkdir "$local_file";; + (de*) rm -rf "$local_file";; + (mo*) rm -rf "$local_file"; mv "$changed_file" "$local_file";; + (ad*) mv "$changed_file" "$local_file";; + esac + + # shellcheck disable=SC2181 + if [ "$?" -ne 0 ] + then + warn "couldn't commit $changed_file" + TRY_EXIT_STATUS=1 + fi + done + } +fi + +################################################################################ +## Defines which changes we want to ignore in the summary and commit +################################################################################ + +ignore_changes() { + ignore_file="$1" + + grep -v -f "$ignore_file" +} + +################################################################################ +## Lists all upperdir changes in raw format +################################################################################ + +find_upperdir_changes() { + sandbox_dir="$1" + ignore_file="$2" + + find "$sandbox_dir/upperdir/" -type f -o \( -type c -size 0 \) -o -type d -o -type l | ignore_changes "$ignore_file" +} + +################################################################################ +# Processes upperdir changes to an internal format that can be processed by summary and commit +# +# Output format: +# +# XX PATH +# +# where: +# XX is a two character code for the modification +# - rd: Replaced with a directory +# - md: Created a directory +# - de: Deleted a file +# - mo: Modified a file +# - ad: Added a file +# +# PATH is the local/host path (i.e., without the upper +################################################################################ + +process_changes() { + sandbox_dir="$1" + changed_files="$2" + + while IFS= read -r changed_file + do + local_file="${changed_file#"$sandbox_dir/upperdir"}" + if [ -L "$changed_file" ] + then + # // TRYCASE(symlink, *) + echo "ln $local_file" + elif [ -d "$changed_file" ] + then + if ! [ -e "$local_file" ] + then + # // TRYCASE(dir, nonexist) + echo "md $local_file" + continue + fi + + if [ "$(getfattr --absolute-names --only-values --e text -n user.overlay.opaque "$changed_file" 2>/dev/null)" = "y" ] + then + # // TRYCASE(opaque, *) + # // TRYCASE(dir, dir) + echo "rd $local_file" + continue + fi + + if ! [ -d "$local_file" ] + then + # // TRYCASE(dir, file) + # // TRYCASE(dir, symlink) + echo "rd $local_file" + continue + fi + + # must be a directory, but not opaque---leave it! + elif [ -c "$changed_file" ] && ! [ -s "$changed_file" ] && [ "$(stat -c %t,%T "$changed_file")" = "0,0" ] + then + # // TRYCASE(whiteout, *) + echo "de $local_file" + elif [ -f "$changed_file" ] + then + if [ -f "$changed_file" ] && getfattr --absolute-names -d "$changed_file" 2>/dev/null | grep -q -e "user.overlay.whiteout" + then + # // TRYCASE(whiteout, *) + echo "de $local_file" + continue + fi + + if [ -e "$local_file" ] + then + # // TRYCASE(file, file) + # // TRYCASE(file, dir) + # // TRYCASE(file, symlink) + echo "mo $local_file" + else + # // TRYCASE(file, nonexist) + echo "ad $local_file" + fi + fi + done <&2 +} + +################################################################################ +# Emit a warning and exit +################################################################################ + +error() { + msg="$1" + exit_status="$2" + + warn "$msg" + exit "$exit_status" +} + +################################################################################ +# Argument parsing +################################################################################ + +usage() { + cat >&2 <>"$IGNORE_FILE";; + (D) if ! [ -d "$OPTARG" ] + then + error "could not find sandbox directory '$OPTARG'" 2 + 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" ] + then + error "could not find executable union helper '$OPTARG'" 2 + fi + UNION_HELPER="$OPTARG" + export UNION_HELPER;; + (x) EXTRA_NS="--net";; + (h|*) usage + exit 0;; + esac +done + +shift $((OPTIND - 1)) + +if [ "$#" -eq 0 ] +then + usage + exit 2 +fi + +TRY_EXIT_STATUS=1 +case "$1" in + (summary) : "${SANDBOX_DIR=$2}" + summary + rm "$IGNORE_FILE" # we didn't move it to the sandbox, so clean up + ;; + (commit) : "${SANDBOX_DIR=$2}" + commit + rm "$IGNORE_FILE" # we didn't move it to the sandbox, so clean up + ;; + (explore) : "${SANDBOX_DIR=$2}" + set_TRY_SHELL + try "$TRY_SHELL";; + (--) shift + try "$@";; + (*) try "$@";; +esac + +exit "$TRY_EXIT_STATUS" diff --git a/test/check_shell.sh b/test/check_shell.sh index 97d3cc9a..86399c3e 100755 --- a/test/check_shell.sh +++ b/test/check_shell.sh @@ -24,7 +24,10 @@ check_case() { expected="$(mktemp)" out="$(mktemp)" echo "$expected_output" >"$expected" - TRY_SHELL="$try_shell" SHELL="$shell" "$TRY" "echo \"\$TRY_SHELL\"" >"$out" || exit 1 + TRY_SHELL="$try_shell" SHELL="$shell" "$TRY" "readlink /proc/$$/exe" >"$out" || exit 1 + + cat "$out" + #cat "$expected_output" if ! diff -q "$expected" "$out"; then exit "$case" diff --git a/test/print-shell.sh b/test/print-shell.sh new file mode 100755 index 00000000..2f860cb3 --- /dev/null +++ b/test/print-shell.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +readlink /proc/$$/exe diff --git a/try b/try index 47755271..1c34b373 100755 --- a/try +++ b/try @@ -159,7 +159,18 @@ make_overlay() { sandbox_dir="$1" 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,index=off,xino=off" "$sandbox_dir/temproot/$overlay_mountpoint" + 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" +} + +check_fstype() { + mountpoint="$1" + fstype=$(stat -f -c %T "$mountpoint") + + if [[ "$fstype" = "msdos" || "$fstype" = "exfat" || "$fstype" = "hfs" || "$fstype" = "hfs+" ]]; then + return 1 + fi + + return 0 } @@ -225,9 +236,18 @@ do esac # Try mounting everything normally - make_overlay "$SANDBOX_DIR" "$mountpoint" "$pure_mountpoint" 2>>"$try_mount_log" + # If the fstype is valid we mount everything + + check=0 + + if check_fstype "$pure_mountpoint"; then + make_overlay "$SANDBOX_DIR" "$mountpoint" "$pure_mountpoint" 2>>"$try_mount_log" + else + check=1 + fi + # If mounting everything normally fails, we try using either using mergerfs or unionfs to mount them. - if [ "$?" -ne 0 ] + if [[ "$?" -ne 0 || "$check" -ne 0 ]] then ## 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. ## To address this, we use unionfs/mergerfs (they support the same functionality) to show all mounts under the target mount as normal directories. From 4fe9f09402ceb777ac2479fa167c9b43a991713f Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Thu, 6 Nov 2025 01:35:00 -0500 Subject: [PATCH 03/10] Added check for fstypes --- mount_notes.txt | 33 --- temptry | 706 -------------------------------------------- test/print-shell.sh | 3 - 3 files changed, 742 deletions(-) delete mode 100644 mount_notes.txt delete mode 100644 temptry delete mode 100755 test/print-shell.sh diff --git a/mount_notes.txt b/mount_notes.txt deleted file mode 100644 index d2dd7f68..00000000 --- a/mount_notes.txt +++ /dev/null @@ -1,33 +0,0 @@ - - -Idea: - -1) Create partitions of various file systems -2) Mount the fs on / and check try to see if overlay failed on that specific file - -Results: -ext3: no errors -ext2: no errors -ext4: no errors (duh) -btrfs: no errors -bcachefs: no errors -exfat: failed -f2fs: no errors -fat16 (msdos): failed -fat32 (msdos): failed -hfs: failed -hfs+: failed -jfs: no errors -swap: failure to mount swap partition (no fault of overlayfs) -LVM2: failure to mount LVM2 partition (no fault of overlayfs) -minux: no errors -nilfs2: no errors -ntfs: no errors (however when doing stat -f it shows the fs type as "UNKNOWN" followed by an address) -resierfs: failure to mount resierfs partition (no fault of overlayfs) -udf: no errors -xtfs: no errors - -Solution: -Now that we know what fs types fail we can check before the overlay call and test if the dir is a type that fails -(maybe using stat -f). If it does lets just ignore the over call and go straight to the mergerfs call - diff --git a/temptry b/temptry deleted file mode 100644 index 940902f3..00000000 --- a/temptry +++ /dev/null @@ -1,706 +0,0 @@ -#!/bin/sh - -# Copyright (c) 2023 The PaSh Authors. -# -# Usage of this source code is governed by the MIT license, you can find the -# LICENSE file in the root directory of this project. -# -# https://github.com/binpash/try - -TRY_VERSION="0.2.0" -TRY_COMMAND="${0##*/}" -EXECID="$(date +%s%3N)" -export EXECID -export TRY_COMMAND - -# exit status invariants -# -# 0 -- command ran -# 1 -- consistency error/failure -# 2 -- input error - -################################################################################ -# Tries to detect a setting for TRY_SHELL -################################################################################ - -set_TRY_SHELL() { - # case: TRY_SHELL set to an executable - [ -x "$TRY_SHELL" ] && return - - # use SHELL (if it's not fish) - if [ -x "$SHELL" ] && [ "${SHELL##*/}" != "fish" ]; then - TRY_SHELL="$SHELL" - - else - login_shell=$(grep -e "^$LOGNAME" /etc/passwd | cut -d: -f7) - - # use login shell (if it's not fish) - if [ -x "$login_shell" ] && [ "${login_shell##*/}" != "fish" ]; then - TRY_SHELL="$login_shell" - else - TRY_SHELL="/bin/sh" - fi - fi - - export TRY_SHELL -} - - -################################################################################ -# Run a command (in `$@`) in an overlay (in `$SANDBOX_DIR`) -################################################################################ - -try() { - set_TRY_SHELL - START_DIR="$PWD" - - if [ "$SANDBOX_DIR" ] - then - ## If the name of a sandbox is given then we need to exit prematurely if its directory doesn't exist - [ -d "$SANDBOX_DIR" ] || error "could not find sandbox directory $SANDBOX_DIR" 2 - # Force absolute path - SANDBOX_DIR="$(cd "$SANDBOX_DIR" && pwd)" - - # shellcheck disable=SC2181 - [ "$?" -eq 0 ] || error "could not find sandbox directory $SANDBOX_DIR (could not cd in)" 2 - else - ## Create a new sandbox if one was not given - SANDBOX_DIR="$(mktemp -d --suffix ".try-$EXECID")" - fi - OLDPWD="a" - export "OLDPWD"="$OLDPWD" - - ## If the sandbox is not valid we exit early - if ! sandbox_valid_or_empty "$SANDBOX_DIR" - then - error "given sandbox '$SANDBOX_DIR' is invalid" 1 - fi - - ## Make any directories that don't already exist, this is OK to do here - ## because we have already checked if it valid. - export SANDBOX_DIR - - # We created "$IGNORE_FILE" up front, but now we can stash it in the sandbox. - mv "$IGNORE_FILE" "$SANDBOX_DIR"/ignore - IGNORE_FILE="$SANDBOX_DIR"/ignore - - try_mount_log="$SANDBOX_DIR"/mount.log - export try_mount_log - - # If we're in a docker container, we want to mount tmpfs on sandbox_dir, #136 - # tail -n +2 to ignore the first line with the column name - tmpfstype=$(df --output=fstype "$SANDBOX_DIR" | tail -n +2) - if [ "$tmpfstype" = "overlay" ] && [ "$(id -u)" -eq "0" ] - then - echo "mounting sandbox '$SANDBOX_DIR' as tmpfs (underlying fs is overlayfs)" >> "$try_mount_log" - echo "consider docker volumes if you want persistence" >> "$try_mount_log" - mount -t tmpfs tmpfs "$SANDBOX_DIR" - fi - - mkdir -p "$SANDBOX_DIR/upperdir" "$SANDBOX_DIR/workdir" "$SANDBOX_DIR/temproot" - - ## Find all the directories and mounts that need to be mounted - DIRS_AND_MOUNTS="$SANDBOX_DIR"/mounts - export DIRS_AND_MOUNTS - find / -maxdepth 1 >"$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="$SANDBOX_DIR"/mounts.updated - export UPDATED_DIRS_AND_MOUNTS - while IFS="" read -r mountpoint - do - new_mountpoint="" - OLDIFS=$IFS - IFS=":" - - for lower_dir in $LOWER_DIRS - do - temp_mountpoint="$lower_dir/upperdir$mountpoint" - # Make sure we put : between, but not at the beginning - new_mountpoint="${new_mountpoint:+$new_mountpoint:}$temp_mountpoint" - 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 - # - # KK 2023-06-29 This approach (of mounting each root directory separately) was necessary because we could not mount `/` in an overlay. - # However, this might be solvable using mergerfs/unionfs, allowing us to mount an overlay on a unionfs of the `/` once. - while IFS="" read -r mountpoint - do - ## Only make the directory if the original is a directory too - if [ -d "$mountpoint" ] && ! [ -L "$mountpoint" ] - then - # shellcheck disable=SC2174 # warning acknowledged, "When used with -p, -m only applies to the deepest directory." - mkdir -m "$(stat -c %a "$mountpoint")" -p "${SANDBOX_DIR}/upperdir/${mountpoint}" "${SANDBOX_DIR}/workdir/${mountpoint}" "${SANDBOX_DIR}/temproot/${mountpoint}" - fi - done <"$DIRS_AND_MOUNTS" - - chmod "$(stat -c %a /)" "$SANDBOX_DIR/temproot" - - mount_and_execute="$SANDBOX_DIR"/mount_and_execute.sh - chroot_executable="$SANDBOX_DIR"/chroot_executable.sh - script_to_execute="$SANDBOX_DIR"/script_to_execute.sh - - export chroot_executable - export script_to_execute - - cat >"$mount_and_execute" <<"EOF" -#!/bin/sh - -TRY_COMMAND="$TRY_COMMAND($0)" - -## A wrapper of `mount -t overlay` to have cleaner looking code -make_overlay() { - sandbox_dir="$1" - 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` -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 - UNION_HELPER=mergerfs - elif command -v unionfs >/dev/null; then - UNION_HELPER=unionfs - fi -} - -# Detect if union_helper is set, if not, we try to autodetect them -if [ -z "$UNION_HELPER" ] -then - ## Try to detect the union_helper (the variable could still be empty afterwards). - autodetect_union_helper -fi - -# actually mount the overlays -for mountpoint in $(cat "$UPDATED_DIRS_AND_MOUNTS") -do - pure_mountpoint=${mountpoint##*:} - - ## We are not interested in mounts that are not directories - if ! [ -d "$pure_mountpoint" ] - then - continue - fi - - ## Symlinks - if [ -L "$pure_mountpoint" ] - then - ln -s $(readlink "$pure_mountpoint") "$SANDBOX_DIR/temproot/$pure_mountpoint" - 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" "$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 - ## 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. - ## To address this, we use unionfs/mergerfs (they support the same functionality) to show all mounts under the target mount as normal directories. - ## Then we can normally make the overlay on the new union directory. - ## - ## MMG 2025-01-27 - ## There used to be more complicated logic here using `findmnt`, but we currently - ## just build unions for every mount in the root. - - if [ -z "$UNION_HELPER" ] - then - ## We can ignore this mountpoint, if the user program tries to use it, it will crash, but if not we can run normally - printf "%s: Warning: Failed mounting $mountpoint as an overlay and mergerfs or unionfs not set and could not be found, see \"$try_mount_log\"\n" "$TRY_COMMAND" >&2 - else - merger_dir="$SANDBOX_DIR"/mergerdir"$(echo "$pure_mountpoint" | tr '/' '.')" - mkdir "$merger_dir" - - ## Create a union directory - ## NB $mountpoint is the local directory to mount - ## $merger_dir is where we'll put its merger - "$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_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 -done - -## 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 - cp $chroot_executable "$SANDBOX_DIR/temproot/$chroot_executable" -fi - -unshare --root="$SANDBOX_DIR/temproot" "$TRY_SHELL" "$chroot_executable" -exitcode="$?" - -# unmount the devices -rm "$sandbox_dir/temproot/dev/stdin" -rm "$sandbox_dir/temproot/dev/stdout" -rm "$sandbox_dir/temproot/dev/stderr" - -unmount_devices "$SANDBOX_DIR" - -exit $exitcode -EOF - - # NB we substitute in the heredoc, so the early unsets are okay! - cat >"$chroot_executable" <"$script_to_execute" - - # `$script_to_execute` need not be +x to be sourced - chmod +x "$mount_and_execute" "$chroot_executable" - - # enable job control so interactive commands will play nicely with try asking for user input later(for committing). #5 - [ -t 0 ] && set -m - - # --mount: mounting and unmounting filesystems will not affect the rest of the system outside the unshare - # --map-root-user: map to the superuser UID and GID in the newly created user namespace. - # --user: the process will have a distinct set of UIDs, GIDs and capabilities. - # --pid: create a new process namespace (needed fr procfs to work right) - # --fork: necessary if we do --pid - # "Creation of a persistent PID namespace will fail if the --fork option is not also specified." - # shellcheck disable=SC2086 # we want field splitting! - unshare --mount --map-root-user --user --pid --fork $EXTRA_NS "$mount_and_execute" - TRY_EXIT_STATUS=$? - - OLDPWD="a" - - #echo "$OLDPWD" - #cd $TEMP_OLDPWD - #cd $TEMP_PWD - #echo "$OLDPWD" - - # remove symlink - # first set temproot to be writible, rhel derivatives defaults / to r-xr-xr-x - chmod 755 "${SANDBOX_DIR}/temproot" - while IFS="" read -r mountpoint - do - pure_mountpoint=${mountpoint##*:} - if [ -L "$pure_mountpoint" ] - then - rm "${SANDBOX_DIR}/temproot/${mountpoint}" - fi - done <"$DIRS_AND_MOUNTS" - - ################################################################################ - # commit? - - case "$NO_COMMIT" in - (quiet) ;; - (show) echo "$SANDBOX_DIR";; - (commit) commit;; - (interactive) summary >&2 - # shellcheck disable=SC2181 - if [ "$?" -eq 0 ] - then - printf "\nCommit these changes? [y/N] " >&2 - echo "$OLDPWD" - read -r DO_COMMIT - case "$DO_COMMIT" in - (y|Y|yes|YES) commit;; - (*) echo "Not committing." >&2 - echo "$SANDBOX_DIR";; - esac - fi;; - esac -} - -################################################################################ -# Summarize the overlay in `$SANDBOX_DIR` -################################################################################ - -if type try-summary >/dev/null 2>&1 -then - summary() { - try-summary -i "$IGNORE_FILE" "$SANDBOX_DIR" || return 1 - TRY_EXIT_STATUS=0 - } -else - summary() { - if ! [ -d "$SANDBOX_DIR" ] - then - error "could not find directory $SANDBOX_DIR" 2 - elif ! [ -d "$SANDBOX_DIR/upperdir" ] - then - error "could not find directory $SANDBOX_DIR/upperdir" 1 - fi - - ## Finds all potential changes - changed_files=$(find_upperdir_changes "$SANDBOX_DIR" "$IGNORE_FILE") - summary_output=$(process_changes "$SANDBOX_DIR" "$changed_files") - if [ -z "$summary_output" ] - then - return 1 - fi - - echo - echo "Changes detected in the following files:" - echo - - echo "$summary_output" | while IFS= read -r summary_line - do - local_file="$(echo "$summary_line" | cut -c 4-)" - case "$summary_line" in - (ln*) echo "$local_file (symlink)";; - (rd*) echo "$local_file (replaced with dir)";; - (md*) echo "$local_file (created dir)";; - (de*) echo "$local_file (deleted)";; - (mo*) echo "$local_file (modified)";; - (ad*) echo "$local_file (added)";; - esac - done - - TRY_EXIT_STATUS=0 - } -fi - -################################################################################ -# Commit the results of an overlay in `$SANDBOX_DIR` -################################################################################ - -if type try-commit >/dev/null 2>&1 -then - commit() { - try-commit -i "$IGNORE_FILE" "$SANDBOX_DIR" - TRY_EXIT_STATUS=$? - } -else - commit() { - if ! [ -d "$SANDBOX_DIR" ] - then - error "could not find directory $SANDBOX_DIR" "$TRY_COMMAND" 2 - elif ! [ -d "$SANDBOX_DIR/upperdir" ] - then - error "could not find directory $SANDBOX_DIR/upperdir" 1 - fi - - changed_files=$(find_upperdir_changes "$SANDBOX_DIR" "$IGNORE_FILE") - summary_output=$(process_changes "$SANDBOX_DIR" "$changed_files") - - TRY_EXIT_STATUS=0 - echo "$summary_output" | while IFS= read -r summary_line; do - local_file="$(echo "$summary_line" | cut -c 4-)" - changed_file="$SANDBOX_DIR/upperdir$local_file" - case $summary_line in - (ln*) rm -rf "$local_file"; ln -s "$(readlink "$changed_file")" "$local_file";; - (rd*) rm -rf "$local_file"; mkdir "$local_file";; - (md*) mkdir "$local_file";; - (de*) rm -rf "$local_file";; - (mo*) rm -rf "$local_file"; mv "$changed_file" "$local_file";; - (ad*) mv "$changed_file" "$local_file";; - esac - - # shellcheck disable=SC2181 - if [ "$?" -ne 0 ] - then - warn "couldn't commit $changed_file" - TRY_EXIT_STATUS=1 - fi - done - } -fi - -################################################################################ -## Defines which changes we want to ignore in the summary and commit -################################################################################ - -ignore_changes() { - ignore_file="$1" - - grep -v -f "$ignore_file" -} - -################################################################################ -## Lists all upperdir changes in raw format -################################################################################ - -find_upperdir_changes() { - sandbox_dir="$1" - ignore_file="$2" - - find "$sandbox_dir/upperdir/" -type f -o \( -type c -size 0 \) -o -type d -o -type l | ignore_changes "$ignore_file" -} - -################################################################################ -# Processes upperdir changes to an internal format that can be processed by summary and commit -# -# Output format: -# -# XX PATH -# -# where: -# XX is a two character code for the modification -# - rd: Replaced with a directory -# - md: Created a directory -# - de: Deleted a file -# - mo: Modified a file -# - ad: Added a file -# -# PATH is the local/host path (i.e., without the upper -################################################################################ - -process_changes() { - sandbox_dir="$1" - changed_files="$2" - - while IFS= read -r changed_file - do - local_file="${changed_file#"$sandbox_dir/upperdir"}" - if [ -L "$changed_file" ] - then - # // TRYCASE(symlink, *) - echo "ln $local_file" - elif [ -d "$changed_file" ] - then - if ! [ -e "$local_file" ] - then - # // TRYCASE(dir, nonexist) - echo "md $local_file" - continue - fi - - if [ "$(getfattr --absolute-names --only-values --e text -n user.overlay.opaque "$changed_file" 2>/dev/null)" = "y" ] - then - # // TRYCASE(opaque, *) - # // TRYCASE(dir, dir) - echo "rd $local_file" - continue - fi - - if ! [ -d "$local_file" ] - then - # // TRYCASE(dir, file) - # // TRYCASE(dir, symlink) - echo "rd $local_file" - continue - fi - - # must be a directory, but not opaque---leave it! - elif [ -c "$changed_file" ] && ! [ -s "$changed_file" ] && [ "$(stat -c %t,%T "$changed_file")" = "0,0" ] - then - # // TRYCASE(whiteout, *) - echo "de $local_file" - elif [ -f "$changed_file" ] - then - if [ -f "$changed_file" ] && getfattr --absolute-names -d "$changed_file" 2>/dev/null | grep -q -e "user.overlay.whiteout" - then - # // TRYCASE(whiteout, *) - echo "de $local_file" - continue - fi - - if [ -e "$local_file" ] - then - # // TRYCASE(file, file) - # // TRYCASE(file, dir) - # // TRYCASE(file, symlink) - echo "mo $local_file" - else - # // TRYCASE(file, nonexist) - echo "ad $local_file" - fi - fi - done <&2 -} - -################################################################################ -# Emit a warning and exit -################################################################################ - -error() { - msg="$1" - exit_status="$2" - - warn "$msg" - exit "$exit_status" -} - -################################################################################ -# Argument parsing -################################################################################ - -usage() { - cat >&2 <>"$IGNORE_FILE";; - (D) if ! [ -d "$OPTARG" ] - then - error "could not find sandbox directory '$OPTARG'" 2 - 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" ] - then - error "could not find executable union helper '$OPTARG'" 2 - fi - UNION_HELPER="$OPTARG" - export UNION_HELPER;; - (x) EXTRA_NS="--net";; - (h|*) usage - exit 0;; - esac -done - -shift $((OPTIND - 1)) - -if [ "$#" -eq 0 ] -then - usage - exit 2 -fi - -TRY_EXIT_STATUS=1 -case "$1" in - (summary) : "${SANDBOX_DIR=$2}" - summary - rm "$IGNORE_FILE" # we didn't move it to the sandbox, so clean up - ;; - (commit) : "${SANDBOX_DIR=$2}" - commit - rm "$IGNORE_FILE" # we didn't move it to the sandbox, so clean up - ;; - (explore) : "${SANDBOX_DIR=$2}" - set_TRY_SHELL - try "$TRY_SHELL";; - (--) shift - try "$@";; - (*) try "$@";; -esac - -exit "$TRY_EXIT_STATUS" diff --git a/test/print-shell.sh b/test/print-shell.sh deleted file mode 100755 index 2f860cb3..00000000 --- a/test/print-shell.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -readlink /proc/$$/exe From a1705642f2b6c22eb05ba472017af30356006e1c Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Thu, 6 Nov 2025 01:40:21 -0500 Subject: [PATCH 04/10] Added check for fstypes --- test/check_shell.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/check_shell.sh b/test/check_shell.sh index 86399c3e..97d3cc9a 100755 --- a/test/check_shell.sh +++ b/test/check_shell.sh @@ -24,10 +24,7 @@ check_case() { expected="$(mktemp)" out="$(mktemp)" echo "$expected_output" >"$expected" - TRY_SHELL="$try_shell" SHELL="$shell" "$TRY" "readlink /proc/$$/exe" >"$out" || exit 1 - - cat "$out" - #cat "$expected_output" + TRY_SHELL="$try_shell" SHELL="$shell" "$TRY" "echo \"\$TRY_SHELL\"" >"$out" || exit 1 if ! diff -q "$expected" "$out"; then exit "$case" From 163c04e354ba01148cb787f4bf8c0c6b1abc3ddf Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Thu, 6 Nov 2025 01:43:03 -0500 Subject: [PATCH 05/10] Added check for fstypes --- test/try | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/try diff --git a/test/try b/test/try new file mode 100644 index 00000000..e69de29b From 9d864ff53f661c3fb2a7fd11ea1a024096099b33 Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Thu, 6 Nov 2025 01:44:49 -0500 Subject: [PATCH 06/10] Added check for fstypes --- try | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/try b/try index 1c34b373..3628bf3c 100755 --- a/try +++ b/try @@ -238,16 +238,16 @@ do # Try mounting everything normally # If the fstype is valid we mount everything - check=0 + fs_fail_flag=0 if check_fstype "$pure_mountpoint"; then make_overlay "$SANDBOX_DIR" "$mountpoint" "$pure_mountpoint" 2>>"$try_mount_log" else - check=1 + fs_fail_flag=1 fi # If mounting everything normally fails, we try using either using mergerfs or unionfs to mount them. - if [[ "$?" -ne 0 || "$check" -ne 0 ]] + if [[ "$?" -ne 0 || "$fs_fail_flag" -ne 0 ]] then ## 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. ## To address this, we use unionfs/mergerfs (they support the same functionality) to show all mounts under the target mount as normal directories. From 830df48181d0b4a4f9711b94b1d49c351ad0e12e Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Thu, 6 Nov 2025 01:45:28 -0500 Subject: [PATCH 07/10] Added check for fstypes --- try | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/try b/try index 3628bf3c..7a582aa9 100755 --- a/try +++ b/try @@ -166,7 +166,7 @@ check_fstype() { mountpoint="$1" fstype=$(stat -f -c %T "$mountpoint") - if [[ "$fstype" = "msdos" || "$fstype" = "exfat" || "$fstype" = "hfs" || "$fstype" = "hfs+" ]]; then + if [[ "$fstype" = "msdos" || "$fstype" = "exfat" || "$fstype" = "hfs" || "$fstype" = "hfs+" ]]; then return 1 fi From bf9bef4b4c86746d2d6cc68538de7e5ef5a223a5 Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Thu, 6 Nov 2025 01:46:21 -0500 Subject: [PATCH 08/10] Added check for fstypes --- try | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/try b/try index 7a582aa9..ada4b2ad 100755 --- a/try +++ b/try @@ -159,7 +159,7 @@ make_overlay() { sandbox_dir="$1" 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" + mount -t overlay overlay -o userxattr -o "lowerdir=$lowerdirs,upperdir=$sandbox_dir/upperdir/$overlay_mountpoint,workdir=$sandbox_dir/workdir/$overlay_mountpoint,index=off" "$sandbox_dir/temproot/$overlay_mountpoint" } check_fstype() { From 5fe0c19e69ce1eadc3365b63041721d853f31b0c Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Tue, 18 Nov 2025 03:05:59 -0500 Subject: [PATCH 09/10] Added rm log --- Vagrantfile | 2 +- test/merge_multiple_dirs.sh | 69 ++++++++++++++++-------------------- try | 28 ++++++++------- utils/make-socket | Bin 0 -> 20320 bytes 4 files changed, 47 insertions(+), 52 deletions(-) create mode 100755 utils/make-socket diff --git a/Vagrantfile b/Vagrantfile index 0119e767..2223c3a3 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -144,7 +144,7 @@ Vagrant.configure("2") do |config| config.vm.define "rocky9" do |rocky| rocky.vm.box = "generic/rocky9" rocky.vm.provision "file", source: "./", destination: "/home/vagrant/try" - rocky.vm.provision "shell", privileged: false, inline: " + rocky.vm.provision "shell", privileged: false, inline: " sudo yum install -y git expect curl attr pandoc fuse wget https://github.com/trapexit/mergerfs/releases/download/2.40.2/mergerfs-2.40.2-1.el9.x86_64.rpm sudo rpm -i mergerfs-2.40.2-1.el9.x86_64.rpm diff --git a/test/merge_multiple_dirs.sh b/test/merge_multiple_dirs.sh index 12c97008..94a49f1b 100755 --- a/test/merge_multiple_dirs.sh +++ b/test/merge_multiple_dirs.sh @@ -4,41 +4,34 @@ TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working- 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 + 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 @@ -59,13 +52,13 @@ expected2="$(mktemp)" expected3="$(mktemp)" touch "$expected1" -echo "test2" > "$expected2" -echo "test3" > "$expected3" +echo "test2" >"$expected2" +echo "test3" >"$expected3" "$TRY" -D "$try_example_dir1" "touch file_1.txt; echo test > file_2.txt; rm file.txt.gz" || exit 2 "$TRY" -D "$try_example_dir2" "echo test2 > file_2.txt" || exit 3 "$TRY" -D "$try_example_dir3" "echo test3 > file_3.txt" || exit 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"|| exit 5 +"$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" || exit 5 diff -q "$expected1" out1 || exit 6 diff -q "$expected2" out2 || exit 7 diff --git a/try b/try index ada4b2ad..8de16cd9 100755 --- a/try +++ b/try @@ -83,7 +83,9 @@ try() { IGNORE_FILE="$SANDBOX_DIR"/ignore try_mount_log="$SANDBOX_DIR"/mount.log + try_remove_log="$SANDBOX_DIR/error.log" export try_mount_log + export try_remove_log # If we're in a docker container, we want to mount tmpfs on sandbox_dir, #136 # tail -n +2 to ignore the first line with the column name @@ -162,18 +164,16 @@ make_overlay() { mount -t overlay overlay -o userxattr -o "lowerdir=$lowerdirs,upperdir=$sandbox_dir/upperdir/$overlay_mountpoint,workdir=$sandbox_dir/workdir/$overlay_mountpoint,index=off" "$sandbox_dir/temproot/$overlay_mountpoint" } -check_fstype() { +mountable_without_mergerfs() { mountpoint="$1" fstype=$(stat -f -c %T "$mountpoint") - if [[ "$fstype" = "msdos" || "$fstype" = "exfat" || "$fstype" = "hfs" || "$fstype" = "hfs+" ]]; then - return 1 - fi - - return 0 + case "$fstype" in + (exfat|hfs|hfs+) return 1;; + (*) return 0;; + esac } - devices_to_mount="tty null zero full random urandom" ## Mounts and unmounts a few select devices instead of the whole `/dev` @@ -191,7 +191,7 @@ unmount_devices() { for dev in $devices_to_mount do umount "$sandbox_dir/temproot/dev/$dev" 2>>"$try_mount_log" - rm -f "$sandbox_dir/temproot/dev/$dev" + rm -f "$sandbox_dir/temproot/dev/$dev" 2>>"$try_remove_log" done } @@ -240,7 +240,7 @@ do fs_fail_flag=0 - if check_fstype "$pure_mountpoint"; then + if mountable_without_mergerfs "$pure_mountpoint"; then make_overlay "$SANDBOX_DIR" "$mountpoint" "$pure_mountpoint" 2>>"$try_mount_log" else fs_fail_flag=1 @@ -257,6 +257,8 @@ do ## There used to be more complicated logic here using `findmnt`, but we currently ## just build unions for every mount in the root. + echo "after: $pure_mountpoint" + if [ -z "$UNION_HELPER" ] then ## We can ignore this mountpoint, if the user program tries to use it, it will crash, but if not we can run normally @@ -289,9 +291,9 @@ unshare --root="$SANDBOX_DIR/temproot" "$TRY_SHELL" "$chroot_executable" exitcode="$?" # unmount the devices -rm "$sandbox_dir/temproot/dev/stdin" -rm "$sandbox_dir/temproot/dev/stdout" -rm "$sandbox_dir/temproot/dev/stderr" +rm "$sandbox_dir/temproot/dev/stdin" 2>>"$try_remove_log" +rm "$sandbox_dir/temproot/dev/stdout" 2>>"$try_remove_log" +rm "$sandbox_dir/temproot/dev/stderr" 2>>"$try_remove_log" unmount_devices "$SANDBOX_DIR" @@ -339,7 +341,7 @@ EOF pure_mountpoint=${mountpoint##*:} if [ -L "$pure_mountpoint" ] then - rm "${SANDBOX_DIR}/temproot/${mountpoint}" + rm "${SANDBOX_DIR}/temproot/${mountpoint}" 2>>"$try_remove_log" fi done <"$DIRS_AND_MOUNTS" diff --git a/utils/make-socket b/utils/make-socket new file mode 100755 index 0000000000000000000000000000000000000000..9956eddbd4c63bfe371356872829642f0caf8183 GIT binary patch literal 20320 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 literal 0 HcmV?d00001 From c101a1dac065fd7259f74ff27bbe26ed8a81c72f Mon Sep 17 00:00:00 2001 From: Ioa1 Date: Tue, 18 Nov 2025 03:09:03 -0500 Subject: [PATCH 10/10] Added rm log --- Vagrantfile | 2 +- test/merge_multiple_dirs.sh | 69 ++++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 2223c3a3..0119e767 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -144,7 +144,7 @@ Vagrant.configure("2") do |config| config.vm.define "rocky9" do |rocky| rocky.vm.box = "generic/rocky9" rocky.vm.provision "file", source: "./", destination: "/home/vagrant/try" - rocky.vm.provision "shell", privileged: false, inline: " + rocky.vm.provision "shell", privileged: false, inline: " sudo yum install -y git expect curl attr pandoc fuse wget https://github.com/trapexit/mergerfs/releases/download/2.40.2/mergerfs-2.40.2-1.el9.x86_64.rpm sudo rpm -i mergerfs-2.40.2-1.el9.x86_64.rpm diff --git a/test/merge_multiple_dirs.sh b/test/merge_multiple_dirs.sh index 94a49f1b..12c97008 100755 --- a/test/merge_multiple_dirs.sh +++ b/test/merge_multiple_dirs.sh @@ -4,34 +4,41 @@ TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working- 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 + 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 @@ -52,13 +59,13 @@ expected2="$(mktemp)" expected3="$(mktemp)" touch "$expected1" -echo "test2" >"$expected2" -echo "test3" >"$expected3" +echo "test2" > "$expected2" +echo "test3" > "$expected3" "$TRY" -D "$try_example_dir1" "touch file_1.txt; echo test > file_2.txt; rm file.txt.gz" || exit 2 "$TRY" -D "$try_example_dir2" "echo test2 > file_2.txt" || exit 3 "$TRY" -D "$try_example_dir3" "echo test3 > file_3.txt" || exit 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" || exit 5 +"$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"|| exit 5 diff -q "$expected1" out1 || exit 6 diff -q "$expected2" out2 || exit 7