Merge pull request #77 from Random-Liu/unmount-dev-shm

Unmount dev shm and cleanup container when stop/remove sandbox
This commit is contained in:
Lantao Liu 2017-06-16 13:59:47 -07:00 committed by GitHub
commit 9658159534
25 changed files with 1131 additions and 23 deletions

5
Godeps/Godeps.json generated
View File

@ -219,6 +219,11 @@
"Comment": "v2.6.0-rc.1-130-gb38e583",
"Rev": "b38e5838b7b2f2ad48e06ec4b500011976080621"
},
{
"ImportPath": "github.com/docker/docker/pkg/mount",
"Comment": "v1.13.1",
"Rev": "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
},
{
"ImportPath": "github.com/docker/docker/pkg/random",
"Comment": "v1.13.1",

View File

@ -22,6 +22,7 @@ import (
"os"
"github.com/containerd/fifo"
"github.com/docker/docker/pkg/mount"
"golang.org/x/net/context"
"golang.org/x/sys/unix"
)
@ -90,7 +91,12 @@ func (RealOS) Mount(source string, target string, fstype string, flags uintptr,
return unix.Mount(source, target, fstype, flags, data)
}
// Unmount will call unix.Unmount to unmount the file.
// Unmount will call unix.Unmount to unmount the file. The function doesn't
// return error if target is not mounted.
func (RealOS) Unmount(target string, flags int) error {
// TODO(random-liu): Follow symlink to make sure the result is correct.
if mounted, err := mount.Mounted(target); err != nil || !mounted {
return err
}
return unix.Unmount(target, flags)
}

View File

@ -55,59 +55,68 @@ func (c *criContainerdService) StopContainer(ctx context.Context, r *runtime.Sto
if err != nil {
return nil, fmt.Errorf("an error occurred when try to find container %q: %v", r.GetContainerId(), err)
}
id := r.GetContainerId()
if err := c.stopContainer(ctx, meta, time.Duration(r.GetTimeout())*time.Second); err != nil {
return nil, err
}
return &runtime.StopContainerResponse{}, nil
}
// stopContainer stops a container based on the container metadata.
func (c *criContainerdService) stopContainer(ctx context.Context, meta *metadata.ContainerMetadata, timeout time.Duration) error {
id := meta.ID
// Return without error if container is not running. This makes sure that
// stop only takes real action after the container is started.
if meta.State() != runtime.ContainerState_CONTAINER_RUNNING {
glog.V(2).Infof("Container to stop %q is not running, current state %q",
id, criContainerStateToString(meta.State()))
return &runtime.StopContainerResponse{}, nil
return nil
}
if r.GetTimeout() > 0 {
if timeout > 0 {
// TODO(random-liu): [P1] Get stop signal from image config.
stopSignal := unix.SIGTERM
glog.V(2).Infof("Stop container %q with signal %v", id, stopSignal)
_, err = c.taskService.Kill(ctx, &execution.KillRequest{
_, err := c.taskService.Kill(ctx, &execution.KillRequest{
ContainerID: id,
Signal: uint32(stopSignal),
PidOrAll: &execution.KillRequest_All{All: true},
})
if err != nil {
if !isContainerdGRPCNotFoundError(err) && !isRuncProcessAlreadyFinishedError(err) {
return nil, fmt.Errorf("failed to stop container %q: %v", id, err)
return fmt.Errorf("failed to stop container %q: %v", id, err)
}
// Move on to make sure container status is updated.
}
err = c.waitContainerStop(ctx, id, time.Duration(r.GetTimeout())*time.Second)
err = c.waitContainerStop(ctx, id, timeout)
if err == nil {
return &runtime.StopContainerResponse{}, nil
return nil
}
glog.Errorf("Stop container %q timed out: %v", id, err)
}
// Event handler will Delete the container from containerd after it handles the Exited event.
glog.V(2).Infof("Kill container %q", id)
_, err = c.taskService.Kill(ctx, &execution.KillRequest{
_, err := c.taskService.Kill(ctx, &execution.KillRequest{
ContainerID: id,
Signal: uint32(unix.SIGKILL),
PidOrAll: &execution.KillRequest_All{All: true},
})
if err != nil {
if !isContainerdGRPCNotFoundError(err) && !isRuncProcessAlreadyFinishedError(err) {
return nil, fmt.Errorf("failed to kill container %q: %v", id, err)
return fmt.Errorf("failed to kill container %q: %v", id, err)
}
// Move on to make sure container status is updated.
}
// Wait for a fixed timeout until container stop is observed by event monitor.
if err := c.waitContainerStop(ctx, id, killContainerTimeout); err != nil {
return nil, fmt.Errorf("an error occurs during waiting for container %q to stop: %v",
id, err)
return fmt.Errorf("an error occurs during waiting for container %q to stop: %v", id, err)
}
return &runtime.StopContainerResponse{}, nil
return nil
}
// waitContainerStop polls container state until timeout exceeds or container is stopped.

View File

@ -53,8 +53,6 @@ func (c *criContainerdService) RemovePodSandbox(ctx context.Context, r *runtime.
// Use the full sandbox id.
id := sandbox.ID
// TODO(random-liu): [P2] Remove all containers in the sandbox.
// Return error if sandbox container is not fully stopped.
// TODO(random-liu): [P0] Make sure network is torn down, may need to introduce a state.
_, err = c.taskService.Info(ctx, &execution.InfoRequest{ContainerID: id})
@ -73,7 +71,25 @@ func (c *criContainerdService) RemovePodSandbox(ctx context.Context, r *runtime.
glog.V(5).Infof("Remove called for snapshot %q that does not exist", id)
}
// TODO(random-liu): [P0] Cleanup shm created in RunPodSandbox.
// Remove all containers inside the sandbox.
// NOTE(random-liu): container could still be created after this point, Kubelet should
// not rely on this behavior.
// TODO(random-liu): Introduce an intermediate state to avoid container creation after
// this point.
cntrs, err := c.containerStore.List()
if err != nil {
return nil, fmt.Errorf("failed to list all containers: %v", err)
}
for _, cntr := range cntrs {
if cntr.SandboxID != id {
continue
}
_, err = c.RemoveContainer(ctx, &runtime.RemoveContainerRequest{ContainerId: cntr.ID})
if err != nil {
return nil, fmt.Errorf("failed to remove container %q: %v", cntr.ID, err)
}
}
// TODO(random-liu): [P1] Remove permanent namespace once used.
// Cleanup the sandbox root directory.

View File

@ -17,6 +17,7 @@ package server
import (
"fmt"
"testing"
"time"
"github.com/containerd/containerd/api/services/containers"
snapshotapi "github.com/containerd/containerd/api/services/snapshot"
@ -176,3 +177,57 @@ func TestRemovePodSandbox(t *testing.T) {
assert.NotNil(t, res, "remove should be idempotent")
}
}
func TestRemoveContainersInSandbox(t *testing.T) {
testID := "test-id"
testName := "test-name"
testMetadata := metadata.SandboxMetadata{
ID: testID,
Name: testName,
}
testContainersMetadata := []*metadata.ContainerMetadata{
{
ID: "test-cid-1",
Name: "test-cname-1",
SandboxID: testID,
FinishedAt: time.Now().UnixNano(),
},
{
ID: "test-cid-2",
Name: "test-cname-2",
SandboxID: testID,
FinishedAt: time.Now().UnixNano(),
},
{
ID: "test-cid-3",
Name: "test-cname-3",
SandboxID: "other-sandbox-id",
FinishedAt: time.Now().UnixNano(),
},
}
c := newTestCRIContainerdService()
WithFakeSnapshotClient(c)
assert.NoError(t, c.sandboxNameIndex.Reserve(testName, testID))
assert.NoError(t, c.sandboxIDIndex.Add(testID))
assert.NoError(t, c.sandboxStore.Create(testMetadata))
for _, cntr := range testContainersMetadata {
assert.NoError(t, c.containerNameIndex.Reserve(cntr.Name, cntr.ID))
assert.NoError(t, c.containerStore.Create(*cntr))
}
res, err := c.RemovePodSandbox(context.Background(), &runtime.RemovePodSandboxRequest{
PodSandboxId: testID,
})
assert.NoError(t, err)
assert.NotNil(t, res)
meta, err := c.sandboxStore.Get(testID)
assert.Error(t, err)
assert.True(t, metadata.IsNotExistError(err))
assert.Nil(t, meta, "sandbox metadata should be removed")
cntrs, err := c.containerStore.List()
assert.NoError(t, err)
assert.Equal(t, testContainersMetadata[2:], cntrs, "container metadata should be removed")
}

View File

@ -183,7 +183,10 @@ func (c *criContainerdService) RunPodSandbox(ctx context.Context, r *runtime.Run
}
defer func() {
if retErr != nil {
c.cleanupSandboxFiles(sandboxRootDir, config)
if err = c.unmountSandboxFiles(sandboxRootDir, config); err != nil {
glog.Errorf("Failed to unmount sandbox files in %q: %v",
sandboxRootDir, err)
}
}
}()
@ -403,12 +406,15 @@ func parseDNSOptions(servers, searches, options []string) (string, error) {
return resolvContent, nil
}
// cleanupSandboxFiles only unmount files, we rely on the removal of sandbox root directory to remove files.
// Each cleanup task should log error instead of returning, so as to keep on cleanup on error.
func (c *criContainerdService) cleanupSandboxFiles(rootDir string, config *runtime.PodSandboxConfig) {
// unmountSandboxFiles unmount some sandbox files, we rely on the removal of sandbox root directory to
// remove these files. Unmount should *NOT* return error when:
// 1) The mount point is already unmounted.
// 2) The mount point doesn't exist.
func (c *criContainerdService) unmountSandboxFiles(rootDir string, config *runtime.PodSandboxConfig) error {
if !config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetHostIpc() {
if err := c.os.Unmount(getSandboxDevShm(rootDir), unix.MNT_DETACH); err != nil && os.IsNotExist(err) {
glog.Errorf("failed to unmount sandbox shm: %v", err)
if err := c.os.Unmount(getSandboxDevShm(rootDir), unix.MNT_DETACH); err != nil && !os.IsNotExist(err) {
return err
}
}
return nil
}

View File

@ -46,6 +46,25 @@ func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.St
// Use the full sandbox id.
id := sandbox.ID
// Stop all containers inside the sandbox. This terminates the container forcibly,
// and container may still be so production should not rely on this behavior.
// TODO(random-liu): Delete the sandbox container before this after permanent network namespace
// is introduced, so that no container will be started after that.
containers, err := c.containerStore.List()
if err != nil {
return nil, fmt.Errorf("failed to list all containers: %v", err)
}
for _, container := range containers {
if container.SandboxID != id {
continue
}
// Forcibly stop the container. Do not use `StopContainer`, because it introduces a race
// if a container is removed after list.
if err = c.stopContainer(ctx, container, 0); err != nil {
return nil, fmt.Errorf("failed to stop container %q: %v", container.ID, err)
}
}
// Teardown network for sandbox.
_, err = c.os.Stat(sandbox.NetNS)
if err == nil {
@ -58,6 +77,11 @@ func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.St
}
glog.V(2).Infof("TearDown network for sandbox %q successfully", id)
sandboxRoot := getSandboxRootDir(c.rootDir, id)
if err = c.unmountSandboxFiles(sandboxRoot, sandbox.Config); err != nil {
return nil, fmt.Errorf("failed to unmount sandbox files in %q: %v", sandboxRoot, err)
}
// TODO(random-liu): [P1] Handle sandbox container graceful deletion.
// Delete the sandbox container from containerd.
_, err = c.taskService.Delete(ctx, &execution.DeleteRequest{ContainerID: id})
@ -65,6 +89,5 @@ func (c *criContainerdService) StopPodSandbox(ctx context.Context, r *runtime.St
return nil, fmt.Errorf("failed to delete sandbox container %q: %v", id, err)
}
// TODO(random-liu): [P2] Stop all containers inside the sandbox.
return &runtime.StopPodSandboxResponse{}, nil
}

View File

@ -20,7 +20,9 @@ import (
"errors"
"os"
"testing"
"time"
"github.com/containerd/containerd/api/services/execution"
"github.com/containerd/containerd/api/types/task"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
@ -58,6 +60,7 @@ func TestStopPodSandbox(t *testing.T) {
injectErr error
injectStatErr error
injectCNIErr error
injectUnmountErr error
expectErr bool
expectCalls []string
expectedCNICalls []string
@ -115,6 +118,15 @@ func TestStopPodSandbox(t *testing.T) {
injectCNIErr: errors.New("arbitrary error"),
expectCalls: []string{},
},
"stop sandbox with unmount error": {
sandboxTasks: []task.Task{testContainer},
injectSandbox: true,
expectErr: true,
expectedCNICalls: []string{"TearDownPod"},
injectCNIErr: errors.New("arbitrary error"),
injectUnmountErr: errors.New("arbitrary error"),
expectCalls: []string{},
},
} {
t.Logf("TestCase %q", desc)
c := newTestCRIContainerdService()
@ -136,6 +148,9 @@ func TestStopPodSandbox(t *testing.T) {
if test.injectStatErr != nil {
fakeOS.InjectError("Stat", test.injectStatErr)
}
if test.injectUnmountErr != nil {
fakeOS.InjectError("Unmount", test.injectUnmountErr)
}
fakeCNIPlugin.SetFakePodNetwork(testSandbox.NetNS, testSandbox.Config.GetMetadata().GetNamespace(),
testSandbox.Config.GetMetadata().GetName(), testID, sandboxStatusTestIP)
@ -153,3 +168,108 @@ func TestStopPodSandbox(t *testing.T) {
assert.Equal(t, test.expectedCNICalls, fakeCNIPlugin.GetCalledNames())
}
}
func TestStopContainersInSandbox(t *testing.T) {
testID := "test-id"
testSandbox := metadata.SandboxMetadata{
ID: testID,
Name: "test-name",
Config: &runtime.PodSandboxConfig{
Metadata: &runtime.PodSandboxMetadata{
Name: "test-name",
Uid: "test-uid",
Namespace: "test-ns",
}},
NetNS: "test-netns",
}
testContainers := []metadata.ContainerMetadata{
{
ID: "test-cid-1",
Name: "test-cname-1",
SandboxID: testID,
Pid: 2,
StartedAt: time.Now().UnixNano(),
},
{
ID: "test-cid-2",
Name: "test-cname-2",
SandboxID: testID,
Pid: 3,
StartedAt: time.Now().UnixNano(),
},
{
ID: "test-cid-3",
Name: "test-cname-3",
SandboxID: "other-sandbox-id",
Pid: 4,
StartedAt: time.Now().UnixNano(),
},
}
testContainerdContainers := []task.Task{
{
ID: testID,
Pid: 1,
Status: task.StatusRunning,
},
{
ID: "test-cid-1",
Pid: 2,
Status: task.StatusRunning,
},
{
ID: "test-cid-2",
Pid: 3,
Status: task.StatusRunning,
},
{
ID: "test-cid-3",
Pid: 4,
Status: task.StatusRunning,
},
}
c := newTestCRIContainerdService()
fake := servertesting.NewFakeExecutionClient().WithEvents()
defer fake.Stop()
c.taskService = fake
fake.SetFakeTasks(testContainerdContainers)
assert.NoError(t, c.sandboxStore.Create(testSandbox))
assert.NoError(t, c.sandboxIDIndex.Add(testID))
for _, cntr := range testContainers {
assert.NoError(t, c.containerStore.Create(cntr))
}
fakeCNIPlugin := c.netPlugin.(*servertesting.FakeCNIPlugin)
fakeCNIPlugin.SetFakePodNetwork(testSandbox.NetNS, testSandbox.Config.GetMetadata().GetNamespace(),
testSandbox.Config.GetMetadata().GetName(), testID, sandboxStatusTestIP)
eventClient, err := fake.Events(context.Background(), &execution.EventsRequest{})
assert.NoError(t, err)
// Start a simple test event monitor.
go func(e execution.Tasks_EventsClient) {
for {
if err := c.handleEventStream(e); err != nil { // nolint: vetshadow
return
}
}
}(eventClient)
res, err := c.StopPodSandbox(context.Background(), &runtime.StopPodSandboxRequest{
PodSandboxId: testID,
})
assert.NoError(t, err)
assert.NotNil(t, res)
cntrs, err := c.containerStore.List()
assert.NoError(t, err)
assert.Len(t, cntrs, 3)
expectedStates := map[string]runtime.ContainerState{
"test-cid-1": runtime.ContainerState_CONTAINER_EXITED,
"test-cid-2": runtime.ContainerState_CONTAINER_EXITED,
"test-cid-3": runtime.ContainerState_CONTAINER_RUNNING,
}
for id, expected := range expectedStates {
cntr, err := c.containerStore.Get(id)
assert.NoError(t, err)
assert.Equal(t, expected, cntr.State())
}
}

149
vendor/github.com/docker/docker/pkg/mount/flags.go generated vendored Normal file
View File

@ -0,0 +1,149 @@
package mount
import (
"fmt"
"strings"
)
var flags = map[string]struct {
clear bool
flag int
}{
"defaults": {false, 0},
"ro": {false, RDONLY},
"rw": {true, RDONLY},
"suid": {true, NOSUID},
"nosuid": {false, NOSUID},
"dev": {true, NODEV},
"nodev": {false, NODEV},
"exec": {true, NOEXEC},
"noexec": {false, NOEXEC},
"sync": {false, SYNCHRONOUS},
"async": {true, SYNCHRONOUS},
"dirsync": {false, DIRSYNC},
"remount": {false, REMOUNT},
"mand": {false, MANDLOCK},
"nomand": {true, MANDLOCK},
"atime": {true, NOATIME},
"noatime": {false, NOATIME},
"diratime": {true, NODIRATIME},
"nodiratime": {false, NODIRATIME},
"bind": {false, BIND},
"rbind": {false, RBIND},
"unbindable": {false, UNBINDABLE},
"runbindable": {false, RUNBINDABLE},
"private": {false, PRIVATE},
"rprivate": {false, RPRIVATE},
"shared": {false, SHARED},
"rshared": {false, RSHARED},
"slave": {false, SLAVE},
"rslave": {false, RSLAVE},
"relatime": {false, RELATIME},
"norelatime": {true, RELATIME},
"strictatime": {false, STRICTATIME},
"nostrictatime": {true, STRICTATIME},
}
var validFlags = map[string]bool{
"": true,
"size": true,
"mode": true,
"uid": true,
"gid": true,
"nr_inodes": true,
"nr_blocks": true,
"mpol": true,
}
var propagationFlags = map[string]bool{
"bind": true,
"rbind": true,
"unbindable": true,
"runbindable": true,
"private": true,
"rprivate": true,
"shared": true,
"rshared": true,
"slave": true,
"rslave": true,
}
// MergeTmpfsOptions merge mount options to make sure there is no duplicate.
func MergeTmpfsOptions(options []string) ([]string, error) {
// We use collisions maps to remove duplicates.
// For flag, the key is the flag value (the key for propagation flag is -1)
// For data=value, the key is the data
flagCollisions := map[int]bool{}
dataCollisions := map[string]bool{}
var newOptions []string
// We process in reverse order
for i := len(options) - 1; i >= 0; i-- {
option := options[i]
if option == "defaults" {
continue
}
if f, ok := flags[option]; ok && f.flag != 0 {
// There is only one propagation mode
key := f.flag
if propagationFlags[option] {
key = -1
}
// Check to see if there is collision for flag
if !flagCollisions[key] {
// We prepend the option and add to collision map
newOptions = append([]string{option}, newOptions...)
flagCollisions[key] = true
}
continue
}
opt := strings.SplitN(option, "=", 2)
if len(opt) != 2 || !validFlags[opt[0]] {
return nil, fmt.Errorf("Invalid tmpfs option %q", opt)
}
if !dataCollisions[opt[0]] {
// We prepend the option and add to collision map
newOptions = append([]string{option}, newOptions...)
dataCollisions[opt[0]] = true
}
}
return newOptions, nil
}
// Parse fstab type mount options into mount() flags
// and device specific data
func parseOptions(options string) (int, string) {
var (
flag int
data []string
)
for _, o := range strings.Split(options, ",") {
// If the option does not exist in the flags table or the flag
// is not supported on the platform,
// then it is a data value for a specific fs type
if f, exists := flags[o]; exists && f.flag != 0 {
if f.clear {
flag &= ^f.flag
} else {
flag |= f.flag
}
} else {
data = append(data, o)
}
}
return flag, strings.Join(data, ",")
}
// ParseTmpfsOptions parse fstab type mount options into flags and data
func ParseTmpfsOptions(options string) (int, string, error) {
flags, data := parseOptions(options)
for _, o := range strings.Split(data, ",") {
opt := strings.SplitN(o, "=", 2)
if !validFlags[opt[0]] {
return 0, "", fmt.Errorf("Invalid tmpfs option %q", opt)
}
}
return flags, data, nil
}

View File

@ -0,0 +1,48 @@
// +build freebsd,cgo
package mount
/*
#include <sys/mount.h>
*/
import "C"
const (
// RDONLY will mount the filesystem as read-only.
RDONLY = C.MNT_RDONLY
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
// take effect.
NOSUID = C.MNT_NOSUID
// NOEXEC will not allow execution of any binaries on the mounted file system.
NOEXEC = C.MNT_NOEXEC
// SYNCHRONOUS will allow any I/O to the file system to be done synchronously.
SYNCHRONOUS = C.MNT_SYNCHRONOUS
// NOATIME will not update the file access time when reading from a file.
NOATIME = C.MNT_NOATIME
)
// These flags are unsupported.
const (
BIND = 0
DIRSYNC = 0
MANDLOCK = 0
NODEV = 0
NODIRATIME = 0
UNBINDABLE = 0
RUNBINDABLE = 0
PRIVATE = 0
RPRIVATE = 0
SHARED = 0
RSHARED = 0
SLAVE = 0
RSLAVE = 0
RBIND = 0
RELATIVE = 0
RELATIME = 0
REMOUNT = 0
STRICTATIME = 0
)

View File

@ -0,0 +1,85 @@
package mount
import (
"syscall"
)
const (
// RDONLY will mount the file system read-only.
RDONLY = syscall.MS_RDONLY
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
// take effect.
NOSUID = syscall.MS_NOSUID
// NODEV will not interpret character or block special devices on the file
// system.
NODEV = syscall.MS_NODEV
// NOEXEC will not allow execution of any binaries on the mounted file system.
NOEXEC = syscall.MS_NOEXEC
// SYNCHRONOUS will allow I/O to the file system to be done synchronously.
SYNCHRONOUS = syscall.MS_SYNCHRONOUS
// DIRSYNC will force all directory updates within the file system to be done
// synchronously. This affects the following system calls: create, link,
// unlink, symlink, mkdir, rmdir, mknod and rename.
DIRSYNC = syscall.MS_DIRSYNC
// REMOUNT will attempt to remount an already-mounted file system. This is
// commonly used to change the mount flags for a file system, especially to
// make a readonly file system writeable. It does not change device or mount
// point.
REMOUNT = syscall.MS_REMOUNT
// MANDLOCK will force mandatory locks on a filesystem.
MANDLOCK = syscall.MS_MANDLOCK
// NOATIME will not update the file access time when reading from a file.
NOATIME = syscall.MS_NOATIME
// NODIRATIME will not update the directory access time.
NODIRATIME = syscall.MS_NODIRATIME
// BIND remounts a subtree somewhere else.
BIND = syscall.MS_BIND
// RBIND remounts a subtree and all possible submounts somewhere else.
RBIND = syscall.MS_BIND | syscall.MS_REC
// UNBINDABLE creates a mount which cannot be cloned through a bind operation.
UNBINDABLE = syscall.MS_UNBINDABLE
// RUNBINDABLE marks the entire mount tree as UNBINDABLE.
RUNBINDABLE = syscall.MS_UNBINDABLE | syscall.MS_REC
// PRIVATE creates a mount which carries no propagation abilities.
PRIVATE = syscall.MS_PRIVATE
// RPRIVATE marks the entire mount tree as PRIVATE.
RPRIVATE = syscall.MS_PRIVATE | syscall.MS_REC
// SLAVE creates a mount which receives propagation from its master, but not
// vice versa.
SLAVE = syscall.MS_SLAVE
// RSLAVE marks the entire mount tree as SLAVE.
RSLAVE = syscall.MS_SLAVE | syscall.MS_REC
// SHARED creates a mount which provides the ability to create mirrors of
// that mount such that mounts and unmounts within any of the mirrors
// propagate to the other mirrors.
SHARED = syscall.MS_SHARED
// RSHARED marks the entire mount tree as SHARED.
RSHARED = syscall.MS_SHARED | syscall.MS_REC
// RELATIME updates inode access times relative to modify or change time.
RELATIME = syscall.MS_RELATIME
// STRICTATIME allows to explicitly request full atime updates. This makes
// it possible for the kernel to default to relatime or noatime but still
// allow userspace to override it.
STRICTATIME = syscall.MS_STRICTATIME
)

View File

@ -0,0 +1,30 @@
// +build !linux,!freebsd freebsd,!cgo solaris,!cgo
package mount
// These flags are unsupported.
const (
BIND = 0
DIRSYNC = 0
MANDLOCK = 0
NOATIME = 0
NODEV = 0
NODIRATIME = 0
NOEXEC = 0
NOSUID = 0
UNBINDABLE = 0
RUNBINDABLE = 0
PRIVATE = 0
RPRIVATE = 0
SHARED = 0
RSHARED = 0
SLAVE = 0
RSLAVE = 0
RBIND = 0
RELATIME = 0
RELATIVE = 0
REMOUNT = 0
STRICTATIME = 0
SYNCHRONOUS = 0
RDONLY = 0
)

74
vendor/github.com/docker/docker/pkg/mount/mount.go generated vendored Normal file
View File

@ -0,0 +1,74 @@
package mount
import (
"time"
)
// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
return parseMountTable()
}
// Mounted determines if a specified mountpoint has been mounted.
// On Linux it looks at /proc/self/mountinfo and on Solaris at mnttab.
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
}
// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
flag, _ := parseOptions(options)
if flag&REMOUNT != REMOUNT {
if mounted, err := Mounted(target); err != nil || mounted {
return err
}
}
return ForceMount(device, target, mType, options)
}
// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
flag, data := parseOptions(options)
if err := mount(device, target, mType, uintptr(flag), data); err != nil {
return err
}
return nil
}
// Unmount will unmount the target filesystem, so long as it is mounted.
func Unmount(target string) error {
if mounted, err := Mounted(target); err != nil || !mounted {
return err
}
return ForceUnmount(target)
}
// ForceUnmount will force an unmount of the target filesystem, regardless if
// it is mounted or not.
func ForceUnmount(target string) (err error) {
// Simple retry logic for unmount
for i := 0; i < 10; i++ {
if err = unmount(target, 0); err == nil {
return nil
}
time.Sleep(100 * time.Millisecond)
}
return
}

View File

@ -0,0 +1,59 @@
package mount
/*
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_iovec.h>
#include <sys/mount.h>
#include <sys/param.h>
*/
import "C"
import (
"fmt"
"strings"
"syscall"
"unsafe"
)
func allocateIOVecs(options []string) []C.struct_iovec {
out := make([]C.struct_iovec, len(options))
for i, option := range options {
out[i].iov_base = unsafe.Pointer(C.CString(option))
out[i].iov_len = C.size_t(len(option) + 1)
}
return out
}
func mount(device, target, mType string, flag uintptr, data string) error {
isNullFS := false
xs := strings.Split(data, ",")
for _, x := range xs {
if x == "bind" {
isNullFS = true
}
}
options := []string{"fspath", target}
if isNullFS {
options = append(options, "fstype", "nullfs", "target", device)
} else {
options = append(options, "fstype", mType, "from", device)
}
rawOptions := allocateIOVecs(options)
for _, rawOption := range rawOptions {
defer C.free(rawOption.iov_base)
}
if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
reason := C.GoString(C.strerror(*C.__error()))
return fmt.Errorf("Failed to call nmount: %s", reason)
}
return nil
}
func unmount(target string, flag int) error {
return syscall.Unmount(target, flag)
}

View File

@ -0,0 +1,21 @@
package mount
import (
"syscall"
)
func mount(device, target, mType string, flag uintptr, data string) error {
if err := syscall.Mount(device, target, mType, flag, data); err != nil {
return err
}
// If we have a bind mount or remount, remount...
if flag&syscall.MS_BIND == syscall.MS_BIND && flag&syscall.MS_RDONLY == syscall.MS_RDONLY {
return syscall.Mount(device, target, mType, flag|syscall.MS_REMOUNT, data)
}
return nil
}
func unmount(target string, flag int) error {
return syscall.Unmount(target, flag)
}

View File

@ -0,0 +1,33 @@
// +build solaris,cgo
package mount
import (
"golang.org/x/sys/unix"
"unsafe"
)
// #include <stdlib.h>
// #include <stdio.h>
// #include <sys/mount.h>
// int Mount(const char *spec, const char *dir, int mflag,
// char *fstype, char *dataptr, int datalen, char *optptr, int optlen) {
// return mount(spec, dir, mflag, fstype, dataptr, datalen, optptr, optlen);
// }
import "C"
func mount(device, target, mType string, flag uintptr, data string) error {
spec := C.CString(device)
dir := C.CString(target)
fstype := C.CString(mType)
_, err := C.Mount(spec, dir, C.int(flag), fstype, nil, 0, nil, 0)
C.free(unsafe.Pointer(spec))
C.free(unsafe.Pointer(dir))
C.free(unsafe.Pointer(fstype))
return err
}
func unmount(target string, flag int) error {
err := unix.Unmount(target, flag)
return err
}

View File

@ -0,0 +1,11 @@
// +build !linux,!freebsd,!solaris freebsd,!cgo solaris,!cgo
package mount
func mount(device, target, mType string, flag uintptr, data string) error {
panic("Not implemented")
}
func unmount(target string, flag int) error {
panic("Not implemented")
}

40
vendor/github.com/docker/docker/pkg/mount/mountinfo.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package mount
// Info reveals information about a particular mounted filesystem. This
// struct is populated from the content in the /proc/<pid>/mountinfo file.
type Info struct {
// ID is a unique identifier of the mount (may be reused after umount).
ID int
// Parent indicates the ID of the mount parent (or of self for the top of the
// mount tree).
Parent int
// Major indicates one half of the device ID which identifies the device class.
Major int
// Minor indicates one half of the device ID which identifies a specific
// instance of device.
Minor int
// Root of the mount within the filesystem.
Root string
// Mountpoint indicates the mount point relative to the process's root.
Mountpoint string
// Opts represents mount-specific options.
Opts string
// Optional represents optional fields.
Optional string
// Fstype indicates the type of filesystem, such as EXT3.
Fstype string
// Source indicates filesystem specific information or "none".
Source string
// VfsOpts represents per super block options.
VfsOpts string
}

View File

@ -0,0 +1,41 @@
package mount
/*
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
*/
import "C"
import (
"fmt"
"reflect"
"unsafe"
)
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
// bind mounts.
func parseMountTable() ([]*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
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
out = append(out, &mountinfo)
}
return out, nil
}

View File

@ -0,0 +1,95 @@
// +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
}
// 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)
}

View File

@ -0,0 +1,37 @@
// +build solaris,cgo
package mount
/*
#include <stdio.h>
#include <sys/mnttab.h>
*/
import "C"
import (
"fmt"
)
func parseMountTable() ([]*Info, error) {
mnttab := C.fopen(C.CString(C.MNTTAB), C.CString("r"))
if mnttab == nil {
return nil, fmt.Errorf("Failed to open %s", C.MNTTAB)
}
var out []*Info
var mp C.struct_mnttab
ret := C.getmntent(mnttab, &mp)
for ret == 0 {
var mountinfo Info
mountinfo.Mountpoint = C.GoString(mp.mnt_mountp)
mountinfo.Source = C.GoString(mp.mnt_special)
mountinfo.Fstype = C.GoString(mp.mnt_fstype)
mountinfo.Opts = C.GoString(mp.mnt_mntopts)
out = append(out, &mountinfo)
ret = C.getmntent(mnttab, &mp)
}
C.fclose(mnttab)
return out, nil
}

View File

@ -0,0 +1,12 @@
// +build !windows,!linux,!freebsd,!solaris freebsd,!cgo solaris,!cgo
package mount
import (
"fmt"
"runtime"
)
func parseMountTable() ([]*Info, error) {
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}

View File

@ -0,0 +1,6 @@
package mount
func parseMountTable() ([]*Info, error) {
// Do NOT return an error!
return nil, nil
}

View File

@ -0,0 +1,69 @@
// +build linux
package mount
// MakeShared ensures a mounted filesystem has the SHARED mount option enabled.
// See the supported options in flags.go for further reference.
func MakeShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "shared")
}
// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled.
// See the supported options in flags.go for further reference.
func MakeRShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "rshared")
}
// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled.
// See the supported options in flags.go for further reference.
func MakePrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "private")
}
// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option
// enabled. See the supported options in flags.go for further reference.
func MakeRPrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "rprivate")
}
// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled.
// See the supported options in flags.go for further reference.
func MakeSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "slave")
}
// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled.
// See the supported options in flags.go for further reference.
func MakeRSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "rslave")
}
// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option
// enabled. See the supported options in flags.go for further reference.
func MakeUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "unbindable")
}
// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount
// option enabled. See the supported options in flags.go for further reference.
func MakeRUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "runbindable")
}
func ensureMountedAs(mountPoint, options string) error {
mounted, err := Mounted(mountPoint)
if err != nil {
return err
}
if !mounted {
if err := Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil {
return err
}
}
if _, err = Mounted(mountPoint); err != nil {
return err
}
return ForceMount("", mountPoint, "none", options)
}

View File

@ -0,0 +1,58 @@
// +build solaris
package mount
// MakeShared ensures a mounted filesystem has the SHARED mount option enabled.
// See the supported options in flags.go for further reference.
func MakeShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "shared")
}
// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled.
// See the supported options in flags.go for further reference.
func MakeRShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "rshared")
}
// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled.
// See the supported options in flags.go for further reference.
func MakePrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "private")
}
// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option
// enabled. See the supported options in flags.go for further reference.
func MakeRPrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "rprivate")
}
// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled.
// See the supported options in flags.go for further reference.
func MakeSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "slave")
}
// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled.
// See the supported options in flags.go for further reference.
func MakeRSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "rslave")
}
// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option
// enabled. See the supported options in flags.go for further reference.
func MakeUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "unbindable")
}
// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount
// option enabled. See the supported options in flags.go for further reference.
func MakeRUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "runbindable")
}
func ensureMountedAs(mountPoint, options string) error {
// TODO: Solaris does not support bind mounts.
// Evaluate lofs and also look at the relevant
// mount flags to be supported.
return nil
}