summaryrefslogtreecommitdiffhomepage
path: root/runsc/container
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/container')
-rw-r--r--runsc/container/BUILD2
-rw-r--r--runsc/container/container.go75
-rw-r--r--runsc/container/container_test.go165
-rw-r--r--runsc/container/fs.go287
-rw-r--r--runsc/container/fs_test.go158
5 files changed, 196 insertions, 491 deletions
diff --git a/runsc/container/BUILD b/runsc/container/BUILD
index 3b25ff79a..2936b7cdf 100644
--- a/runsc/container/BUILD
+++ b/runsc/container/BUILD
@@ -6,7 +6,6 @@ go_library(
name = "container",
srcs = [
"container.go",
- "fs.go",
"hook.go",
"status.go",
],
@@ -34,7 +33,6 @@ go_test(
srcs = [
"console_test.go",
"container_test.go",
- "fs_test.go",
"multi_container_test.go",
"shared_volume_test.go",
],
diff --git a/runsc/container/container.go b/runsc/container/container.go
index 6f092a5ce..fdcf8d7b7 100644
--- a/runsc/container/container.go
+++ b/runsc/container/container.go
@@ -281,18 +281,6 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
if specutils.ShouldCreateSandbox(spec) {
log.Debugf("Creating new sandbox for container %q", id)
- // Setup rootfs and mounts. It returns a new mount list with destination
- // paths resolved. Since the spec for the root container is read from disk,
- // Write the new spec to a new file that will be used by the sandbox.
- cleanMounts, err := setupFS(spec, conf, bundleDir)
- if err != nil {
- return nil, fmt.Errorf("setup mounts: %v", err)
- }
- spec.Mounts = cleanMounts
- if err := specutils.WriteCleanSpec(bundleDir, spec); err != nil {
- return nil, fmt.Errorf("writing clean spec: %v", err)
- }
-
// Create and join cgroup before processes are created to ensure they are
// part of the cgroup from the start (and all tneir children processes).
cg, err := cgroup.New(spec)
@@ -306,14 +294,14 @@ func Create(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSo
}
}
if err := runInCgroup(cg, func() error {
- ioFiles, err := c.createGoferProcess(spec, conf, bundleDir)
+ ioFiles, specFile, err := c.createGoferProcess(spec, conf, bundleDir)
if err != nil {
return err
}
// Start a new sandbox for this container. Any errors after this point
// must destroy the container.
- c.Sandbox, err = sandbox.New(id, spec, conf, bundleDir, consoleSocket, userLog, ioFiles, cg)
+ c.Sandbox, err = sandbox.New(id, spec, conf, bundleDir, consoleSocket, userLog, ioFiles, specFile, cg)
return err
}); err != nil {
return nil, err
@@ -387,26 +375,22 @@ func (c *Container) Start(conf *boot.Config) error {
return err
}
} else {
- // Setup rootfs and mounts. It returns a new mount list with destination
- // paths resolved. Replace the original spec with new mount list and start
- // container.
- cleanMounts, err := setupFS(c.Spec, conf, c.BundleDir)
- if err != nil {
- return fmt.Errorf("setup mounts: %v", err)
- }
- c.Spec.Mounts = cleanMounts
- if err := specutils.WriteCleanSpec(c.BundleDir, c.Spec); err != nil {
- return fmt.Errorf("writing clean spec: %v", err)
- }
-
// Join cgroup to strt gofer process to ensure it's part of the cgroup from
// the start (and all tneir children processes).
if err := runInCgroup(c.Sandbox.Cgroup, func() error {
// Create the gofer process.
- ioFiles, err := c.createGoferProcess(c.Spec, conf, c.BundleDir)
+ ioFiles, mountsFile, err := c.createGoferProcess(c.Spec, conf, c.BundleDir)
if err != nil {
return err
}
+ defer mountsFile.Close()
+
+ cleanMounts, err := specutils.ReadMounts(mountsFile)
+ if err != nil {
+ return fmt.Errorf("reading mounts file: %v", err)
+ }
+ c.Spec.Mounts = cleanMounts
+
return c.Sandbox.StartContainer(c.Spec, conf, c.ID, ioFiles)
}); err != nil {
return err
@@ -665,12 +649,6 @@ func (c *Container) Destroy() error {
errs = append(errs, err.Error())
}
- if err := destroyFS(c.Spec); err != nil {
- err = fmt.Errorf("destroying container fs: %v", err)
- log.Warningf("%v", err)
- errs = append(errs, err.Error())
- }
-
if err := os.RemoveAll(c.Root); err != nil && !os.IsNotExist(err) {
err = fmt.Errorf("deleting container root directory %q: %v", c.Root, err)
log.Warningf("%v", err)
@@ -787,7 +765,7 @@ func (c *Container) waitForStopped() error {
return backoff.Retry(op, b)
}
-func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundleDir string) ([]*os.File, error) {
+func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bundleDir string) ([]*os.File, *os.File, error) {
// Start with the general config flags.
args := conf.ToFlags()
@@ -800,7 +778,7 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bund
if conf.LogFilename != "" {
logFile, err := os.OpenFile(conf.LogFilename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
- return nil, fmt.Errorf("opening log file %q: %v", conf.LogFilename, err)
+ return nil, nil, fmt.Errorf("opening log file %q: %v", conf.LogFilename, err)
}
defer logFile.Close()
goferEnds = append(goferEnds, logFile)
@@ -811,7 +789,7 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bund
if conf.DebugLog != "" {
debugLogFile, err := specutils.DebugLogFile(conf.DebugLog, "gofer")
if err != nil {
- return nil, fmt.Errorf("opening debug log file in %q: %v", conf.DebugLog, err)
+ return nil, nil, fmt.Errorf("opening debug log file in %q: %v", conf.DebugLog, err)
}
defer debugLogFile.Close()
goferEnds = append(goferEnds, debugLogFile)
@@ -825,30 +803,39 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bund
}
// Open the spec file to donate to the sandbox.
- specFile, err := specutils.OpenCleanSpec(bundleDir)
+ specFile, err := specutils.OpenSpec(bundleDir)
if err != nil {
- return nil, fmt.Errorf("opening spec file: %v", err)
+ return nil, nil, fmt.Errorf("opening spec file: %v", err)
}
defer specFile.Close()
goferEnds = append(goferEnds, specFile)
args = append(args, "--spec-fd="+strconv.Itoa(nextFD))
nextFD++
+ // Create pipe that allows gofer to send mount list to sandbox after all paths
+ // have been resolved.
+ mountsSand, mountsGofer, err := os.Pipe()
+ if err != nil {
+ return nil, nil, err
+ }
+ defer mountsGofer.Close()
+ goferEnds = append(goferEnds, mountsGofer)
+ args = append(args, fmt.Sprintf("--mounts-fd=%d", nextFD))
+ nextFD++
+
// Add root mount and then add any other additional mounts.
mountCount := 1
-
- // Add additional mounts.
for _, m := range spec.Mounts {
if specutils.Is9PMount(m) {
mountCount++
}
}
- sandEnds := make([]*os.File, 0, mountCount)
+ sandEnds := make([]*os.File, 0, mountCount)
for i := 0; i < mountCount; i++ {
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
if err != nil {
- return nil, err
+ return nil, nil, err
}
sandEnds = append(sandEnds, os.NewFile(uintptr(fds[0]), "sandbox IO FD"))
@@ -884,12 +871,12 @@ func (c *Container) createGoferProcess(spec *specs.Spec, conf *boot.Config, bund
// Start the gofer in the given namespace.
log.Debugf("Starting gofer: %s %v", binPath, args)
if err := specutils.StartInNS(cmd, nss); err != nil {
- return nil, err
+ return nil, nil, err
}
log.Infof("Gofer started, PID: %d", cmd.Process.Pid)
c.GoferPid = cmd.Process.Pid
c.goferIsChild = true
- return sandEnds, nil
+ return sandEnds, mountsSand, nil
}
// changeStatus transitions from one status to another ensuring that the
diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go
index 06a25de6d..f17155175 100644
--- a/runsc/container/container_test.go
+++ b/runsc/container/container_test.go
@@ -1594,6 +1594,171 @@ func TestCreateWorkingDir(t *testing.T) {
}
}
+// TestMountPropagation verifies that mount propagates to slave but not to
+// private mounts.
+func TestMountPropagation(t *testing.T) {
+ // Setup dir structure:
+ // - src: is mounted as shared and is used as source for both private and
+ // slave mounts
+ // - dir: will be bind mounted inside src and should propagate to slave
+ tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "mount")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir() failed: %v", err)
+ }
+ src := filepath.Join(tmpDir, "src")
+ srcMnt := filepath.Join(src, "mnt")
+ dir := filepath.Join(tmpDir, "dir")
+ for _, path := range []string{src, srcMnt, dir} {
+ if err := os.MkdirAll(path, 0777); err != nil {
+ t.Fatalf("MkdirAll(%q): %v", path, err)
+ }
+ }
+ dirFile := filepath.Join(dir, "file")
+ f, err := os.Create(dirFile)
+ if err != nil {
+ t.Fatalf("os.Create(%q): %v", dirFile, err)
+ }
+ f.Close()
+
+ // Setup src as a shared mount.
+ if err := syscall.Mount(src, src, "bind", syscall.MS_BIND, ""); err != nil {
+ t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err)
+ }
+ if err := syscall.Mount("", src, "", syscall.MS_SHARED, ""); err != nil {
+ t.Fatalf("mount(%q, MS_SHARED): %v", srcMnt, err)
+ }
+
+ spec := testutil.NewSpecWithArgs("sleep", "1000")
+
+ priv := filepath.Join(tmpDir, "priv")
+ slave := filepath.Join(tmpDir, "slave")
+ spec.Mounts = []specs.Mount{
+ {
+ Source: src,
+ Destination: priv,
+ Type: "bind",
+ Options: []string{"private"},
+ },
+ {
+ Source: src,
+ Destination: slave,
+ Type: "bind",
+ Options: []string{"slave"},
+ },
+ }
+
+ conf := testutil.TestConfig()
+ rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+ defer os.RemoveAll(bundleDir)
+
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
+ if err != nil {
+ t.Fatalf("creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ // After the container is started, mount dir inside source and check what
+ // happens to both destinations.
+ if err := syscall.Mount(dir, srcMnt, "bind", syscall.MS_BIND, ""); err != nil {
+ t.Fatalf("mount(%q, %q, MS_BIND): %v", dir, srcMnt, err)
+ }
+
+ // Check that mount didn't propagate to private mount.
+ privFile := filepath.Join(priv, "mnt", "file")
+ args := &control.ExecArgs{
+ Filename: "/usr/bin/test",
+ Argv: []string{"test", "!", "-f", privFile},
+ }
+ if ws, err := cont.executeSync(args); err != nil || ws != 0 {
+ t.Fatalf("exec: test ! -f %q, ws: %v, err: %v", privFile, ws, err)
+ }
+
+ // Check that mount propagated to slave mount.
+ slaveFile := filepath.Join(slave, "mnt", "file")
+ args = &control.ExecArgs{
+ Filename: "/usr/bin/test",
+ Argv: []string{"test", "-f", slaveFile},
+ }
+ if ws, err := cont.executeSync(args); err != nil || ws != 0 {
+ t.Fatalf("exec: test -f %q, ws: %v, err: %v", privFile, ws, err)
+ }
+}
+
+func TestMountSymlink(t *testing.T) {
+ for _, conf := range configs(overlay) {
+ t.Logf("Running test with conf: %+v", conf)
+
+ dir, err := ioutil.TempDir(testutil.TmpDir(), "mount-symlink")
+ if err != nil {
+ t.Fatalf("ioutil.TempDir() failed: %v", err)
+ }
+
+ source := path.Join(dir, "source")
+ target := path.Join(dir, "target")
+ for _, path := range []string{source, target} {
+ if err := os.MkdirAll(path, 0777); err != nil {
+ t.Fatalf("os.MkdirAll(): %v", err)
+ }
+ }
+ f, err := os.Create(path.Join(source, "file"))
+ if err != nil {
+ t.Fatalf("os.Create(): %v", err)
+ }
+ f.Close()
+
+ link := path.Join(dir, "link")
+ if err := os.Symlink(target, link); err != nil {
+ t.Fatalf("os.Symlink(%q, %q): %v", target, link, err)
+ }
+
+ spec := testutil.NewSpecWithArgs("/bin/sleep", "1000")
+
+ // Mount to a symlink to ensure the mount code will follow it and mount
+ // at the symlink target.
+ spec.Mounts = append(spec.Mounts, specs.Mount{
+ Type: "bind",
+ Destination: link,
+ Source: source,
+ })
+
+ rootDir, bundleDir, err := testutil.SetupContainer(spec, conf)
+ if err != nil {
+ t.Fatalf("error setting up container: %v", err)
+ }
+ defer os.RemoveAll(rootDir)
+ defer os.RemoveAll(bundleDir)
+
+ cont, err := Create(testutil.UniqueContainerID(), spec, conf, bundleDir, "", "", "")
+ if err != nil {
+ t.Fatalf("creating container: %v", err)
+ }
+ defer cont.Destroy()
+
+ if err := cont.Start(conf); err != nil {
+ t.Fatalf("starting container: %v", err)
+ }
+
+ // Check that symlink was resolved and mount was created where the symlink
+ // is pointing to.
+ file := path.Join(target, "file")
+ args := &control.ExecArgs{
+ Filename: "/usr/bin/test",
+ Argv: []string{"test", "-f", file},
+ }
+ if ws, err := cont.executeSync(args); err != nil || ws != 0 {
+ t.Fatalf("exec: test -f %q, ws: %v, err: %v", file, ws, err)
+ }
+ }
+}
+
// executeSync synchronously executes a new process.
func (cont *Container) executeSync(args *control.ExecArgs) (syscall.WaitStatus, error) {
pid, err := cont.Execute(args)
diff --git a/runsc/container/fs.go b/runsc/container/fs.go
deleted file mode 100644
index 998160487..000000000
--- a/runsc/container/fs.go
+++ /dev/null
@@ -1,287 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-package container
-
-import (
- "bufio"
- "fmt"
- "os"
- "path/filepath"
- "strings"
- "syscall"
-
- specs "github.com/opencontainers/runtime-spec/specs-go"
- "gvisor.googlesource.com/gvisor/pkg/log"
- "gvisor.googlesource.com/gvisor/runsc/boot"
- "gvisor.googlesource.com/gvisor/runsc/specutils"
-)
-
-type mapping struct {
- set bool
- val uint32
-}
-
-var optionsMap = map[string]mapping{
- "acl": {set: true, val: syscall.MS_POSIXACL},
- "async": {set: false, val: syscall.MS_SYNCHRONOUS},
- "atime": {set: false, val: syscall.MS_NOATIME},
- "bind": {set: true, val: syscall.MS_BIND},
- "defaults": {set: true, val: 0},
- "dev": {set: false, val: syscall.MS_NODEV},
- "diratime": {set: false, val: syscall.MS_NODIRATIME},
- "dirsync": {set: true, val: syscall.MS_DIRSYNC},
- "exec": {set: false, val: syscall.MS_NOEXEC},
- "iversion": {set: true, val: syscall.MS_I_VERSION},
- "loud": {set: false, val: syscall.MS_SILENT},
- "mand": {set: true, val: syscall.MS_MANDLOCK},
- "noacl": {set: false, val: syscall.MS_POSIXACL},
- "noatime": {set: true, val: syscall.MS_NOATIME},
- "nodev": {set: true, val: syscall.MS_NODEV},
- "nodiratime": {set: true, val: syscall.MS_NODIRATIME},
- "noexec": {set: true, val: syscall.MS_NOEXEC},
- "noiversion": {set: false, val: syscall.MS_I_VERSION},
- "nomand": {set: false, val: syscall.MS_MANDLOCK},
- "norelatime": {set: false, val: syscall.MS_RELATIME},
- "nostrictatime": {set: false, val: syscall.MS_STRICTATIME},
- "nosuid": {set: true, val: syscall.MS_NOSUID},
- "private": {set: true, val: syscall.MS_PRIVATE},
- "rbind": {set: true, val: syscall.MS_BIND | syscall.MS_REC},
- "relatime": {set: true, val: syscall.MS_RELATIME},
- "remount": {set: true, val: syscall.MS_REMOUNT},
- "ro": {set: true, val: syscall.MS_RDONLY},
- "rprivate": {set: true, val: syscall.MS_PRIVATE | syscall.MS_REC},
- "rw": {set: false, val: syscall.MS_RDONLY},
- "silent": {set: true, val: syscall.MS_SILENT},
- "strictatime": {set: true, val: syscall.MS_STRICTATIME},
- "suid": {set: false, val: syscall.MS_NOSUID},
- "sync": {set: true, val: syscall.MS_SYNCHRONOUS},
-}
-
-// setupFS creates the container directory structure under 'spec.Root.Path'.
-// This allows the gofer serving the containers to be chroot under this
-// directory to create an extra layer to security in case the gofer gets
-// compromised.
-// Returns list of mounts equivalent to 'spec.Mounts' with all destination paths
-// cleaned and with symlinks resolved.
-func setupFS(spec *specs.Spec, conf *boot.Config, bundleDir string) ([]specs.Mount, error) {
- rv := make([]specs.Mount, 0, len(spec.Mounts))
- for _, m := range spec.Mounts {
- if m.Type != "bind" || !specutils.IsSupportedDevMount(m) {
- rv = append(rv, m)
- continue
- }
-
- // It's possible that 'm.Destination' follows symlinks inside the
- // container.
- dst, err := resolveSymlinks(spec.Root.Path, m.Destination)
- if err != nil {
- return nil, fmt.Errorf("resolving symlinks to %q: %v", m.Destination, err)
- }
-
- flags := optionsToFlags(m.Options)
- flags |= syscall.MS_BIND
- log.Infof("Mounting src: %q, dst: %q, flags: %#x", m.Source, dst, flags)
- if err := specutils.Mount(m.Source, dst, m.Type, flags); err != nil {
- return nil, fmt.Errorf("mounting %v: %v", m, err)
- }
-
- // Make the mount a slave, so that for recursive bind mount, umount won't
- // propagate to the source.
- flags = syscall.MS_SLAVE | syscall.MS_REC
- if err := syscall.Mount("", dst, "", uintptr(flags), ""); err != nil {
- return nil, fmt.Errorf("mount rslave dst: %q, flags: %#x, err: %v", dst, flags, err)
- }
-
- cpy := m
- relDst, err := filepath.Rel(spec.Root.Path, dst)
- if err != nil {
- panic(fmt.Sprintf("%q could not be made relative to %q: %v", dst, spec.Root.Path, err))
- }
- cpy.Destination = filepath.Join("/", relDst)
- rv = append(rv, cpy)
- }
-
- if spec.Process.Cwd != "" {
- dst, err := resolveSymlinks(spec.Root.Path, spec.Process.Cwd)
- if err != nil {
- return nil, fmt.Errorf("resolving symlinks to %q: %v", spec.Process.Cwd, err)
- }
- if err := os.MkdirAll(dst, 0755); err != nil {
- return nil, err
- }
- }
-
- // If root is read only, check if it needs to be remounted as readonly.
- if spec.Root.Readonly {
- isMountPoint, readonly, err := mountInfo(spec.Root.Path)
- if err != nil {
- return nil, err
- }
- if readonly {
- return rv, nil
- }
- if !isMountPoint {
- // Readonly root is not a mount point nor read-only. Can't do much other
- // than just logging a warning. The gofer will prevent files to be open
- // in write mode.
- log.Warningf("Mount where root is located is not read-only and cannot be changed: %q", spec.Root.Path)
- return rv, nil
- }
-
- // If root is a mount point but not read-only, we can change mount options
- // to make it read-only for extra safety.
- log.Infof("Remounting root as readonly: %q", spec.Root.Path)
- flags := uintptr(syscall.MS_BIND | syscall.MS_REMOUNT | syscall.MS_RDONLY | syscall.MS_REC)
- src := spec.Root.Path
- if err := syscall.Mount(src, src, "bind", flags, ""); err != nil {
- return nil, fmt.Errorf("remounting root as read-only with source: %q, target: %q, flags: %#x, err: %v", spec.Root.Path, spec.Root.Path, flags, err)
- }
- }
- return rv, nil
-}
-
-// mountInfo returns whether the path is a mount point and whether the mount
-// that path belongs to is read-only.
-func mountInfo(path string) (bool, bool, error) {
- // Mounts are listed by their real paths.
- realPath, err := filepath.EvalSymlinks(path)
- if err != nil {
- return false, false, err
- }
- f, err := os.Open("/proc/mounts")
- if err != nil {
- return false, false, err
- }
- scanner := bufio.NewScanner(f)
-
- var mountPoint string
- var readonly bool
- for scanner.Scan() {
- line := scanner.Text()
- parts := strings.Split(line, " ")
- if len(parts) < 4 {
- return false, false, fmt.Errorf("invalid /proc/mounts line format %q", line)
- }
- mp := parts[1]
- opts := strings.Split(parts[3], ",")
-
- // Find the closest submount to the path.
- if strings.Contains(realPath, mp) && len(mp) > len(mountPoint) {
- mountPoint = mp
- readonly = specutils.ContainsStr(opts, "ro")
- }
- }
- if err := scanner.Err(); err != nil {
- return false, false, err
- }
- return mountPoint == realPath, readonly, nil
-}
-
-// destroyFS unmounts mounts done by runsc under `spec.Root.Path`. This
-// recovers the container rootfs into the original state.
-func destroyFS(spec *specs.Spec) error {
- for _, m := range spec.Mounts {
- if m.Type != "bind" || !specutils.IsSupportedDevMount(m) {
- continue
- }
-
- // It's possible that 'm.Destination' follows symlinks inside the
- // container.
- dst, err := resolveSymlinks(spec.Root.Path, m.Destination)
- if err != nil {
- return err
- }
-
- flags := syscall.MNT_DETACH
- log.Infof("Unmounting dst: %q, flags: %#x", dst, flags)
- // Do not return error if dst is not a mountpoint.
- // Based on http://man7.org/linux/man-pages/man2/umount.2.html
- // For kernel version 2.6+ and MNT_DETACH flag, EINVAL means
- // the dst is not a mount point.
- if err := syscall.Unmount(dst, flags); err != nil &&
- !os.IsNotExist(err) && err != syscall.EINVAL {
- return err
- }
- }
- return nil
-}
-
-// resolveSymlinks walks 'rel' having 'root' as the root directory. If there are
-// symlinks, they are evaluated relative to 'root' to ensure the end result is
-// the same as if the process was running inside the container.
-func resolveSymlinks(root, rel string) (string, error) {
- return resolveSymlinksImpl(root, root, rel, 255)
-}
-
-func resolveSymlinksImpl(root, base, rel string, followCount uint) (string, error) {
- if followCount == 0 {
- return "", fmt.Errorf("too many symlinks to follow, path: %q", filepath.Join(base, rel))
- }
-
- rel = filepath.Clean(rel)
- for _, name := range strings.Split(rel, string(filepath.Separator)) {
- if name == "" {
- continue
- }
- // Note that Join() resolves things like ".." and returns a clean path.
- path := filepath.Join(base, name)
- if !strings.HasPrefix(path, root) {
- // One cannot '..' their way out of root.
- path = root
- continue
- }
- fi, err := os.Lstat(path)
- if err != nil {
- if !os.IsNotExist(err) {
- return "", err
- }
- // Not found means there is no symlink to check. Just keep walking dirs.
- base = path
- continue
- }
- if fi.Mode()&os.ModeSymlink != 0 {
- link, err := os.Readlink(path)
- if err != nil {
- return "", err
- }
- if filepath.IsAbs(link) {
- base = root
- }
- base, err = resolveSymlinksImpl(root, base, link, followCount-1)
- if err != nil {
- return "", err
- }
- continue
- }
- base = path
- }
- return base, nil
-}
-
-func optionsToFlags(opts []string) uint32 {
- var rv uint32
- for _, opt := range opts {
- if m, ok := optionsMap[opt]; ok {
- if m.set {
- rv |= m.val
- } else {
- rv ^= m.val
- }
- } else {
- log.Warningf("Ignoring mount option %q", opt)
- }
- }
- return rv
-}
diff --git a/runsc/container/fs_test.go b/runsc/container/fs_test.go
deleted file mode 100644
index 87cdb078e..000000000
--- a/runsc/container/fs_test.go
+++ /dev/null
@@ -1,158 +0,0 @@
-// Copyright 2018 Google LLC
-//
-// 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.
-
-package container
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "testing"
-
- "gvisor.googlesource.com/gvisor/runsc/test/testutil"
-)
-
-type dir struct {
- rel string
- link string
-}
-
-func construct(root string, dirs []dir) error {
- for _, d := range dirs {
- p := path.Join(root, d.rel)
- if d.link == "" {
- if err := os.MkdirAll(p, 0755); err != nil {
- return fmt.Errorf("error creating dir: %v", err)
- }
- } else {
- if err := os.MkdirAll(path.Dir(p), 0755); err != nil {
- return fmt.Errorf("error creating dir: %v", err)
- }
- if err := os.Symlink(d.link, p); err != nil {
- return fmt.Errorf("error creating symlink: %v", err)
- }
- }
- }
- return nil
-}
-
-func TestResolveSymlinks(t *testing.T) {
- root, err := ioutil.TempDir(testutil.TmpDir(), "root")
- if err != nil {
- t.Fatal("ioutil.TempDir() failed:", err)
- }
- dirs := []dir{
- {"dir1/dir11/dir111/dir1111", ""}, // Just a boring dir
- {"dir1/lnk12", "dir11"}, // Link to sibling
- {"dir1/lnk13", "./dir11"}, // Link to sibling through self
- {"dir1/lnk14", "../dir1/dir11"}, // Link to sibling through parent
- {"dir1/dir15/lnk151", ".."}, // Link to parent
- {"dir1/lnk16", "dir11/dir111"}, // Link to child
- {"dir1/lnk17", "."}, // Link to self
- {"dir1/lnk18", "lnk13"}, // Link to link
- {"lnk2", "dir1/lnk13"}, // Link to link to link
- {"dir3/dir21/lnk211", "../.."}, // Link to root relative
- {"dir3/lnk22", "/"}, // Link to root absolute
- {"dir3/lnk23", "/dir1"}, // Link to dir absolute
- {"dir3/lnk24", "/dir1/lnk12"}, // Link to link absolute
- {"lnk5", "../../.."}, // Link outside root
- }
- if err := construct(root, dirs); err != nil {
- t.Fatal("construct failed:", err)
- }
-
- tests := []struct {
- name string
- rel string
- want string
- compareHost bool
- }{
- {name: "root", rel: "/", want: "/", compareHost: true},
- {name: "basic dir", rel: "/dir1/dir11/dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "dot 1", rel: "/dir1/dir11/./dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "dot 2", rel: "/dir1/././dir11/./././././dir111/.", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "dotdot 1", rel: "/dir1/dir11/../dir15", want: "/dir1/dir15", compareHost: true},
- {name: "dotdot 2", rel: "/dir1/dir11/dir1111/../..", want: "/dir1", compareHost: true},
-
- {name: "link sibling", rel: "/dir1/lnk12", want: "/dir1/dir11", compareHost: true},
- {name: "link sibling + dir", rel: "/dir1/lnk12/dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "link sibling through self", rel: "/dir1/lnk13", want: "/dir1/dir11", compareHost: true},
- {name: "link sibling through parent", rel: "/dir1/lnk14", want: "/dir1/dir11", compareHost: true},
-
- {name: "link parent", rel: "/dir1/dir15/lnk151", want: "/dir1", compareHost: true},
- {name: "link parent + dir", rel: "/dir1/dir15/lnk151/dir11", want: "/dir1/dir11", compareHost: true},
- {name: "link child", rel: "/dir1/lnk16", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "link child + dir", rel: "/dir1/lnk16/dir1111", want: "/dir1/dir11/dir111/dir1111", compareHost: true},
- {name: "link self", rel: "/dir1/lnk17", want: "/dir1", compareHost: true},
- {name: "link self + dir", rel: "/dir1/lnk17/dir11", want: "/dir1/dir11", compareHost: true},
-
- {name: "link^2", rel: "/dir1/lnk18", want: "/dir1/dir11", compareHost: true},
- {name: "link^2 + dir", rel: "/dir1/lnk18/dir111", want: "/dir1/dir11/dir111", compareHost: true},
- {name: "link^3", rel: "/lnk2", want: "/dir1/dir11", compareHost: true},
- {name: "link^3 + dir", rel: "/lnk2/dir111", want: "/dir1/dir11/dir111", compareHost: true},
-
- {name: "link abs", rel: "/dir3/lnk23", want: "/dir1"},
- {name: "link abs + dir", rel: "/dir3/lnk23/dir11", want: "/dir1/dir11"},
- {name: "link^2 abs", rel: "/dir3/lnk24", want: "/dir1/dir11"},
- {name: "link^2 abs + dir", rel: "/dir3/lnk24/dir111", want: "/dir1/dir11/dir111"},
-
- {name: "root link rel", rel: "/dir3/dir21/lnk211", want: "/", compareHost: true},
- {name: "root link abs", rel: "/dir3/lnk22", want: "/"},
- {name: "root contain link", rel: "/lnk5/dir1", want: "/dir1"},
- {name: "root contain dotdot", rel: "/dir1/dir11/../../../../../../../..", want: "/"},
-
- {name: "crazy", rel: "/dir3/dir21/lnk211/dir3/lnk22/dir1/dir11/../../lnk5/dir3/../dir3/lnk24/dir111/dir1111/..", want: "/dir1/dir11/dir111"},
- }
- for _, tst := range tests {
- t.Run(tst.name, func(t *testing.T) {
- got, err := resolveSymlinks(root, tst.rel)
- if err != nil {
- t.Errorf("resolveSymlinks(root, %q) failed: %v", tst.rel, err)
- }
- want := path.Join(root, tst.want)
- if got != want {
- t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, got, want)
- }
- if tst.compareHost {
- // Check that host got to the same end result.
- host, err := filepath.EvalSymlinks(path.Join(root, tst.rel))
- if err != nil {
- t.Errorf("path.EvalSymlinks(root, %q) failed: %v", tst.rel, err)
- }
- if host != got {
- t.Errorf("resolveSymlinks(root, %q) got: %q, want: %q", tst.rel, host, got)
- }
- }
- })
- }
-}
-
-func TestResolveSymlinksLoop(t *testing.T) {
- root, err := ioutil.TempDir(testutil.TmpDir(), "root")
- if err != nil {
- t.Fatal("ioutil.TempDir() failed:", err)
- }
- dirs := []dir{
- {"loop1", "loop2"},
- {"loop2", "loop1"},
- }
- if err := construct(root, dirs); err != nil {
- t.Fatal("construct failed:", err)
- }
- if _, err := resolveSymlinks(root, "loop1"); err == nil {
- t.Errorf("resolveSymlinks() should have failed")
- }
-}