mirror of
https://github.com/containers/podman.git
synced 2025-06-20 17:13:43 +08:00
Merge pull request #20799 from edsantiago/symlink-mounts
[systests] podman mount no-dereference: complete rewrite
This commit is contained in:
@ -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
|
||||
|
Reference in New Issue
Block a user