Repository: opencontainers/runc
Pull request #2256 (2020-03-14 02:44:36)

Use faster mountinfo parser (part 1)
This is **part 1** of improving runc wrt /proc/self/mountinfo parsing (and an alternative to https://github.com/opencontainers/runc/pull/2255).

It needs improvements because:
 * the current code has 5 different implementations of runc parser
 * a single `runc run` command reads mountinfo from from 72 to 116 times (depending on whether `--systemd-cgroup` is set) on my system.
 * reading mountinfo from the kernel might be slow if there are many containers running (and thus many mounts)
 * reading mountinfo might also be racy (see https://github.com/kolyshkin/procfs-test)

This PR
* removes the old `libcontainer/mount` mountinfo parser (which was taken from Docker in Nov 2017)
* makes use of moby/sys/mountinfo package which evolves from the same code as above but is fast (for background on improvements, see moby/moby#36091, moby/moby#38977, moby/moby/pull/40684)
 * converts existing users to benefit from the above.

The new parser itself is up to 8x faster than the one removed; in addition, it implements mountinfo filters that may result in additional savings (faster, less garbage to collect).

### History
* v2: switch to golang 1.13
* v3: switch to go modules (after  #2073 is merged)

My future plans (**not in this PR**) are
 - [ ] part 2: make other runc code that parses mountinfo on its own to use the above package
 - [ ] part 3: look into cgroups code, trying to minimize calls to parse mountinfo

kolyshkin: This is failing because we still use golang 1.12 :(
kolyshkin: Added a temporary commit switching to golang 1.13. Ultimately, we need to merge https://github.com/opencontainers/runc/pull/2234 and https://github.com/opencontainers/runc/pull/2239
kolyshkin: > Please roll back to go 1.13

done
kolyshkin: @opencontainers/runc-maintainers PTAL
kolyshkin: IDK what's going on on travis, it doesn't like `oldstable` as go version. Works fine on my laptop though, and same gimme version.

My laptop:
```
$ gimme -r oldstable
1.13.8
$ gimme -V
v1.5.3
```

Travis:
```
gimme version
v1.5.3
...
gimme: version specifier 'oldstable' unknown
error: GIMME_GO_VERSION not recognized as valid
  got: oldstable
Failed to run gimme
The command "travis_setup_go" failed and exited with 86 during .
```

Anyway I'll just use 1.13 and 1.14
kolyshkin: Some glitch in CI, rebasing to re-trigger it

> gzip: stdin: not in gzip format
> 1452tar: Child returned status 1
> 1453tar: Error is not recoverable: exiting now
> 1454The command '/bin/sh -c mkdir -p /usr/src/criu     && curl -sSL https://github.com/checkpoint-restore/criu/archive/${CRIU_VERSION}.tar.gz | tar -v -C /usr/src/criu/ -xz --strip-components=1     && cd /usr/src/criu     && make install-criu     && rm -rf /usr/src/criu' returned a non-zero code: 2
kolyshkin: CI is good now, was a glitch

Patch:
diff --git a/.travis.yml b/.travis.yml
index 829823b1..b137f75e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,25 +1,24 @@
 dist: bionic
 language: go
 go:
-  - 1.12.x
+  - 1.14.x
   - 1.13.x
-  - 1.x
   - tip
 
 matrix:
   include:
-    - go: 1.x
+    - go: 1.13.x
       name: "verify-dependencies"
       script:
         - make verify-dependencies
-    - go: 1.12.x
+    - go: 1.13.x
       name: "cgroup-systemd"
       env:
         - RUNC_USE_SYSTEMD=1
       script:
         - make BUILDTAGS="${BUILDTAGS}" all
         - sudo PATH="$PATH" make localintegration RUNC_USE_SYSTEMD=1
-    - go: 1.12.x
+    - go: 1.13.x
       name: "cgroup-v2"
       env:
         - VIRTUALBOX_VERSION=6.0
diff --git a/Dockerfile b/Dockerfile
index 5c654705..83107cdb 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.12-stretch
+FROM golang:1.13-stretch
 
 RUN dpkg --add-architecture armel \
     && dpkg --add-architecture armhf \
diff --git a/go.mod b/go.mod
index 42bbe6cb..6bdc26c0 100644
--- a/go.mod
+++ b/go.mod
@@ -11,6 +11,7 @@ require (
 	github.com/docker/go-units v0.3.3
 	github.com/godbus/dbus v0.0.0-20181101234600-2ff6f7ffd60f
 	github.com/golang/protobuf v1.0.0
+	github.com/moby/sys/mountinfo v0.1.0
 	github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618
 	github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559
 	github.com/opencontainers/selinux v1.3.3
diff --git a/go.sum b/go.sum
index 2485b2bc..1ca8e203 100644
--- a/go.sum
+++ b/go.sum
@@ -18,6 +18,8 @@ github.com/golang/protobuf v1.0.0 h1:lsek0oXi8iFE9L+EXARyHIjU5rlWIhhTkjDz3vHhWWQ
 github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/moby/sys/mountinfo v0.1.0 h1:r8vMRbMAFEAfiNptYVokP+nfxPJzvRuia5e2vzXtENo=
+github.com/moby/sys/mountinfo v0.1.0/go.mod h1:w2t2Avltqx8vE7gX5l+QiBKxODu2TX0+Syr3h52Tw4o=
 github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618 h1:7InQ7/zrOh6SlFjaXFubv0xX0HsuC9qJsdqm7bNQpYM=
 github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
 github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559 h1:Cef96rKLuXxeGzERI/0ve9yAzIeTpx0qz9JKFDZALYw=
diff --git a/libcontainer/factory_linux.go b/libcontainer/factory_linux.go
index 437633c6..a572c0f6 100644
--- a/libcontainer/factory_linux.go
+++ b/libcontainer/factory_linux.go
@@ -11,7 +11,8 @@ import (
 	"runtime/debug"
 	"strconv"
 
-	"github.com/cyphar/filepath-securejoin"
+	securejoin "github.com/cyphar/filepath-securejoin"
+	"github.com/moby/sys/mountinfo"
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/cgroups/fs"
 	"github.com/opencontainers/runc/libcontainer/cgroups/fs2"
@@ -19,7 +20,6 @@ import (
 	"github.com/opencontainers/runc/libcontainer/configs"
 	"github.com/opencontainers/runc/libcontainer/configs/validate"
 	"github.com/opencontainers/runc/libcontainer/intelrdt"
-	"github.com/opencontainers/runc/libcontainer/mount"
 	"github.com/opencontainers/runc/libcontainer/utils"
 	"github.com/pkg/errors"
 
@@ -137,7 +137,7 @@ func IntelRdtFs(l *LinuxFactory) error {
 
 // TmpfsRoot is an option func to mount LinuxFactory.Root to tmpfs.
 func TmpfsRoot(l *LinuxFactory) error {
-	mounted, err := mount.Mounted(l.Root)
+	mounted, err := mountinfo.Mounted(l.Root)
 	if err != nil {
 		return err
 	}
diff --git a/libcontainer/mount/mount.go b/libcontainer/mount/mount.go
deleted file mode 100644
index e8965e08..00000000
--- a/libcontainer/mount/mount.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package mount
-
-// GetMounts retrieves a list of mounts for the current running process.
-func GetMounts() ([]*Info, error) {
-	return parseMountTable()
-}
-
-// Mounted looks at /proc/self/mountinfo to determine of the specified
-// mountpoint has been mounted
-func Mounted(mountpoint string) (bool, error) {
-	entries, err := parseMountTable()
-	if err != nil {
-		return false, err
-	}
-
-	// Search the table for the mountpoint
-	for _, e := range entries {
-		if e.Mountpoint == mountpoint {
-			return true, nil
-		}
-	}
-	return false, nil
-}
diff --git a/libcontainer/mount/mount_linux.go b/libcontainer/mount/mount_linux.go
deleted file mode 100644
index 1e519192..00000000
--- a/libcontainer/mount/mount_linux.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// +build linux
-
-package mount
-
-import (
-	"bufio"
-	"fmt"
-	"io"
-	"os"
-	"strings"
-)
-
-const (
-	/* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
-	   (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
-
-	   (1) mount ID:  unique identifier of the mount (may be reused after umount)
-	   (2) parent ID:  ID of parent (or of self for the top of the mount tree)
-	   (3) major:minor:  value of st_dev for files on filesystem
-	   (4) root:  root of the mount within the filesystem
-	   (5) mount point:  mount point relative to the process's root
-	   (6) mount options:  per mount options
-	   (7) optional fields:  zero or more fields of the form "tag[:value]"
-	   (8) separator:  marks the end of the optional fields
-	   (9) filesystem type:  name of filesystem of the form "type[.subtype]"
-	   (10) mount source:  filesystem specific information or "none"
-	   (11) super options:  per super block options*/
-	mountinfoFormat = "%d %d %d:%d %s %s %s %s"
-)
-
-// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
-// bind mounts
-func parseMountTable() ([]*Info, error) {
-	f, err := os.Open("/proc/self/mountinfo")
-	if err != nil {
-		return nil, err
-	}
-	defer f.Close()
-
-	return parseInfoFile(f)
-}
-
-func parseInfoFile(r io.Reader) ([]*Info, error) {
-	var (
-		s   = bufio.NewScanner(r)
-		out = []*Info{}
-	)
-
-	for s.Scan() {
-		if err := s.Err(); err != nil {
-			return nil, err
-		}
-
-		var (
-			p              = &Info{}
-			text           = s.Text()
-			optionalFields string
-		)
-
-		if _, err := fmt.Sscanf(text, mountinfoFormat,
-			&p.ID, &p.Parent, &p.Major, &p.Minor,
-			&p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil {
-			return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
-		}
-		// Safe as mountinfo encodes mountpoints with spaces as \040.
-		index := strings.Index(text, " - ")
-		postSeparatorFields := strings.Fields(text[index+3:])
-		if len(postSeparatorFields) < 3 {
-			return nil, fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
-		}
-
-		if optionalFields != "-" {
-			p.Optional = optionalFields
-		}
-
-		p.Fstype = postSeparatorFields[0]
-		p.Source = postSeparatorFields[1]
-		p.VfsOpts = strings.Join(postSeparatorFields[2:], " ")
-		out = append(out, p)
-	}
-	return out, nil
-}
diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go
index 106c4c2b..84c5143a 100644
--- a/libcontainer/rootfs_linux.go
+++ b/libcontainer/rootfs_linux.go
@@ -14,10 +14,10 @@ import (
 	"time"
 
 	securejoin "github.com/cyphar/filepath-securejoin"
+	"github.com/moby/sys/mountinfo"
 	"github.com/mrunalp/fileutils"
 	"github.com/opencontainers/runc/libcontainer/cgroups"
 	"github.com/opencontainers/runc/libcontainer/configs"
-	"github.com/opencontainers/runc/libcontainer/mount"
 	"github.com/opencontainers/runc/libcontainer/system"
 	libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
 	"github.com/opencontainers/selinux/go-selinux/label"
@@ -661,46 +661,26 @@ func mknodDevice(dest string, node *configs.Device) error {
 	return unix.Chown(dest, int(node.Uid), int(node.Gid))
 }
 
-func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info {
-	for _, m := range mountinfo {
-		if m.Mountpoint == dir {
-			return m
-		}
-	}
-	return nil
-}
-
 // Get the parent mount point of directory passed in as argument. Also return
 // optional fields.
 func getParentMount(rootfs string) (string, string, error) {
-	var path string
-
-	mountinfos, err := mount.GetMounts()
+	mi, err := mountinfo.GetMounts(mountinfo.ParentsFilter(rootfs))
 	if err != nil {
 		return "", "", err
 	}
-
-	mountinfo := getMountInfo(mountinfos, rootfs)
-	if mountinfo != nil {
-		return rootfs, mountinfo.Optional, nil
+	if len(mi) < 1 {
+		return "", "", fmt.Errorf("could not find parent mount of %s", rootfs)
 	}
 
-	path = rootfs
-	for {
-		path = filepath.Dir(path)
-
-		mountinfo = getMountInfo(mountinfos, path)
-		if mountinfo != nil {
-			return path, mountinfo.Optional, nil
-		}
-
-		if path == "/" {
-			break
+	// find the longest mount point
+	var idx, maxlen int
+	for i := range mi {
+		if len(mi[i].Mountpoint) > maxlen {
+			maxlen = len(mi[i].Mountpoint)
+			idx = i
 		}
 	}
-
-	// If we are here, we did not find parent mount. Something is wrong.
-	return "", "", fmt.Errorf("Could not find parent mount of %s", rootfs)
+	return mi[idx].Mountpoint, mi[idx].Optional, nil
 }
 
 // Make parent mount private if it was shared
@@ -825,25 +805,22 @@ func pivotRoot(rootfs string) error {
 }
 
 func msMoveRoot(rootfs string) error {
-	mountinfos, err := mount.GetMounts()
-	if err != nil {
-		return err
-	}
-
-	absRootfs, err := filepath.Abs(rootfs)
+	mountinfos, err := mountinfo.GetMounts(func(info *mountinfo.Info) (skip, stop bool) {
+		skip = false
+		stop = false
+		// Collect every sysfs and proc file systems, except those under the container rootfs
+		if (info.Fstype != "proc" && info.Fstype != "sysfs") || strings.HasPrefix(info.Mountpoint, rootfs) {
+			skip = true
+			return
+		}
+		return
+	})
 	if err != nil {
 		return err
 	}
 
 	for _, info := range mountinfos {
-		p, err := filepath.Abs(info.Mountpoint)
-		if err != nil {
-			return err
-		}
-		// Umount every syfs and proc file systems, except those under the container rootfs
-		if (info.Fstype != "proc" && info.Fstype != "sysfs") || filepath.HasPrefix(p, absRootfs) {
-			continue
-		}
+		p := info.Mountpoint
 		// Be sure umount events are not propagated to the host.
 		if err := unix.Mount("", p, "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil {
 			return err
diff --git a/vendor/github.com/moby/sys/mountinfo/LICENSE b/vendor/github.com/moby/sys/mountinfo/LICENSE
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/vendor/github.com/moby/sys/mountinfo/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/vendor/github.com/moby/sys/mountinfo/go.mod b/vendor/github.com/moby/sys/mountinfo/go.mod
new file mode 100644
index 00000000..10d9a15a
--- /dev/null
+++ b/vendor/github.com/moby/sys/mountinfo/go.mod
@@ -0,0 +1,3 @@
+module github.com/moby/sys/mountinfo
+
+go 1.14
diff --git a/libcontainer/mount/mountinfo.go b/vendor/github.com/moby/sys/mountinfo/mountinfo.go
similarity index 66%
rename from libcontainer/mount/mountinfo.go
rename to vendor/github.com/moby/sys/mountinfo/mountinfo.go
index e3fc3535..ec3b5934 100644
--- a/libcontainer/mount/mountinfo.go
+++ b/vendor/github.com/moby/sys/mountinfo/mountinfo.go
@@ -1,4 +1,21 @@
-package mount
+package mountinfo
+
+// GetMounts retrieves a list of mounts for the current running process,
+// with an optional filter applied (use nil for no filter).
+func GetMounts(f FilterFunc) ([]*Info, error) {
+	return parseMountTable(f)
+}
+
+// Mounted determines if a specified mountpoint has been mounted.
+// On Linux it looks at /proc/self/mountinfo.
+func Mounted(mountpoint string) (bool, error) {
+	entries, err := GetMounts(SingleEntryFilter(mountpoint))
+	if err != nil {
+		return false, err
+	}
+
+	return len(entries) > 0, nil
+}
 
 // Info reveals information about a particular mounted filesystem. This
 // struct is populated from the content in the /proc/<pid>/mountinfo file.
diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go
new file mode 100644
index 00000000..96d49414
--- /dev/null
+++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_filters.go
@@ -0,0 +1,56 @@
+package mountinfo
+
+import "strings"
+
+// FilterFunc is a type defining a callback function for GetMount(),
+// used to filter out mountinfo entries we're not interested in.
+//
+// It takes a pointer to the Info struct (not fully populated,
+// currently only Mountpoint is filled in), and returns two booleans:
+//
+//  - skip: true if the entry should be skipped
+//  - stop: true if parsing should be stopped after the entry
+type FilterFunc func(*Info) (skip, stop bool)
+
+// PrefixFilter discards all entries whose mount points
+// do not start with a specific prefix
+func PrefixFilter(prefix string) FilterFunc {
+	return func(m *Info) (bool, bool) {
+		skip := !strings.HasPrefix(m.Mountpoint, prefix)
+		return skip, false
+	}
+}
+
+// SingleEntryFilter looks for a specific entry
+func SingleEntryFilter(mp string) FilterFunc {
+	return func(m *Info) (bool, bool) {
+		if m.Mountpoint == mp {
+			return false, true // don't skip, stop now
+		}
+		return true, false // skip, keep going
+	}
+}
+
+// ParentsFilter returns all entries whose mount points
+// can be parents of a path specified, discarding others.
+//
+// For example, given `/var/lib/docker/something`, entries
+// like `/var/lib/docker`, `/var` and `/` are returned.
+func ParentsFilter(path string) FilterFunc {
+	return func(m *Info) (bool, bool) {
+		skip := !strings.HasPrefix(path, m.Mountpoint)
+		return skip, false
+	}
+}
+
+// FstypeFilter returns all entries that match provided fstype(s).
+func FstypeFilter(fstype ...string) FilterFunc {
+	return func(m *Info) (bool, bool) {
+		for _, t := range fstype {
+			if m.Fstype == t {
+				return false, false // don't skeep, keep going
+			}
+		}
+		return true, false // skip, keep going
+	}
+}
diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go
new file mode 100644
index 00000000..ea02568b
--- /dev/null
+++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_freebsd.go
@@ -0,0 +1,54 @@
+package mountinfo
+
+/*
+#include <sys/param.h>
+#include <sys/ucred.h>
+#include <sys/mount.h>
+*/
+import "C"
+
+import (
+	"fmt"
+	"reflect"
+	"unsafe"
+)
+
+// parseMountTable returns information about mounted filesystems
+func parseMountTable(filter FilterFunc) ([]*Info, error) {
+	var rawEntries *C.struct_statfs
+
+	count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
+	if count == 0 {
+		return nil, fmt.Errorf("Failed to call getmntinfo")
+	}
+
+	var entries []C.struct_statfs
+	header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
+	header.Cap = count
+	header.Len = count
+	header.Data = uintptr(unsafe.Pointer(rawEntries))
+
+	var out []*Info
+	for _, entry := range entries {
+		var mountinfo Info
+		var skip, stop bool
+		mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
+		mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
+
+		if filter != nil {
+			// filter out entries we're not interested in
+			skip, stop = filter(&mountinfo)
+			if skip {
+				continue
+			}
+		}
+
+		mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
+
+		out = append(out, &mountinfo)
+		if stop {
+			break
+		}
+	}
+	return out, nil
+}
diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go
new file mode 100644
index 00000000..a2c60e91
--- /dev/null
+++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_linux.go
@@ -0,0 +1,154 @@
+// +build go1.13
+
+package mountinfo
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+)
+
+func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
+	s := bufio.NewScanner(r)
+	out := []*Info{}
+	var err error
+	for s.Scan() {
+		if err = s.Err(); err != nil {
+			return nil, err
+		}
+		/*
+		   See http://man7.org/linux/man-pages/man5/proc.5.html
+
+		   36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
+		   (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
+
+		   (1) mount ID:  unique identifier of the mount (may be reused after umount)
+		   (2) parent ID:  ID of parent (or of self for the top of the mount tree)
+		   (3) major:minor:  value of st_dev for files on filesystem
+		   (4) root:  root of the mount within the filesystem
+		   (5) mount point:  mount point relative to the process's root
+		   (6) mount options:  per mount options
+		   (7) optional fields:  zero or more fields of the form "tag[:value]"
+		   (8) separator:  marks the end of the optional fields
+		   (9) filesystem type:  name of filesystem of the form "type[.subtype]"
+		   (10) mount source:  filesystem specific information or "none"
+		   (11) super options:  per super block options
+
+		   In other words, we have:
+		    * 6 mandatory fields	(1)..(6)
+		    * 0 or more optional fields	(7)
+		    * a separator field		(8)
+		    * 3 mandatory fields	(9)..(11)
+		*/
+
+		text := s.Text()
+		fields := strings.Split(text, " ")
+		numFields := len(fields)
+		if numFields < 10 {
+			// should be at least 10 fields
+			return nil, fmt.Errorf("Parsing '%s' failed: not enough fields (%d)", text, numFields)
+		}
+
+		// separator field
+		sepIdx := numFields - 4
+		// In Linux <= 3.9 mounting a cifs with spaces in a share
+		// name (like "//srv/My Docs") _may_ end up having a space
+		// in the last field of mountinfo (like "unc=//serv/My Docs").
+		// Since kernel 3.10-rc1, cifs option "unc=" is ignored,
+		// so spaces should not appear.
+		//
+		// Check for a separator, and work around the spaces bug
+		for fields[sepIdx] != "-" {
+			sepIdx--
+			if sepIdx == 5 {
+				return nil, fmt.Errorf("Parsing '%s' failed: missing - separator", text)
+			}
+		}
+
+		p := &Info{}
+
+		// Fill in the fields that a filter might check
+		// (currently only Mountpoint and Fstype)
+		p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
+		if err != nil {
+			return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote mount point field: %w", fields[4], err)
+		}
+		p.Fstype = fields[sepIdx+1]
+
+		// Run a filter soon so we can skip parsing/adding entries
+		// the caller is not interested in
+		var skip, stop bool
+		if filter != nil {
+			skip, stop = filter(p)
+			if skip {
+				continue
+			}
+		}
+
+		// Fill in the rest of the fields
+
+		// ignore any numbers parsing errors, as there should not be any
+		p.ID, _ = strconv.Atoi(fields[0])
+		p.Parent, _ = strconv.Atoi(fields[1])
+		mm := strings.Split(fields[2], ":")
+		if len(mm) != 2 {
+			return nil, fmt.Errorf("Parsing '%s' failed: unexpected minor:major pair %s", text, mm)
+		}
+		p.Major, _ = strconv.Atoi(mm[0])
+		p.Minor, _ = strconv.Atoi(mm[1])
+
+		p.Root, err = strconv.Unquote(`"` + fields[3] + `"`)
+		if err != nil {
+			return nil, fmt.Errorf("Parsing '%s' failed: unable to unquote root field: %w", fields[3], err)
+		}
+
+		p.Opts = fields[5]
+
+		// zero or more optional fields
+		switch {
+		case sepIdx == 6:
+			// zero, do nothing
+		case sepIdx == 7:
+			p.Optional = fields[6]
+		default:
+			p.Optional = strings.Join(fields[6:sepIdx-1], " ")
+		}
+
+		p.Source = fields[sepIdx+2]
+		p.VfsOpts = fields[sepIdx+3]
+
+		out = append(out, p)
+		if stop {
+			break
+		}
+	}
+	return out, nil
+}
+
+// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
+// bind mounts
+func parseMountTable(filter FilterFunc) ([]*Info, error) {
+	f, err := os.Open("/proc/self/mountinfo")
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	return parseInfoFile(f, filter)
+}
+
+// PidMountInfo collects the mounts for a specific process ID. If the process
+// ID is unknown, it is better to use `GetMounts` which will inspect
+// "/proc/self/mountinfo" instead.
+func PidMountInfo(pid int) ([]*Info, error) {
+	f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	return parseInfoFile(f, nil)
+}
diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go
new file mode 100644
index 00000000..8ff917e0
--- /dev/null
+++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_unsupported.go
@@ -0,0 +1,12 @@
+// +build !windows,!linux,!freebsd freebsd,!cgo
+
+package mountinfo
+
+import (
+	"fmt"
+	"runtime"
+)
+
+func parseMountTable(f FilterFunc) ([]*Info, error) {
+	return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
+}
diff --git a/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go b/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go
new file mode 100644
index 00000000..efd0b271
--- /dev/null
+++ b/vendor/github.com/moby/sys/mountinfo/mountinfo_windows.go
@@ -0,0 +1,6 @@
+package mountinfo
+
+func parseMountTable(f FilterFunc) ([]*Info, error) {
+	// Do NOT return an error!
+	return nil, nil
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 8f8b1973..b2456e63 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -28,6 +28,9 @@ github.com/godbus/dbus
 github.com/golang/protobuf/proto
 # github.com/konsorten/go-windows-terminal-sequences v1.0.1
 github.com/konsorten/go-windows-terminal-sequences
+# github.com/moby/sys/mountinfo v0.1.0
+## explicit
+github.com/moby/sys/mountinfo
 # github.com/mrunalp/fileutils v0.0.0-20171103030105-7d4729fb3618
 ## explicit
 github.com/mrunalp/fileutils


Test patch:
diff --git a/libcontainer/factory_linux_test.go b/libcontainer/factory_linux_test.go
index 8d0ca8a4..af91aace 100644
--- a/libcontainer/factory_linux_test.go
+++ b/libcontainer/factory_linux_test.go
@@ -9,8 +9,8 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/moby/sys/mountinfo"
 	"github.com/opencontainers/runc/libcontainer/configs"
-	"github.com/opencontainers/runc/libcontainer/mount"
 	"github.com/opencontainers/runc/libcontainer/utils"
 	"github.com/opencontainers/runtime-spec/specs-go"
 
@@ -101,33 +101,28 @@ func TestFactoryNewTmpfs(t *testing.T) {
 	if factory.Type() != "libcontainer" {
 		t.Fatalf("unexpected factory type: %q, expected %q", factory.Type(), "libcontainer")
 	}
-	mounted, err := mount.Mounted(lfactory.Root)
+	mounted, err := mountinfo.Mounted(lfactory.Root)
 	if err != nil {
 		t.Fatal(err)
 	}
 	if !mounted {
 		t.Fatalf("Factory Root is not mounted")
 	}
-	mounts, err := mount.GetMounts()
+	mounts, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(lfactory.Root))
 	if err != nil {
 		t.Fatal(err)
 	}
-	var found bool
-	for _, m := range mounts {
-		if m.Mountpoint == lfactory.Root {
-			if m.Fstype != "tmpfs" {
-				t.Fatalf("Fstype of root: %s, expected %s", m.Fstype, "tmpfs")
-			}
-			if m.Source != "tmpfs" {
-				t.Fatalf("Source of root: %s, expected %s", m.Source, "tmpfs")
-			}
-			found = true
-		}
-	}
-	if !found {
+	if len(mounts) != 1 {
 		t.Fatalf("Factory Root is not listed in mounts list")
 	}
-	defer unix.Unmount(root, unix.MNT_DETACH)
+	m := mounts[0]
+	if m.Fstype != "tmpfs" {
+		t.Fatalf("Fstype of root: %s, expected %s", m.Fstype, "tmpfs")
+	}
+	if m.Source != "tmpfs" {
+		t.Fatalf("Source of root: %s, expected %s", m.Source, "tmpfs")
+	}
+	unix.Unmount(root, unix.MNT_DETACH)
 }
 
 func TestFactoryLoadNotExists(t *testing.T) {