Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
74 changes: 74 additions & 0 deletions test/reentry_delete_recreate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/sh

# Test: Files can be deleted and recreated across re-entries
# This tests that overlayfs whiteouts work correctly with re-entry

TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
TRY="$TRY_TOP/try"

cleanup() {
cd /

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

if [ -d "$try_sandbox" ]
then
rm -rf "$try_sandbox"
fi

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

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

if [ -f /tmp/testfile.txt ]
then
rm /tmp/testfile.txt
fi
}

trap 'cleanup' EXIT

try_workspace="$(mktemp -d)"
cd "$try_workspace" || exit 9

try_sandbox="$(mktemp -d)"
expected1="$(mktemp)"
expected2="$(mktemp)"

echo "first content" >"$expected1"
echo "second content" >"$expected2"

# First invocation: create file with content
"$TRY" -D "$try_sandbox" "echo 'first content' > /tmp/testfile.txt" || exit 1

# Verify file exists in upperdir
[ -f "$try_sandbox/upperdir/tmp/testfile.txt" ] || exit 2
diff -q "$expected1" "$try_sandbox/upperdir/tmp/testfile.txt" || exit 3

# Second invocation: delete the file
"$TRY" -D "$try_sandbox" "rm /tmp/testfile.txt" || exit 4

# Verify file is marked as deleted (whiteout should exist)
# Note: We can't directly check for whiteout, but file shouldn't exist in upperdir as a regular file
# Instead, check that after commit, the file doesn't exist

# Third invocation: recreate file with different content
"$TRY" -D "$try_sandbox" "echo 'second content' > /tmp/testfile.txt" || exit 5

# Verify new content
[ -f "$try_sandbox/upperdir/tmp/testfile.txt" ] || exit 6
diff -q "$expected2" "$try_sandbox/upperdir/tmp/testfile.txt" || exit 7

# Commit and verify final state has the recreated file
"$TRY" commit "$try_sandbox" || exit 8
[ -f /tmp/testfile.txt ] || exit 9
diff -q "$expected2" /tmp/testfile.txt || exit 10
62 changes: 62 additions & 0 deletions test/reentry_file_modifications.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/bin/sh

# Test: Multiple consecutive try commands should see and modify files from previous invocations
# This tests that file content modifications accumulate across re-entries

TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
TRY="$TRY_TOP/try"

cleanup() {
cd /

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

if [ -d "$try_sandbox" ]
then
rm -rf "$try_sandbox"
fi

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

if [ -f /tmp/testfile.txt ]
then
rm /tmp/testfile.txt
fi
}

trap 'cleanup' EXIT

try_workspace="$(mktemp -d)"
cd "$try_workspace" || exit 9

try_sandbox="$(mktemp -d)"
expected="$(mktemp)"

# Create expected output with three lines
cat >"$expected" <<EOF
line1
line2
line3
EOF

# First invocation: create file with line1
"$TRY" -D "$try_sandbox" "echo 'line1' > /tmp/testfile.txt" || exit 1

# Second invocation: append line2
"$TRY" -D "$try_sandbox" "echo 'line2' >> /tmp/testfile.txt" || exit 2

# Third invocation: append line3
"$TRY" -D "$try_sandbox" "echo 'line3' >> /tmp/testfile.txt" || exit 3

# Verify the file has all three lines in upperdir
diff -q "$expected" "$try_sandbox/upperdir/tmp/testfile.txt" || exit 4

# Commit and verify
"$TRY" commit "$try_sandbox" || exit 5
diff -q "$expected" /tmp/testfile.txt || exit 6
52 changes: 52 additions & 0 deletions test/reentry_multiple_files.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/bin/sh

# Test: Multiple consecutive try commands with -D should accumulate file changes
# This tests that each invocation sees changes from previous invocations

TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
TRY="$TRY_TOP/try"

cleanup() {
cd /

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

if [ -d "$try_sandbox" ]
then
rm -rf "$try_sandbox"
fi
}

trap 'cleanup' EXIT

try_workspace="$(mktemp -d)"
cd "$try_workspace" || exit 9

try_sandbox="$(mktemp -d)"

# First invocation: create file1.txt
"$TRY" -D "$try_sandbox" "touch /tmp/file1.txt" || exit 1

# Second invocation: create file2.txt
"$TRY" -D "$try_sandbox" "touch /tmp/file2.txt" || exit 2

# Third invocation: create file3.txt
"$TRY" -D "$try_sandbox" "touch /tmp/file3.txt" || exit 3

# Verify all three files exist in the upperdir
[ -f "$try_sandbox/upperdir/tmp/file1.txt" ] || exit 4
[ -f "$try_sandbox/upperdir/tmp/file2.txt" ] || exit 5
[ -f "$try_sandbox/upperdir/tmp/file3.txt" ] || exit 6

# Commit and verify all files are present
"$TRY" commit "$try_sandbox" || exit 7

[ -f /tmp/file1.txt ] || exit 8
[ -f /tmp/file2.txt ] || exit 9
[ -f /tmp/file3.txt ] || exit 10

# Cleanup committed files
rm -f /tmp/file1.txt /tmp/file2.txt /tmp/file3.txt
54 changes: 54 additions & 0 deletions test/reentry_workdir_creation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/sh

# Test: Each re-entry should create a unique workdir directory
# This verifies the fix for overlayfs workdir reuse issue

TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
TRY="$TRY_TOP/try"

cleanup() {
cd /

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

if [ -d "$try_sandbox" ]
then
rm -rf "$try_sandbox"
fi
}

trap 'cleanup' EXIT

try_workspace="$(mktemp -d)"
cd "$try_workspace" || exit 9

try_sandbox="$(mktemp -d)"

# First invocation
"$TRY" -D "$try_sandbox" "touch /tmp/file1.txt" || exit 1

# Count workdir directories (should be 1)
workdir_count=$(find "$try_sandbox" -maxdepth 1 -type d -name "workdir-*" | wc -l)
[ "$workdir_count" -eq 1 ] || exit 2

# Second invocation
"$TRY" -D "$try_sandbox" "touch /tmp/file2.txt" || exit 3

# Count workdir directories (should be 2)
workdir_count=$(find "$try_sandbox" -maxdepth 1 -type d -name "workdir-*" | wc -l)
[ "$workdir_count" -eq 2 ] || exit 4

# Third invocation
"$TRY" -D "$try_sandbox" "touch /tmp/file3.txt" || exit 5

# Count workdir directories (should be 3)
workdir_count=$(find "$try_sandbox" -maxdepth 1 -type d -name "workdir-*" | wc -l)
[ "$workdir_count" -eq 3 ] || exit 6

# Verify all files still exist in upperdir despite multiple workdirs
[ -f "$try_sandbox/upperdir/tmp/file1.txt" ] || exit 7
[ -f "$try_sandbox/upperdir/tmp/file2.txt" ] || exit 8
[ -f "$try_sandbox/upperdir/tmp/file3.txt" ] || exit 9
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/bin/sh

## This used to be a reasonable test, but the temproot etc doesn't exist anymore after execution


TRY_TOP="${TRY_TOP:-$(git rev-parse --show-toplevel --show-superproject-working-tree 2>/dev/null || echo "${0%/*}")}"
TRY="$TRY_TOP/try"

Expand Down
34 changes: 25 additions & 9 deletions try
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,16 @@ try() {
mount -t tmpfs tmpfs "$SANDBOX_DIR"
fi

mkdir -p "$SANDBOX_DIR/upperdir" "$SANDBOX_DIR/workdir" "$SANDBOX_DIR/temproot"
# Use a unique workdir for each invocation to avoid overlayfs state issues
# This allows multiple commands to accumulate changes in the same upperdir
WORKDIR_SUFFIX="$(date +%s%3N)-$$"
WORKDIR="$SANDBOX_DIR/workdir-$WORKDIR_SUFFIX"
export WORKDIR

# Clean up old workdir directories from previous runs to save space
find "$SANDBOX_DIR" -maxdepth 1 -type d -name "workdir-*" -exec rm -rf {} + 2>/dev/null || true

mkdir -p "$SANDBOX_DIR/upperdir" "$WORKDIR" "$SANDBOX_DIR/temproot"

## Find all the directories and mounts that need to be mounted
DIRS_AND_MOUNTS="$SANDBOX_DIR"/mounts
Expand Down Expand Up @@ -136,7 +145,7 @@ try() {
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}"
mkdir -m "$(stat -c %a "$mountpoint")" -p "${SANDBOX_DIR}/upperdir/${mountpoint}" "${WORKDIR}/${mountpoint}" "${SANDBOX_DIR}/temproot/${mountpoint}"
fi
done <"$DIRS_AND_MOUNTS"

Expand All @@ -159,7 +168,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=$WORKDIR/$overlay_mountpoint" "$sandbox_dir/temproot/$overlay_mountpoint"
}


Expand Down Expand Up @@ -216,7 +225,8 @@ do
## Symlinks
if [ -L "$pure_mountpoint" ]
then
ln -s $(readlink "$pure_mountpoint") "$SANDBOX_DIR/temproot/$pure_mountpoint"
rm -rf "$SANDBOX_DIR/temproot/$pure_mountpoint"
ln -sf "$(readlink "$pure_mountpoint")" "$SANDBOX_DIR/temproot/$pure_mountpoint"
continue
fi

Expand Down Expand Up @@ -244,12 +254,12 @@ do
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"
mkdir -p "$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" ||
"$UNION_HELPER" -o nonempty "$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
Expand Down Expand Up @@ -323,12 +333,16 @@ EOF
while IFS="" read -r mountpoint
do
pure_mountpoint=${mountpoint##*:}
if [ -L "$pure_mountpoint" ]
if [ -L "${SANDBOX_DIR}/temproot/${pure_mountpoint}" ]
then
rm "${SANDBOX_DIR}/temproot/${mountpoint}"
rm "${SANDBOX_DIR}/temproot/${pure_mountpoint}"
fi
done <"$DIRS_AND_MOUNTS"

# remove directories from temproot (but keep temproot itself)
# this ensures clean state for reentry with -D option
find "${SANDBOX_DIR}/temproot" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} + 2>/dev/null || true

################################################################################
# commit?

Expand Down Expand Up @@ -558,7 +572,9 @@ EOF
sandbox_valid_or_empty() {
sandbox_dir="$1"

if ! [ -d "$sandbox_dir/upperdir" ] && ! [ -d "$sandbox_dir/workdir" ] && ! [ -d "$sandbox_dir/temproot" ]
# Check if sandbox is fresh (no upperdir, no workdir-* dirs, no temproot)
# Note: We check for workdir-* pattern since each invocation creates a unique workdir
if ! [ -d "$sandbox_dir/upperdir" ] && ! ls "$sandbox_dir"/workdir-* >/dev/null 2>&1 && ! [ -d "$sandbox_dir/temproot" ]
then
# No sandbox directory exists so we can happily return
return 0
Expand Down
Loading