github.com/containers/podman/v5@v5.1.0-rc1/test/system/060-mount.bats (about) 1 #!/usr/bin/env bats 2 3 load helpers 4 5 @test "podman mount - basic test" { 6 # Only works with root (FIXME: does it work with rootless + vfs?) 7 skip_if_rootless "mount does not work rootless" 8 skip_if_remote "mounting remote is meaningless" 9 10 f_path=/tmp/tmpfile_$(random_string 8) 11 f_content=$(random_string 30) 12 13 c_name=mount_test_$(random_string 5) 14 run_podman run --name $c_name $IMAGE \ 15 sh -c "echo $f_content > $f_path" 16 17 run_podman mount $c_name 18 mount_path=$output 19 20 test -d $mount_path 21 test -e "$mount_path/$f_path" 22 is $(< "$mount_path/$f_path") "$f_content" "contents of file, as read via fs" 23 24 # Make sure that 'podman mount' (no args) returns the expected path 25 run_podman mount --notruncate 26 # FIXME: is it worth the effort to validate the CID ($1) ? 27 reported_mountpoint=$(echo "$output" | awk '{print $2}') 28 is "$reported_mountpoint" "$mount_path" "mountpoint reported by 'podman mount'" 29 30 # umount, and make sure files are gone 31 run_podman umount $c_name 32 if [[ -e "$mount_path/$f_path" ]]; then 33 # With vfs, umount is a NOP: the path always exists as long as the 34 # container exists. But with overlay, umount should truly remove. 35 if [[ "$(podman_storage_driver)" != "vfs" ]]; then 36 die "Mounted file exists even after umount: $mount_path/$f_path" 37 fi 38 fi 39 40 # Remove the container. Now even with vfs the file must be gone. 41 run_podman rm $c_name 42 if [[ -e "$mount_path/$f_path" ]]; then 43 die "Mounted file exists even after container rm: $mount_path/$f_path" 44 fi 45 } 46 47 48 @test "podman image mount" { 49 skip_if_remote "mounting remote is meaningless" 50 skip_if_rootless "too hard to test rootless" 51 52 # Start with clean slate 53 run_podman image umount -a 54 55 # Get full image ID, to verify umount 56 run_podman image inspect --format '{{.ID}}' $IMAGE 57 iid="$output" 58 59 # Mount, and make sure the mount point exists 60 run_podman image mount $IMAGE 61 mount_path="$output" 62 63 test -d $mount_path 64 65 # Image is custom-built and has a file containing the YMD tag. Check it. 66 testimage_file="/home/podman/testimage-id" 67 test -e "$mount_path$testimage_file" 68 is $(< "$mount_path$testimage_file") "$PODMAN_TEST_IMAGE_TAG" \ 69 "Contents of $testimage_file in image" 70 71 # 'image mount', no args, tells us what's mounted 72 run_podman image mount 73 is "$output" "$IMAGE *$mount_path" "podman image mount with no args" 74 75 # Clean up, and make sure nothing is mounted any more 76 run_podman image umount -f $IMAGE 77 is "$output" "$iid" "podman image umount: image ID of what was umounted" 78 79 run_podman image umount $IMAGE 80 is "$output" "" "podman image umount: does not re-umount" 81 82 run_podman 125 image umount no-such-image 83 is "$output" "Error: no-such-image: image not known" \ 84 "error message from image umount no-such-image" 85 86 # Tests for mount -a. This may mount more than one image! (E.g. systemd) 87 run_podman image mount -a 88 is "$output" "$IMAGE .*$mount_path" 89 90 run_podman image umount -a 91 assert "$output" =~ "$iid" "Test image is unmounted" 92 93 run_podman image mount 94 is "$output" "" "podman image mount, no args, after umount" 95 } 96 97 @test "podman run --mount ro=false " { 98 local volpath=/path/in/container 99 local stdopts="type=volume,destination=$volpath" 100 101 # Variations on a theme (not by Paganini). All of these should fail. 102 for varopt in readonly readonly=true ro=true ro rw=false;do 103 run_podman 1 run --rm -q --mount $stdopts,$varopt $IMAGE touch $volpath/a 104 is "$output" "touch: $volpath/a: Read-only file system" "with $varopt" 105 done 106 107 # All of these should pass 108 for varopt in rw rw=true ro=false readonly=false;do 109 run_podman run --rm -q --mount $stdopts,$varopt $IMAGE touch $volpath/a 110 done 111 } 112 113 @test "podman run --mount image" { 114 skip_if_rootless "too hard to test rootless" 115 116 # Run a container with an image mount 117 run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount $IMAGE diff /etc/os-release /image-mount/etc/os-release 118 119 # Make sure the mount is read-only 120 run_podman 1 run --rm --mount type=image,src=$IMAGE,dst=/image-mount $IMAGE touch /image-mount/read-only 121 is "$output" "touch: /image-mount/read-only: Read-only file system" 122 123 # Make sure that rw,readwrite work 124 run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount,rw=true $IMAGE touch /image-mount/readwrite 125 run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount,readwrite=true $IMAGE touch /image-mount/readwrite 126 127 skip_if_remote "mounting remote is meaningless" 128 129 # The mount should be cleaned up during container removal as no other entity mounted the image 130 run_podman image umount $IMAGE 131 is "$output" "" "image mount should have been cleaned up during container removal" 132 133 # Now make sure that the image mount is not cleaned up during container removal when another entity mounted the image 134 run_podman image mount $IMAGE 135 run_podman run --rm --mount type=image,src=$IMAGE,dst=/image-mount $IMAGE diff /etc/os-release /image-mount/etc/os-release 136 137 run_podman image inspect --format '{{.ID}}' $IMAGE 138 iid="$output" 139 140 run_podman image umount $IMAGE 141 is "$output" "$iid" "podman image umount: image ID of what was umounted" 142 143 run_podman image umount $IMAGE 144 is "$output" "" "image mount should have been cleaned up via 'image umount'" 145 146 # Run a container in the background (source is the ID instead of name) 147 run_podman run -d --mount type=image,src=$iid,dst=/image-mount,readwrite=true $IMAGE sleep infinity 148 cid="$output" 149 150 # Unmount the image 151 run_podman image umount $IMAGE 152 is "$output" "$iid" "podman image umount: image ID of what was umounted" 153 run_podman image umount $IMAGE 154 is "$output" "" "image mount should have been cleaned up via 'image umount'" 155 156 # Make sure that the mount in the container is unaffected 157 run_podman exec $cid diff /etc/os-release /image-mount/etc/os-release 158 run_podman exec $cid find /image-mount/etc/ 159 160 # Clean up 161 run_podman rm -t 0 -f $cid 162 } 163 164 @test "podman run --mount image inspection" { 165 skip_if_rootless "too hard to test rootless" 166 167 # Run a container in the background 168 run_podman run -d --mount type=image,src=$IMAGE,dst=/image-mount,rw=true $IMAGE sleep infinity 169 cid="$output" 170 171 run_podman inspect --format "{{(index .Mounts 0).Type}}" $cid 172 is "$output" "image" "inspect data includes image mount type" 173 174 run_podman inspect --format "{{(index .Mounts 0).Source}}" $cid 175 is "$output" "$IMAGE" "inspect data includes image mount source" 176 177 run_podman inspect --format "{{(index .Mounts 0).Destination}}" $cid 178 is "$output" "/image-mount" "inspect data includes image mount source" 179 180 run_podman inspect --format "{{(index .Mounts 0).RW}}" $cid 181 is "$output" "true" "inspect data includes image mount source" 182 183 run_podman rm -t 0 -f $cid 184 } 185 186 @test "podman mount containers.conf" { 187 skip_if_remote "remote does not support CONTAINERS_CONF*" 188 189 dest=/$(random_string 30) 190 tmpfile1=$PODMAN_TMPDIR/volume-test1 191 random1=$(random_string 30) 192 echo $random1 > $tmpfile1 193 194 tmpfile2=$PODMAN_TMPDIR/volume-test2 195 random2=$(random_string 30) 196 echo $random2 > $tmpfile2 197 bogus=$(random_string 10) 198 199 mountStr1=type=bind,src=$tmpfile1,destination=$dest,ro,Z 200 mountStr2=type=bind,src=$tmpfile2,destination=$dest,ro,Z 201 containersconf=$PODMAN_TMPDIR/containers.conf 202 cat >$containersconf <<EOF 203 [containers] 204 mounts=[ "$mountStr1", ] 205 EOF 206 badcontainersconf=$PODMAN_TMPDIR/badcontainers.conf 207 cat >$badcontainersconf <<EOF 208 [containers] 209 mounts=[ "type=$bogus,src=$tmpfile2,destination=$dest,ro", ] 210 EOF 211 212 run_podman 1 run $IMAGE cat $dest 213 is "$output" "cat: can't open '$dest': No such file or directory" "$dest does not exist" 214 215 CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman run $IMAGE cat $dest 216 is "$output" "$random1" "file should contain $random1" 217 218 CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman run --mount $mountStr2 $IMAGE cat $dest 219 is "$output" "$random2" "overridden file should contain $random2" 220 221 CONTAINERS_CONF_OVERRIDE="$containersconf" run_podman 125 run --mount $mountStr1 --mount $mountStr2 $IMAGE cat $dest 222 is "$output" "Error: $dest: duplicate mount destination" "Should through duplicate destination error for $dest" 223 224 CONTAINERS_CONF_OVERRIDE="$badcontainersconf" run_podman 125 run $IMAGE cat $dest 225 is "$output" "Error: parsing containers.conf mounts: invalid filesystem type \"$bogus\"" "containers.conf should fail with bad mounts entry" 226 227 run_podman rm --all --force -t 0 228 } 229 230 @test "podman mount external container - basic test" { 231 # Only works with root (FIXME: does it work with rootless + vfs?) 232 skip_if_rootless "mount does not work rootless" 233 skip_if_remote "mounting remote is meaningless" 234 235 # Create a container that podman does not know about 236 external_cid=$(buildah from $IMAGE) 237 238 run_podman mount $external_cid 239 mount_path=$output 240 241 # Test image will always have this file, and will always have the tag 242 test -d $mount_path 243 is $(< "$mount_path/home/podman/testimage-id") "$PODMAN_TEST_IMAGE_TAG" \ 244 "Contents of well-known file in image" 245 246 # Make sure that 'podman mount' (no args) returns the expected path 247 run_podman mount --notruncate 248 249 reported_mountpoint=$(echo "$output" | awk '{print $2}') 250 is "$reported_mountpoint" "$mount_path" "mountpoint reported by 'podman mount'" 251 252 # umount, and make sure mountpoint no longer exists 253 run_podman umount $external_cid 254 if findmnt "$mount_path" >/dev/null ; then 255 die "'podman umount' did not umount $mount_path" 256 fi 257 buildah rm $external_cid 258 } 259 260 @test "podman volume globs" { 261 v1a=v1_$(random_string) 262 v1b=v1_$(random_string) 263 v2=v2_$(random_string) 264 vol1a=${PODMAN_TMPDIR}/$v1a 265 vol1b=${PODMAN_TMPDIR}/$v1b 266 vol2=${PODMAN_TMPDIR}/$v2 267 touch $vol1a $vol1b $vol2 268 269 # if volumes source and dest match then pass 270 run_podman run --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,ro $IMAGE ls $vol1a $vol1b 271 run_podman 1 run --rm --mount source=${PODMAN_TMPDIR}/v1\*,type=glob,ro $IMAGE ls $vol2 272 is "$output" ".*No such file or directory" "$vol2 should not be mounted in the container" 273 274 run_podman 125 run --rm --mount source=${PODMAN_TMPDIR}/v3\*,type=glob,ro $IMAGE ls $vol2 275 is "$output" "Error: no file paths matching glob \"${PODMAN_TMPDIR}/v3\*\"" "Glob does not match so should throw error" 276 277 run_podman 1 run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=glob,ro,Z $IMAGE touch $vol2 278 is "$output" "touch: $vol2: Read-only file system" "Mount should be read-only" 279 280 run_podman run --rm --mount source=${PODMAN_TMPDIR}/v2\*,type=glob,ro=false,Z $IMAGE touch $vol2 281 282 run_podman run --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,destination=/non/existing/directory,ro $IMAGE ls /non/existing/directory 283 is "$output" ".*$v1a" "podman images --inspect should include $v1a" 284 is "$output" ".*$v1b" "podman images --inspect should include $v1b" 285 286 run_podman create --rm --mount type=glob,src=${PODMAN_TMPDIR}/v1\*,ro $IMAGE ls $vol1a $vol1b 287 cid=$output 288 run_podman container inspect $output 289 is "$output" ".*$vol1a" "podman images --inspect should include $vol1a" 290 is "$output" ".*$vol1b" "podman images --inspect should include $vol1b" 291 292 run_podman 125 run --rm --mount type=bind,source=${PODMAN_TMPDIR}/v2\*,ro=false $IMAGE touch $vol2 293 is "$output" "Error: must set volume destination" "Bind mounts require destination" 294 295 run_podman 125 run --rm --mount type=bind,source=${PODMAN_TMPDIR}/v2\*,destination=/tmp/foobar,ro=false $IMAGE touch $vol2 296 is "$output" "Error: statfs ${PODMAN_TMPDIR}/v2*: no such file or directory" "Bind mount should not interpret glob and must use as is" 297 298 mkdir $PODMAN_TMPDIR/foo1 $PODMAN_TMPDIR/foo2 $PODMAN_TMPDIR/foo3 299 touch $PODMAN_TMPDIR/foo1/bar $PODMAN_TMPDIR/foo2/bar $PODMAN_TMPDIR/foo3/bar 300 touch $PODMAN_TMPDIR/foo1/bar1 $PODMAN_TMPDIR/foo2/bar2 $PODMAN_TMPDIR/foo3/bar3 301 run_podman 125 run --rm --mount type=glob,source=${PODMAN_TMPDIR}/foo?/bar,destination=/tmp $IMAGE ls -l /tmp 302 is "$output" "Error: /tmp/bar: duplicate mount destination" "Should report conflict on destination directory" 303 run_podman run --rm --mount type=glob,source=${PODMAN_TMPDIR}/foo?/bar?,destination=/tmp,ro $IMAGE ls /tmp 304 is "$output" "bar1.*bar2.*bar3" "Should match multiple source files on single destination directory" 305 } 306 307 @test "podman mount noswap memory mounts" { 308 # tmpfs+noswap new in kernel 6.x, mid-2023; likely not in RHEL for a while 309 if ! is_rootless; then 310 testmount=$PODMAN_TMPDIR/testmount 311 mkdir $testmount 312 run mount -t tmpfs -o noswap none $testmount 313 if [[ $status -ne 0 ]]; then 314 if [[ $output =~ "bad option" ]]; then 315 skip "requires kernel with tmpfs + noswap support" 316 fi 317 die "Could not test for tmpfs + noswap support: $output" 318 else 319 umount $testmount 320 fi 321 fi 322 323 # if volumes source and dest match then pass 324 run_podman run --rm --mount type=ramfs,destination=${PODMAN_TMPDIR} $IMAGE stat -f -c "%T" ${PODMAN_TMPDIR} 325 is "$output" "ramfs" "ramfs mounted" 326 327 if is_rootless; then 328 run_podman 125 run --rm --mount type=tmpfs,destination=${PODMAN_TMPDIR},noswap $IMAGE stat -f -c "%T" ${PODMAN_TMPDIR} 329 is "$output" "Error: the 'noswap' option is only allowed with rootful tmpfs mounts: must provide an argument for option" "noswap not supported in rootless mode" 330 else 331 run_podman run --rm --mount type=tmpfs,destination=${PODMAN_TMPDIR},noswap $IMAGE sh -c "mount| grep ${PODMAN_TMPDIR}" 332 is "$output" ".*noswap" "tmpfs noswap mounted" 333 fi 334 } 335 336 @test "podman mount no-dereference" { 337 # Test how bind and glob-mounts behave with respect to relative (rel) and 338 # absolute (abs) symlinks. 339 340 if [ $(podman_runtime) != "crun" ]; then 341 # Requires crun >= 1.11.0 342 skip "only crun supports the no-dereference (copy-symlink) mount option" 343 fi 344 345 # Contents of the file 'data' inside the container image. 346 declare -A datacontent=( 347 [img]="data file inside the IMAGE - $(random_string 15)" 348 ) 349 350 # Purpose of the image is so "link -> data" can point to an existing 351 # file whether or not "data" is mounted. 352 dockerfile=$PODMAN_TMPDIR/Dockerfile 353 cat >$dockerfile <<EOF 354 FROM $IMAGE 355 RUN mkdir /mountroot && echo ${datacontent[img]} > /mountroot/data 356 EOF 357 358 img="localhost/preserve:symlinks" 359 run_podman build -t $img -f $dockerfile 360 361 # Each test is set up in exactly the same way: 362 # 363 # <tmpdir>/ 364 # ├── mountdir/ <----- this is always the source dir 365 # │ ├── data 366 # │ └── link -> ????? 367 # └── otherdir/ 368 # └── data 369 # 370 # The test is run in a container that has its own /mountroot/data file, 371 # so in some situations 'link -> data' will get the container's 372 # data file, in others it'll be the host's, and in others, ENOENT. 373 # 374 # There are four options for 'link': -> data in mountdir (same dir) 375 # or otherdir, and, relative or absolute. Then, for each of those 376 # permutations, run with and without no-dereference. (With no-dereference, 377 # only the first of these options is valid, link->data. The other three 378 # appear in the container as link->path-not-in-container) 379 # 380 # Finally, the table below defines a number of variations of mount 381 # type (bind, glob); mount source (just the link, a glob, or entire 382 # directory); and mount destination. These are the variations that 383 # introduce complexity, hence the special cases in the innermost loop. 384 # 385 # Table format: 386 # 387 # mount type | mount source | mount destination | what_is_data | enoents 388 # 389 # The what_is_data column indicates whether the file "data" in the 390 # container will be the image's copy ("img") or the one from the host 391 # ("in", referring to the source directory). "-" means N/A, no data file. 392 # 393 # The enoent column is a space-separated list of patterns to search for 394 # in the test description. When these match, "link" will point to a 395 # path that does not exist in the directory, and we should expect cat 396 # to result in ENOENT. 397 # 398 tests=" 399 bind | /link | /mountroot/link | img 400 bind | /link | /i/do/not/exist/link | - | relative.*no-dereference 401 bind | / | /mountroot/ | in | absolute out 402 glob | /lin* | /mountroot/ | img 403 glob | /* | /mountroot/ | in 404 " 405 406 defer-assertion-failures 407 408 while read mount_type mount_source mount_dest what_is_data enoents; do 409 # link pointing inside the same directory, or outside 410 for in_out in "in" "out"; do 411 # relative symlink or absolute 412 for rel_abs in "relative" "absolute"; do 413 # Generate fresh new content for each data file (the in & out ones) 414 datacontent[in]="data file in the SAME DIRECTORY - $(random_string 15)" 415 datacontent[out]="data file OUTSIDE the tree - $(random_string 15)" 416 417 # Populate data files in and out our tree 418 local condition="${rel_abs:0:3}-${in_out}" 419 local sourcedir="$PODMAN_TMPDIR/$condition" 420 rm -rf $sourcedir $PODMAN_TMPDIR/outside-the-tree 421 mkdir $sourcedir $PODMAN_TMPDIR/outside-the-tree 422 echo "${datacontent[in]}" > "$sourcedir/data" 423 echo "${datacontent[out]}" > "$PODMAN_TMPDIR/outside-the-tree/data" 424 425 # Create the symlink itself (in the in-dir of course) 426 local target 427 case "$condition" in 428 rel-in) target="data" ;; 429 rel-out) target="../outside-the-tree/data" ;; 430 abs-in) target="$sourcedir/data" ;; 431 abs-out) target="$PODMAN_TMPDIR/outside-the-tree/data" ;; 432 *) die "Internal error, invalid condition '$condition'" ;; 433 esac 434 ln -s $target "$sourcedir/link" 435 436 # Absolute path to 'link' inside the container. What we stat & cat. 437 local containerpath="$mount_dest" 438 if [[ ! $containerpath =~ /link$ ]]; then 439 containerpath="${containerpath}link" 440 fi 441 442 # Now test with no args (mounts link CONTENT) and --no-dereference 443 # (mounts symlink AS A SYMLINK) 444 for mount_opts in "" ",no-dereference"; do 445 local description="$mount_type mount $mount_source -> $mount_dest ($in_out), $rel_abs $mount_opts" 446 447 # Expected exit status. Almost always success. 448 local exit_code=0 449 450 # Without --no-dereference, we always expect exactly the same, 451 # because podman mounts "link" as a data file... 452 local expect_stat="$containerpath" 453 local expect_cat="${datacontent[$in_out]}" 454 # ...except when bind-mounting link's parent directory: "link" 455 # is mounted as a link, and host's "data" file overrides the image 456 if [[ $mount_source = '/' ]]; then 457 expect_stat="'$containerpath' -> '$target'" 458 fi 459 460 # With --no-dereference... 461 if [[ -n "$mount_opts" ]]; then 462 # stat() is always the same (symlink and its target) .... 463 expect_stat="'$containerpath' -> '$target'" 464 465 # ...and the only valid case for cat is same-dir relative: 466 if [[ "$condition" = "rel-in" ]]; then 467 expect_cat="${datacontent[$what_is_data]}" 468 else 469 # All others are ENOENT, because link -> nonexistent-path 470 exit_code=1 471 fi 472 fi 473 474 for ex in $enoents; do 475 if grep -q -w -E "$ex" <<<"$description"; then 476 exit_code=1 477 fi 478 done 479 if [[ $exit_code -eq 1 ]]; then 480 expect_cat="cat: can't open '$containerpath': No such file or directory" 481 fi 482 483 run_podman $exit_code run \ 484 --mount type=$mount_type,src="$sourcedir$mount_source",dst="$mount_dest$mount_opts" \ 485 --rm --privileged $img sh -c "stat -c '%N' $containerpath; cat $containerpath" 486 assert "${lines[0]}" = "$expect_stat" "$description -- stat $containerpath" 487 assert "${lines[1]}" = "$expect_cat" "$description -- cat $containerpath" 488 done 489 done 490 done 491 done < <(parse_table "$tests") 492 493 immediate-assertion-failures 494 495 # Make sure that links are preserved across starts and stops 496 local workdir=$PODMAN_TMPDIR/test-restart 497 mkdir $workdir 498 local datafile="data-$(random_string 5)" 499 local datafile_contents="What we expect to see, $(random_string 20)" 500 echo "$datafile_contents" > $workdir/$datafile 501 ln -s $datafile $workdir/link 502 503 run_podman create --mount type=glob,src=$workdir/*,dst=/mountroot/,no-dereference --privileged $img sh -c "stat -c '%N' /mountroot/link; cat /mountroot/link" 504 cid="$output" 505 run_podman start -a $cid 506 assert "${lines[0]}" = "'/mountroot/link' -> '$datafile'" "symlink is preserved, on start" 507 assert "${lines[1]}" = "$datafile_contents" "glob matches symlink and host 'data' file, on start" 508 run_podman start -a $cid 509 assert "${lines[0]}" = "'/mountroot/link' -> '$datafile'" "symlink is preserved, on restart" 510 assert "${lines[1]}" = "$datafile_contents" "glob matches symlink and host 'data' file, on restart" 511 run_podman rm -f -t=0 $cid 512 513 run_podman rmi -f $img 514 }