From 4e5e9dbec2313b07a4c10ddfd5bc7d23e3fa34f6 Mon Sep 17 00:00:00 2001
From: Giuseppe Scrivano <gscrivan@redhat.com>
Date: Wed, 6 Nov 2019 21:36:18 +0100
Subject: [PATCH] mount: add new options nocopyup|copyup for tmpfs

add a way to disable tmpcopyup for tmpfs.

Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
---
 docs/source/markdown/podman-create.1.md |  4 ++++
 docs/source/markdown/podman-run.1.md    |  4 ++++
 pkg/spec/storage.go                     |  8 ++++++-
 pkg/util/mountOpts.go                   | 31 ++++++++++++++++++-------
 test/e2e/run_volume_test.go             | 22 ++++++++++++++++++
 5 files changed, 59 insertions(+), 10 deletions(-)

diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index 2e0dd934c8..7f62432981 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -500,6 +500,10 @@ Current supported mount TYPES are `bind`, `volume`, and `tmpfs`.
 
               · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux.
 
+              · tmpcopyup: Enable copyup from the image directory at the same location to the tmpfs.  Used by default.
+
+              · notmpcopyup: Disable copying files from the image to the tmpfs.
+
 **--name**=*name*
 
 Assign a name to the container
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 8baa39570e..fc66d1b025 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -511,6 +511,10 @@ Current supported mount TYPES are `bind`, `volume`, and `tmpfs`.
 
 	      · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux.
 
+	      · tmpcopyup: Enable copyup from the image directory at the same location to the tmpfs.  Used by default.
+
+	      · notmpcopyup: Disable copying files from the image to the tmpfs.
+
 **--name**=*name*
 
 Assign a name to the container
diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go
index 0955345896..e30bdfc67d 100644
--- a/pkg/spec/storage.go
+++ b/pkg/spec/storage.go
@@ -514,11 +514,17 @@ func getTmpfsMount(args []string) (spec.Mount, error) {
 		Source: TypeTmpfs,
 	}
 
-	var setDest, setRORW, setSuid, setDev, setExec bool
+	var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool
 
 	for _, val := range args {
 		kv := strings.Split(val, "=")
 		switch kv[0] {
+		case "tmpcopyup", "notmpcopyup":
+			if setTmpcopyup {
+				return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once")
+			}
+			setTmpcopyup = true
+			newMount.Options = append(newMount.Options, kv[0])
 		case "ro", "rw":
 			if setRORW {
 				return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go
index 670daeaf93..d21800bc37 100644
--- a/pkg/util/mountOpts.go
+++ b/pkg/util/mountOpts.go
@@ -30,6 +30,8 @@ func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOption
 		foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ bool
 	)
 
+	var newOptions []string
+
 	for _, opt := range options {
 		// Some options have parameters - size, mode
 		splitOpt := strings.SplitN(opt, "=", 2)
@@ -80,9 +82,19 @@ func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOption
 				return nil, errors.Wrapf(ErrBadMntOption, "the 'tmpcopyup' option is only allowed with tmpfs mounts")
 			}
 			if foundCopyUp {
-				return nil, errors.Wrapf(ErrDupeMntOption, "the 'tmpcopyup' option can only be set once")
+				return nil, errors.Wrapf(ErrDupeMntOption, "the 'tmpcopyup' or 'notmpcopyup' option can only be set once")
 			}
 			foundCopyUp = true
+		case "notmpcopyup":
+			if !isTmpfs {
+				return nil, errors.Wrapf(ErrBadMntOption, "the 'notmpcopyup' option is only allowed with tmpfs mounts")
+			}
+			if foundCopyUp {
+				return nil, errors.Wrapf(ErrDupeMntOption, "the 'tmpcopyup' or 'notmpcopyup' option can only be set once")
+			}
+			foundCopyUp = true
+			// do not propagate notmpcopyup to the OCI runtime
+			continue
 		case "bind", "rbind":
 			if isTmpfs {
 				return nil, errors.Wrapf(ErrBadMntOption, "the 'bind' and 'rbind' options are not allowed with tmpfs mounts")
@@ -101,29 +113,30 @@ func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOption
 		default:
 			return nil, errors.Wrapf(ErrBadMntOption, "unknown mount option %q", opt)
 		}
+		newOptions = append(newOptions, opt)
 	}
 
 	if !foundWrite {
-		options = append(options, "rw")
+		newOptions = append(newOptions, "rw")
 	}
 	if !foundProp {
-		options = append(options, "rprivate")
+		newOptions = append(newOptions, "rprivate")
 	}
 	if !foundExec && (defaults == nil || defaults.Noexec) {
-		options = append(options, "noexec")
+		newOptions = append(newOptions, "noexec")
 	}
 	if !foundSuid && (defaults == nil || defaults.Nosuid) {
-		options = append(options, "nosuid")
+		newOptions = append(newOptions, "nosuid")
 	}
 	if !foundDev && (defaults == nil || defaults.Nodev) {
-		options = append(options, "nodev")
+		newOptions = append(newOptions, "nodev")
 	}
 	if isTmpfs && !foundCopyUp {
-		options = append(options, "tmpcopyup")
+		newOptions = append(newOptions, "tmpcopyup")
 	}
 	if !isTmpfs && !foundBind {
-		options = append(options, "rbind")
+		newOptions = append(newOptions, "rbind")
 	}
 
-	return options, nil
+	return newOptions, nil
 }
diff --git a/test/e2e/run_volume_test.go b/test/e2e/run_volume_test.go
index 8e5de85e4e..0c2389e40c 100644
--- a/test/e2e/run_volume_test.go
+++ b/test/e2e/run_volume_test.go
@@ -116,6 +116,28 @@ var _ = Describe("Podman run with volumes", func() {
 		session.WaitWithDefaultTimeout()
 		Expect(session.ExitCode()).To(Equal(0))
 		Expect(session.OutputToString()).To(ContainSubstring("/run/test rw,nosuid,nodev,noexec,relatime - tmpfs"))
+
+		session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=/etc/ssl,tmpcopyup", ALPINE, "ls", "/etc/ssl"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(session.OutputToString()).To(ContainSubstring("certs"))
+
+		session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=/etc/ssl,tmpcopyup,notmpcopyup", ALPINE, "ls", "/etc/ssl"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Not(Equal(0)))
+
+		session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=bind,src=/tmp,target=/tmp,tmpcopyup", ALPINE, "true"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Not(Equal(0)))
+
+		session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=bind,src=/tmp,target=/tmp,notmpcopyup", ALPINE, "true"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Not(Equal(0)))
+
+		session = podmanTest.Podman([]string{"run", "--rm", "--mount", "type=tmpfs,target=/etc/ssl,notmpcopyup", ALPINE, "ls", "/etc/ssl"})
+		session.WaitWithDefaultTimeout()
+		Expect(session.ExitCode()).To(Equal(0))
+		Expect(session.OutputToString()).To(Not(ContainSubstring("certs")))
 	})
 
 	It("podman run with conflicting volumes errors", func() {