diff --git a/docs/source/markdown/options/mount.md b/docs/source/markdown/options/mount.md index ada1fb8670..bf03d05099 100644 --- a/docs/source/markdown/options/mount.md +++ b/docs/source/markdown/options/mount.md @@ -100,6 +100,8 @@ Options specific to type=**tmpfs** and **ramfs**: - *tmpcopyup*: Enable copyup from the image directory at the same location to the tmpfs/ramfs. Used by default. +- *noatime*: Disable updating file access times when the file is read. + - *notmpcopyup*: Disable copying files from the image to the tmpfs/ramfs. - *U*, *chown*: *true* or *false* (default if unspecified: *false*). Recursively change the owner and group of the source volume based on the UID and GID of the container. diff --git a/pkg/util/mount_opts.go b/pkg/util/mount_opts.go index 4e37fd74a0..e48ed1cc0a 100644 --- a/pkg/util/mount_opts.go +++ b/pkg/util/mount_opts.go @@ -185,6 +185,10 @@ func processOptionsInternal(options []string, isTmpfs bool, sourcePath string, g return nil, fmt.Errorf("the 'U' option can only be set once: %w", ErrDupeMntOption) } foundU = true + case "noatime": + if !isTmpfs { + return nil, fmt.Errorf("the 'noatime' option is only allowed with tmpfs mounts: %w", ErrBadMntOption) + } default: return nil, fmt.Errorf("unknown mount option %q: %w", opt, ErrBadMntOption) } diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index 524f1f0250..797e481772 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -715,10 +715,10 @@ func TestProcessOptions(t *testing.T) { }{ { name: "tmpfs", - options: []string{"rw", "size=512m"}, + options: []string{"rw", "size=512m", "noatime"}, isTmpfs: true, sourcePath: "", - expected: []string{"nodev", "nosuid", "rprivate", "rw", "size=512m", "tmpcopyup"}, + expected: []string{"nodev", "nosuid", "rprivate", "rw", "size=512m", "tmpcopyup", "noatime"}, }, { name: "duplicate idmap option", @@ -810,6 +810,12 @@ func TestProcessOptions(t *testing.T) { options: []string{"bind"}, expected: []string{"nodev", "nosuid", "bind", "private"}, }, + { + name: "noatime allowed only with tmpfs", + sourcePath: "/path/to/source", + options: []string{"noatime"}, + expectErr: true, + }, } for _, tt := range tests { diff --git a/test/apiv2/44-mounts.at b/test/apiv2/44-mounts.at index d54669e7d0..a2efb935cb 100644 --- a/test/apiv2/44-mounts.at +++ b/test/apiv2/44-mounts.at @@ -8,7 +8,7 @@ t POST containers/create?name=hostconfig_test \ Image=$IMAGE \ Cmd='["df","-P","'$tmpfs_name'"]' \ HostConfig='{"Binds":["/tmp/doesnotexist:/test1"]' \ - TmpFs="{\"$tmpfs_name\":\"rw\"}}" \ + TmpFs="{\"$tmpfs_name\":\"rw,noatime\"}}" \ 201 \ .Id~[0-9a-f]\\{64\\} cid=$(jq -r '.Id' <<<"$output") @@ -29,3 +29,11 @@ t GET containers/${cid}/logs?stdout=true 200 # null bytes in the outfile. like "$(tr -d \\0 <$WORKDIR/curl.result.out)" ".* ${tmpfs_name}" \ "'df' output includes tmpfs name" + +# Reject 'noatime' for bind mount +t POST libpod/containers/create \ + Image=$IMAGE \ + Mounts='[{"type":"bind","source":"/nosuchdir","destination":"/data","options":["noatime"]}]' \ + 500 \ + .cause="invalid mount option" \ + .message~"the 'noatime' option is only allowed with tmpfs mounts" diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go index ae4625055c..b0020e1c67 100644 --- a/test/e2e/run_volume_test.go +++ b/test/e2e/run_volume_test.go @@ -1131,4 +1131,18 @@ RUN chmod 755 /test1 /test2 /test3`, ALPINE) outTest := podmanTest.PodmanExitCleanly("run", "--rm", "--mount", fmt.Sprintf("type=volume,src=%s,dest=/mnt", volName), ALPINE, "ls", "/mnt") Expect(outTest.OutputToString()).To(ContainSubstring("testfile")) }) + + It("podman run --tmpfs with noatime option", func() { + session := podmanTest.Podman([]string{"run", "--rm", "--tmpfs", "/mytmpfs:noatime", ALPINE, "grep", "mytmpfs", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + output := session.OutputToString() + Expect(output).To(ContainSubstring("noatime")) + + session = podmanTest.Podman([]string{"run", "--rm", "--tmpfs", "/mytmpfs", ALPINE, "grep", "mytmpfs", "/proc/self/mountinfo"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + output = session.OutputToString() + Expect(output).ToNot(ContainSubstring("noatime")) + }) })