ef98fe0763
Fixes #1992 Right now when you `docker cp` a path which is in a volume, the cp itself works, however you end up getting files that are in the container's fs rather than the files in the volume (which is not in the container's fs). This makes it so when you `docker cp` a path that is in a volume it follows the volume to the real path on the host. archive.go has been modified so that when you do `docker cp mydata:/foo .`, and /foo is the volume, the outputed folder is called "foo" instead of the volume ID (because we are telling it to tar up `/var/lib/docker/vfs/dir/<some id>` and not "foo", but the user would be expecting "foo", not the ID Signed-off-by: Brian Goff <cpuguy83@gmail.com>
338 lines
8.3 KiB
Go
338 lines
8.3 KiB
Go
package daemon
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/docker/docker/daemon/execdriver"
|
|
"github.com/docker/docker/pkg/archive"
|
|
"github.com/docker/docker/pkg/log"
|
|
"github.com/docker/docker/pkg/symlink"
|
|
"github.com/docker/docker/volumes"
|
|
)
|
|
|
|
type Mount struct {
|
|
MountToPath string
|
|
container *Container
|
|
volume *volumes.Volume
|
|
Writable bool
|
|
copyData bool
|
|
}
|
|
|
|
func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
|
|
var name string
|
|
if resource == mnt.MountToPath[1:] {
|
|
name = filepath.Base(resource)
|
|
}
|
|
path, err := filepath.Rel(mnt.MountToPath[1:], resource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return mnt.volume.Export(path, name)
|
|
}
|
|
|
|
func (container *Container) prepareVolumes() error {
|
|
if container.Volumes == nil || len(container.Volumes) == 0 {
|
|
container.Volumes = make(map[string]string)
|
|
container.VolumesRW = make(map[string]bool)
|
|
if err := container.applyVolumesFrom(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return container.createVolumes()
|
|
}
|
|
|
|
// sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
|
|
func (container *Container) sortedVolumeMounts() []string {
|
|
var mountPaths []string
|
|
for path := range container.Volumes {
|
|
mountPaths = append(mountPaths, path)
|
|
}
|
|
|
|
sort.Strings(mountPaths)
|
|
return mountPaths
|
|
}
|
|
|
|
func (container *Container) createVolumes() error {
|
|
mounts, err := container.parseVolumeMountConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, mnt := range mounts {
|
|
if err := mnt.initialize(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *Mount) initialize() error {
|
|
// No need to initialize anything since it's already been initialized
|
|
if _, exists := m.container.Volumes[m.MountToPath]; exists {
|
|
return nil
|
|
}
|
|
|
|
// This is the full path to container fs + mntToPath
|
|
containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
m.container.VolumesRW[m.MountToPath] = m.Writable
|
|
m.container.Volumes[m.MountToPath] = m.volume.Path
|
|
m.volume.AddContainer(m.container.ID)
|
|
if m.Writable && m.copyData {
|
|
// Copy whatever is in the container at the mntToPath to the volume
|
|
copyExistingContents(containerMntPath, m.volume.Path)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (container *Container) VolumePaths() map[string]struct{} {
|
|
var paths = make(map[string]struct{})
|
|
for _, path := range container.Volumes {
|
|
paths[path] = struct{}{}
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func (container *Container) registerVolumes() {
|
|
for _, mnt := range container.VolumeMounts() {
|
|
mnt.volume.AddContainer(container.ID)
|
|
}
|
|
}
|
|
|
|
func (container *Container) derefVolumes() {
|
|
for path := range container.VolumePaths() {
|
|
vol := container.daemon.volumes.Get(path)
|
|
if vol == nil {
|
|
log.Debugf("Volume %s was not found and could not be dereferenced", path)
|
|
continue
|
|
}
|
|
vol.RemoveContainer(container.ID)
|
|
}
|
|
}
|
|
|
|
func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
|
|
var mounts = make(map[string]*Mount)
|
|
// Get all the bind mounts
|
|
for _, spec := range container.hostConfig.Binds {
|
|
path, mountToPath, writable, err := parseBindMountSpec(spec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Check if a volume already exists for this and use it
|
|
vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mounts[mountToPath] = &Mount{
|
|
container: container,
|
|
volume: vol,
|
|
MountToPath: mountToPath,
|
|
Writable: writable,
|
|
}
|
|
}
|
|
|
|
// Get the rest of the volumes
|
|
for path := range container.Config.Volumes {
|
|
// Check if this is already added as a bind-mount
|
|
if _, exists := mounts[path]; exists {
|
|
continue
|
|
}
|
|
|
|
// Check if this has already been created
|
|
if _, exists := container.Volumes[path]; exists {
|
|
continue
|
|
}
|
|
|
|
vol, err := container.daemon.volumes.FindOrCreateVolume("", true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mounts[path] = &Mount{
|
|
container: container,
|
|
MountToPath: path,
|
|
volume: vol,
|
|
Writable: true,
|
|
copyData: true,
|
|
}
|
|
}
|
|
|
|
return mounts, nil
|
|
}
|
|
|
|
func parseBindMountSpec(spec string) (string, string, bool, error) {
|
|
var (
|
|
path, mountToPath string
|
|
writable bool
|
|
arr = strings.Split(spec, ":")
|
|
)
|
|
|
|
switch len(arr) {
|
|
case 2:
|
|
path = arr[0]
|
|
mountToPath = arr[1]
|
|
writable = true
|
|
case 3:
|
|
path = arr[0]
|
|
mountToPath = arr[1]
|
|
writable = validMountMode(arr[2]) && arr[2] == "rw"
|
|
default:
|
|
return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
|
|
}
|
|
|
|
if !filepath.IsAbs(path) {
|
|
return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
|
|
}
|
|
|
|
return path, mountToPath, writable, nil
|
|
}
|
|
|
|
func (container *Container) applyVolumesFrom() error {
|
|
volumesFrom := container.hostConfig.VolumesFrom
|
|
|
|
for _, spec := range volumesFrom {
|
|
mounts, err := parseVolumesFromSpec(container.daemon, spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, mnt := range mounts {
|
|
mnt.container = container
|
|
if err = mnt.initialize(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validMountMode(mode string) bool {
|
|
validModes := map[string]bool{
|
|
"rw": true,
|
|
"ro": true,
|
|
}
|
|
|
|
return validModes[mode]
|
|
}
|
|
|
|
func (container *Container) setupMounts() error {
|
|
mounts := []execdriver.Mount{
|
|
{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true},
|
|
}
|
|
|
|
if container.HostnamePath != "" {
|
|
mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
|
|
}
|
|
|
|
if container.HostsPath != "" {
|
|
mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: 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
|
|
// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
|
|
for _, path := range container.sortedVolumeMounts() {
|
|
mounts = append(mounts, execdriver.Mount{
|
|
Source: container.Volumes[path],
|
|
Destination: path,
|
|
Writable: container.VolumesRW[path],
|
|
})
|
|
}
|
|
|
|
container.command.Mounts = mounts
|
|
return nil
|
|
}
|
|
|
|
func parseVolumesFromSpec(daemon *Daemon, spec string) (map[string]*Mount, error) {
|
|
specParts := strings.SplitN(spec, ":", 2)
|
|
if len(specParts) == 0 {
|
|
return nil, fmt.Errorf("Malformed volumes-from specification: %s", spec)
|
|
}
|
|
|
|
c := daemon.Get(specParts[0])
|
|
if c == nil {
|
|
return nil, fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0])
|
|
}
|
|
|
|
mounts := c.VolumeMounts()
|
|
|
|
if len(specParts) == 2 {
|
|
mode := specParts[1]
|
|
if !validMountMode(mode) {
|
|
return nil, fmt.Errorf("Invalid mode for volumes-from: %s", mode)
|
|
}
|
|
|
|
// Set the mode for the inheritted volume
|
|
for _, mnt := range mounts {
|
|
// Ensure that if the inherited volume is not writable, that we don't make
|
|
// it writable here
|
|
mnt.Writable = mnt.Writable && (mode == "rw")
|
|
}
|
|
}
|
|
|
|
return mounts, nil
|
|
}
|
|
|
|
func (container *Container) VolumeMounts() map[string]*Mount {
|
|
mounts := make(map[string]*Mount)
|
|
|
|
for mountToPath, path := range container.Volumes {
|
|
if v := container.daemon.volumes.Get(path); v != nil {
|
|
mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
|
|
}
|
|
}
|
|
|
|
return mounts
|
|
}
|
|
|
|
func copyExistingContents(source, destination string) error {
|
|
volList, err := ioutil.ReadDir(source)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(volList) > 0 {
|
|
srcList, err := ioutil.ReadDir(destination)
|
|
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(source, destination); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return copyOwnership(source, destination)
|
|
}
|
|
|
|
// copyOwnership copies the permissions and uid:gid of the source file
|
|
// into the destination file
|
|
func copyOwnership(source, destination string) error {
|
|
var stat syscall.Stat_t
|
|
|
|
if err := syscall.Stat(source, &stat); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.Chown(destination, int(stat.Uid), int(stat.Gid)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.Chmod(destination, os.FileMode(stat.Mode))
|
|
}
|