mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
bd94f84ded
As explained in https://github.com/dotcloud/docker/issues/4979 --volumes-from fails with ENOFILE errors. This is because the code tries to look at the "from" volume without ensuring that it is mounted yet. We fix this by mounting the containers before stating in it. Also includes a regression test. Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)
290 lines
7.4 KiB
Go
290 lines
7.4 KiB
Go
package runtime
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/dotcloud/docker/archive"
|
|
"github.com/dotcloud/docker/runtime/execdriver"
|
|
"github.com/dotcloud/docker/utils"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
type BindMap struct {
|
|
SrcPath string
|
|
DstPath string
|
|
Mode string
|
|
}
|
|
|
|
func prepareVolumesForContainer(container *Container) error {
|
|
if container.Volumes == nil || len(container.Volumes) == 0 {
|
|
container.Volumes = make(map[string]string)
|
|
container.VolumesRW = make(map[string]bool)
|
|
if err := applyVolumesFrom(container); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := createVolumes(container); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setupMountsForContainer(container *Container, envPath string) error {
|
|
mounts := []execdriver.Mount{
|
|
{container.runtime.sysInitPath, "/.dockerinit", false, true},
|
|
{envPath, "/.dockerenv", false, true},
|
|
{container.ResolvConfPath, "/etc/resolv.conf", false, true},
|
|
}
|
|
|
|
if container.HostnamePath != "" && container.HostsPath != "" {
|
|
mounts = append(mounts, execdriver.Mount{container.HostnamePath, "/etc/hostname", false, true})
|
|
mounts = append(mounts, execdriver.Mount{container.HostsPath, "/etc/hosts", false, true})
|
|
}
|
|
|
|
// Mount user specified volumes
|
|
// Note, these are not private because you may want propagation of (un)mounts from host
|
|
// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
|
|
// want this new mount in the container
|
|
for r, v := range container.Volumes {
|
|
mounts = append(mounts, execdriver.Mount{v, r, container.VolumesRW[r], false})
|
|
}
|
|
|
|
container.command.Mounts = mounts
|
|
|
|
return nil
|
|
}
|
|
|
|
func applyVolumesFrom(container *Container) error {
|
|
if container.Config.VolumesFrom != "" {
|
|
for _, containerSpec := range strings.Split(container.Config.VolumesFrom, ",") {
|
|
var (
|
|
mountRW = true
|
|
specParts = strings.SplitN(containerSpec, ":", 2)
|
|
)
|
|
|
|
switch len(specParts) {
|
|
case 0:
|
|
return fmt.Errorf("Malformed volumes-from specification: %s", container.Config.VolumesFrom)
|
|
case 2:
|
|
switch specParts[1] {
|
|
case "ro":
|
|
mountRW = false
|
|
case "rw": // mountRW is already true
|
|
default:
|
|
return fmt.Errorf("Malformed volumes-from specification: %s", containerSpec)
|
|
}
|
|
}
|
|
|
|
c := container.runtime.Get(specParts[0])
|
|
if c == nil {
|
|
return fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0])
|
|
}
|
|
|
|
if err := c.Mount(); err != nil {
|
|
return fmt.Errorf("Container %s failed to mount. Impossible to mount its volumes", specParts[0])
|
|
}
|
|
defer c.Unmount()
|
|
|
|
for volPath, id := range c.Volumes {
|
|
if _, exists := container.Volumes[volPath]; exists {
|
|
continue
|
|
}
|
|
stat, err := os.Stat(filepath.Join(c.basefs, volPath))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := createIfNotExists(filepath.Join(container.basefs, volPath), stat.IsDir()); err != nil {
|
|
return err
|
|
}
|
|
container.Volumes[volPath] = id
|
|
if isRW, exists := c.VolumesRW[volPath]; exists {
|
|
container.VolumesRW[volPath] = isRW && mountRW
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getBindMap(container *Container) (map[string]BindMap, error) {
|
|
var (
|
|
// Create the requested bind mounts
|
|
binds = make(map[string]BindMap)
|
|
// Define illegal container destinations
|
|
illegalDsts = []string{"/", "."}
|
|
)
|
|
|
|
for _, bind := range container.hostConfig.Binds {
|
|
// FIXME: factorize bind parsing in parseBind
|
|
var (
|
|
src, dst, mode string
|
|
arr = strings.Split(bind, ":")
|
|
)
|
|
|
|
if len(arr) == 2 {
|
|
src = arr[0]
|
|
dst = arr[1]
|
|
mode = "rw"
|
|
} else if len(arr) == 3 {
|
|
src = arr[0]
|
|
dst = arr[1]
|
|
mode = arr[2]
|
|
} else {
|
|
return nil, fmt.Errorf("Invalid bind specification: %s", bind)
|
|
}
|
|
|
|
// Bail if trying to mount to an illegal destination
|
|
for _, illegal := range illegalDsts {
|
|
if dst == illegal {
|
|
return nil, fmt.Errorf("Illegal bind destination: %s", dst)
|
|
}
|
|
}
|
|
|
|
bindMap := BindMap{
|
|
SrcPath: src,
|
|
DstPath: dst,
|
|
Mode: mode,
|
|
}
|
|
binds[filepath.Clean(dst)] = bindMap
|
|
}
|
|
return binds, nil
|
|
}
|
|
|
|
func createVolumes(container *Container) error {
|
|
binds, err := getBindMap(container)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
volumesDriver := container.runtime.volumes.Driver()
|
|
// Create the requested volumes if they don't exist
|
|
for volPath := range container.Config.Volumes {
|
|
volPath = filepath.Clean(volPath)
|
|
volIsDir := true
|
|
// Skip existing volumes
|
|
if _, exists := container.Volumes[volPath]; exists {
|
|
continue
|
|
}
|
|
var srcPath string
|
|
var isBindMount bool
|
|
srcRW := false
|
|
// If an external bind is defined for this volume, use that as a source
|
|
if bindMap, exists := binds[volPath]; exists {
|
|
isBindMount = true
|
|
srcPath = bindMap.SrcPath
|
|
srcAbs, err := filepath.Abs(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if srcPath != srcAbs {
|
|
return fmt.Errorf("%s should be an absolute path", srcPath)
|
|
}
|
|
if strings.ToLower(bindMap.Mode) == "rw" {
|
|
srcRW = true
|
|
}
|
|
if stat, err := os.Stat(bindMap.SrcPath); err != nil {
|
|
return err
|
|
} else {
|
|
volIsDir = stat.IsDir()
|
|
}
|
|
// Otherwise create an directory in $ROOT/volumes/ and use that
|
|
} else {
|
|
|
|
// Do not pass a container as the parameter for the volume creation.
|
|
// The graph driver using the container's information ( Image ) to
|
|
// create the parent.
|
|
c, err := container.runtime.volumes.Create(nil, "", "", "", "", nil, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srcPath, err = volumesDriver.Get(c.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("Driver %s failed to get volume rootfs %s: %s", volumesDriver, c.ID, err)
|
|
}
|
|
srcRW = true // RW by default
|
|
}
|
|
|
|
if p, err := filepath.EvalSymlinks(srcPath); err != nil {
|
|
return err
|
|
} else {
|
|
srcPath = p
|
|
}
|
|
|
|
container.Volumes[volPath] = srcPath
|
|
container.VolumesRW[volPath] = srcRW
|
|
|
|
// Create the mountpoint
|
|
volPath = filepath.Join(container.basefs, volPath)
|
|
rootVolPath, err := utils.FollowSymlinkInScope(volPath, container.basefs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := createIfNotExists(rootVolPath, volIsDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Do not copy or change permissions if we are mounting from the host
|
|
if srcRW && !isBindMount {
|
|
volList, err := ioutil.ReadDir(rootVolPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(volList) > 0 {
|
|
srcList, err := ioutil.ReadDir(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(srcList) == 0 {
|
|
// If the source volume is empty copy files from the root into the volume
|
|
if err := archive.CopyWithTar(rootVolPath, srcPath); err != nil {
|
|
return err
|
|
}
|
|
|
|
var stat syscall.Stat_t
|
|
if err := syscall.Stat(rootVolPath, &stat); err != nil {
|
|
return err
|
|
}
|
|
var srcStat syscall.Stat_t
|
|
if err := syscall.Stat(srcPath, &srcStat); err != nil {
|
|
return err
|
|
}
|
|
// Change the source volume's ownership if it differs from the root
|
|
// files that were just copied
|
|
if stat.Uid != srcStat.Uid || stat.Gid != srcStat.Gid {
|
|
if err := os.Chown(srcPath, int(stat.Uid), int(stat.Gid)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createIfNotExists(path string, isDir bool) error {
|
|
if _, err := os.Stat(path); err != nil {
|
|
if os.IsNotExist(err) {
|
|
if isDir {
|
|
if err := os.MkdirAll(path, 0755); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return err
|
|
}
|
|
f, err := os.OpenFile(path, os.O_CREATE, 0755)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|