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

[systests] podman mount no-dereference: complete rewrite #20799

Merged
merged 1 commit into from
Nov 29, 2023
Merged
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
199 changes: 149 additions & 50 deletions test/system/060-mount.bats
Original file line number Diff line number Diff line change
Expand Up @@ -330,73 +330,172 @@ EOF
skip "only crun supports the no-dereference (copy-symlink) mount option"
fi

# One directory for testing relative symlinks, another for absolute ones.
rel_dir=$PODMAN_TMPDIR/rel-dir
abs_dir=$PODMAN_TMPDIR/abs-dir
mkdir $rel_dir $abs_dir

# Create random values to discrimate data in the rel/abs directory and the
# one from the image.
rel_random_host="rel_on_the_host_$(random_string 15)"
abs_random_host="abs_on_the_host_$(random_string 15)"
random_img="on_the_image_$(random_string 15)"

# Relative symlink
echo "$rel_random_host" > $rel_dir/data
ln -r -s $rel_dir/data $rel_dir/link
# Absolute symlink
echo "$abs_random_host" > $abs_dir/data
ln -s $abs_dir/data $abs_dir/link
# Contents of the file 'data' inside the container image.
declare -A datacontent=(
[img]="data file inside the IMAGE - $(random_string 15)"
)

# Purpose of the image is so "link -> data" can point to an existing
# file whether or not "data" is mounted.
dockerfile=$PODMAN_TMPDIR/Dockerfile
cat >$dockerfile <<EOF
FROM $IMAGE
RUN echo $random_img > /tmp/data
RUN mkdir /mountroot && echo ${datacontent[img]} > /mountroot/data
EOF

img="localhost/preserve:symlinks"
run_podman build -t $img -f $dockerfile

link_path="/tmp/link"
create_path="/tmp/i/do/not/exist/link"

# Each test is set up in exactly the same way:
#
# <tmpdir>/
# ├── mountdir/ <----- this is always the source dir
# │ ├── data
# │ └── link -> ?????
# └── otherdir/
# └── data
#
# The test is run in a container that has its own /mountroot/data file,
# so in some situations 'link -> data' will get the container's
# data file, in others it'll be the host's, and in others, ENOENT.
#
# There are four options for 'link': -> data in mountdir (same dir)
# or otherdir, and, relative or absolute. Then, for each of those
# permutations, run with and without no-dereference. (With no-dereference,
# only the first of these options is valid, link->data. The other three
# appear in the container as link->path-not-in-container)
#
# Finally, the table below defines a number of variations of mount
# type (bind, glob); mount source (just the link, a glob, or entire
# directory); and mount destination. These are the variations that
# introduce complexity, hence the special cases in the innermost loop.
#
# Table format:
#
# mount type | mount source | mount destination | what_is_data | enoents
#
# The what_is_data column indicates whether the file "data" in the
# container will be the image's copy ("img") or the one from the host
# ("in", referring to the source directory). "-" means N/A, no data file.
#
# The enoent column is a space-separated list of patterns to search for
# in the test description. When these match, "link" will point to a
# path that does not exist in the directory, and we should expect cat
# to result in ENOENT.
#
tests="
0 | bind | $rel_dir/link | /tmp/link | | /tmp/link | $rel_random_host | $link_path | bind mount relative symlink: mounts target from the host
0 | bind | $abs_dir/link | /tmp/link | | /tmp/link | $abs_random_host | $link_path | bind mount absolute symlink: mounts target from the host
0 | glob | $rel_dir/lin* | /tmp/ | | /tmp/link | $rel_random_host | $link_path | glob mount relative symlink: mounts target from the host
0 | glob | $abs_dir/lin* | /tmp/ | | /tmp/link | $abs_random_host | $link_path | glob mount absolute symlink: mounts target from the host
0 | glob | $rel_dir/* | /tmp/ | | /tmp/link | $rel_random_host | $link_path | glob mount entire directory: mounts relative target from the host
0 | glob | $abs_dir/* | /tmp/ | | /tmp/link | $abs_random_host | $link_path | glob mount entire directory: mounts absolute target from the host
0 | bind | $rel_dir/link | /tmp/link | ,no-dereference | '/tmp/link' -> 'data' | $random_img | $link_path | no_deref: bind mount relative symlink: points to file on the image
0 | glob | $rel_dir/lin* | /tmp/ | ,no-dereference | '/tmp/link' -> 'data' | $random_img | $link_path | no_deref: glob mount relative symlink: points to file on the image
0 | bind | $rel_dir/ | /tmp/ | ,no-dereference | '/tmp/link' -> 'data' | $rel_random_host | $link_path | no_deref: bind mount the entire directory: preserves symlink automatically
0 | glob | $rel_dir/* | /tmp/ | ,no-dereference | '/tmp/link' -> 'data' | $rel_random_host | $link_path | no_deref: glob mount the entire directory: preserves symlink automatically
1 | bind | $abs_dir/link | /tmp/link | ,no-dereference | '/tmp/link' -> '$abs_dir/data' | cat: can't open '/tmp/link': No such file or directory | $link_path | bind mount *preserved* absolute symlink: now points to a non-existent file on the container
1 | glob | $abs_dir/lin* | /tmp/ | ,no-dereference | '/tmp/link' -> '$abs_dir/data' | cat: can't open '/tmp/link': No such file or directory | $link_path | glob mount *preserved* absolute symlink: now points to a non-existent file on the container
0 | bind | $rel_dir/link | $create_path | | $create_path | $rel_random_host | $create_path | bind mount relative symlink: creates dirs and mounts target from the host
1 | bind | $rel_dir/link | $create_path | ,no-dereference | '$create_path' -> 'data' | cat: can't open '$create_path': No such file or directory | $create_path | no_deref: bind mount relative symlink: creates dirs and mounts target from the host
bind | /link | /mountroot/link | img
bind | /link | /i/do/not/exist/link | - | relative.*no-dereference
bind | / | /mountroot/ | in | absolute out
glob | /lin* | /mountroot/ | img
glob | /* | /mountroot/ | in
"

while read exit_code mount_type mount_src mount_dst mount_opts line_0 line_1 path description; do
if [[ $mount_opts == "''" ]];then
unset mount_opts
fi
run_podman $exit_code run \
--mount type=$mount_type,src=$mount_src,dst=$mount_dst$mount_opts \
--rm --privileged $img sh -c "stat -c '%N' $path; cat $path"
assert "${lines[0]}" = "$line_0" "$description"
assert "${lines[1]}" = "$line_1" "$description"
defer-assertion-failures

while read mount_type mount_source mount_dest what_is_data enoents; do
# link pointing inside the same directory, or outside
for in_out in "in" "out"; do
# relative symlink or absolute
for rel_abs in "relative" "absolute"; do
# Generate fresh new content for each data file (the in & out ones)
datacontent[in]="data file in the SAME DIRECTORY - $(random_string 15)"
datacontent[out]="data file OUTSIDE the tree - $(random_string 15)"

# Populate data files in and out our tree
local condition="${rel_abs:0:3}-${in_out}"
local sourcedir="$PODMAN_TMPDIR/$condition"
rm -rf $sourcedir $PODMAN_TMPDIR/outside-the-tree
mkdir $sourcedir $PODMAN_TMPDIR/outside-the-tree
echo "${datacontent[in]}" > "$sourcedir/data"
echo "${datacontent[out]}" > "$PODMAN_TMPDIR/outside-the-tree/data"

# Create the symlink itself (in the in-dir of course)
local target
case "$condition" in
rel-in) target="data" ;;
rel-out) target="../outside-the-tree/data" ;;
abs-in) target="$sourcedir/data" ;;
abs-out) target="$PODMAN_TMPDIR/outside-the-tree/data" ;;
*) die "Internal error, invalid condition '$condition'" ;;
esac
ln -s $target "$sourcedir/link"

# Absolute path to 'link' inside the container. What we stat & cat.
local containerpath="$mount_dest"
if [[ ! $containerpath =~ /link$ ]]; then
containerpath="${containerpath}link"
fi

# Now test with no args (mounts link CONTENT) and --no-dereference
# (mounts symlink AS A SYMLINK)
for mount_opts in "" ",no-dereference"; do
local description="$mount_type mount $mount_source -> $mount_dest ($in_out), $rel_abs $mount_opts"

# Expected exit status. Almost always success.
local exit_code=0

# Without --no-dereference, we always expect exactly the same,
# because podman mounts "link" as a data file...
local expect_stat="$containerpath"
local expect_cat="${datacontent[$in_out]}"
# ...except when bind-mounting link's parent directory: "link"
# is mounted as a link, and host's "data" file overrides the image
if [[ $mount_source = '/' ]]; then
expect_stat="'$containerpath' -> '$target'"
fi

# With --no-dereference...
if [[ -n "$mount_opts" ]]; then
# stat() is always the same (symlink and its target) ....
expect_stat="'$containerpath' -> '$target'"

# ...and the only valid case for cat is same-dir relative:
if [[ "$condition" = "rel-in" ]]; then
expect_cat="${datacontent[$what_is_data]}"
else
# All others are ENOENT, because link -> nonexistent-path
exit_code=1
fi
fi

for ex in $enoents; do
if grep -q -w -E "$ex" <<<"$description"; then
exit_code=1
fi
done
if [[ $exit_code -eq 1 ]]; then
expect_cat="cat: can't open '$containerpath': No such file or directory"
fi

run_podman $exit_code run \
--mount type=$mount_type,src="$sourcedir$mount_source",dst="$mount_dest$mount_opts" \
--rm --privileged $img sh -c "stat -c '%N' $containerpath; cat $containerpath"
assert "${lines[0]}" = "$expect_stat" "$description -- stat $containerpath"
assert "${lines[1]}" = "$expect_cat" "$description -- cat $containerpath"
done
done
done
done < <(parse_table "$tests")

# Make sure that it's presvered across starts and stops
run_podman create --mount type=glob,src=$rel_dir/*,dst=/tmp/,no-dereference --privileged $img sh -c "stat -c '%N' /tmp/link; cat /tmp/link"
immediate-assertion-failures

# Make sure that links are preserved across starts and stops
local workdir=$PODMAN_TMPDIR/test-restart
mkdir $workdir
local datafile="data-$(random_string 5)"
local datafile_contents="What we expect to see, $(random_string 20)"
echo "$datafile_contents" > $workdir/$datafile
ln -s $datafile $workdir/link

run_podman create --mount type=glob,src=$workdir/*,dst=/mountroot/,no-dereference --privileged $img sh -c "stat -c '%N' /mountroot/link; cat /mountroot/link"
cid="$output"
run_podman start -a $cid
assert "${lines[0]}" = "'/tmp/link' -> 'data'" "symlink is preserved"
assert "${lines[1]}" = "$rel_random_host" "glob macthes symlink and host 'data' file"
assert "${lines[0]}" = "'/mountroot/link' -> '$datafile'" "symlink is preserved, on start"
assert "${lines[1]}" = "$datafile_contents" "glob matches symlink and host 'data' file, on start"
run_podman start -a $cid
assert "${lines[0]}" = "'/tmp/link' -> 'data'" "symlink is preserved"
assert "${lines[1]}" = "$rel_random_host" "glob macthes symlink and host 'data' file"
assert "${lines[0]}" = "'/mountroot/link' -> '$datafile'" "symlink is preserved, on restart"
assert "${lines[1]}" = "$datafile_contents" "glob matches symlink and host 'data' file, on restart"
run_podman rm -f -t=0 $cid

run_podman rmi -f $img
Expand Down