github.com/erriapo/docker@v1.6.0-rc2/daemon/volumes.go (about)

     1  package daemon
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  
    12  	log "github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/daemon/execdriver"
    14  	"github.com/docker/docker/pkg/chrootarchive"
    15  	"github.com/docker/docker/pkg/symlink"
    16  	"github.com/docker/docker/pkg/system"
    17  	"github.com/docker/docker/volumes"
    18  )
    19  
    20  type Mount struct {
    21  	MountToPath string
    22  	container   *Container
    23  	volume      *volumes.Volume
    24  	Writable    bool
    25  	copyData    bool
    26  	from        *Container
    27  	isBind      bool
    28  }
    29  
    30  func (mnt *Mount) Export(resource string) (io.ReadCloser, error) {
    31  	var name string
    32  	if resource == mnt.MountToPath[1:] {
    33  		name = filepath.Base(resource)
    34  	}
    35  	path, err := filepath.Rel(mnt.MountToPath[1:], resource)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	return mnt.volume.Export(path, name)
    40  }
    41  
    42  func (container *Container) prepareVolumes() error {
    43  	if container.Volumes == nil || len(container.Volumes) == 0 {
    44  		container.Volumes = make(map[string]string)
    45  		container.VolumesRW = make(map[string]bool)
    46  	}
    47  
    48  	return container.createVolumes()
    49  }
    50  
    51  // sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order
    52  func (container *Container) sortedVolumeMounts() []string {
    53  	var mountPaths []string
    54  	for path := range container.Volumes {
    55  		mountPaths = append(mountPaths, path)
    56  	}
    57  
    58  	sort.Strings(mountPaths)
    59  	return mountPaths
    60  }
    61  
    62  func (container *Container) createVolumes() error {
    63  	mounts, err := container.parseVolumeMountConfig()
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	for _, mnt := range mounts {
    69  		if err := mnt.initialize(); err != nil {
    70  			return err
    71  		}
    72  	}
    73  
    74  	// On every start, this will apply any new `VolumesFrom` entries passed in via HostConfig, which may override volumes set in `create`
    75  	return container.applyVolumesFrom()
    76  }
    77  
    78  func (m *Mount) initialize() error {
    79  	// No need to initialize anything since it's already been initialized
    80  	if hostPath, exists := m.container.Volumes[m.MountToPath]; exists {
    81  		// If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create
    82  		// We need to make sure bind-mounts/volumes-from passed on start can override existing ones.
    83  		if (!m.volume.IsBindMount && !m.isBind) && m.from == nil {
    84  			return nil
    85  		}
    86  		if m.volume.Path == hostPath {
    87  			return nil
    88  		}
    89  
    90  		// Make sure we remove these old volumes we don't actually want now.
    91  		// Ignore any errors here since this is just cleanup, maybe someone volumes-from'd this volume
    92  		if v := m.container.daemon.volumes.Get(hostPath); v != nil {
    93  			v.RemoveContainer(m.container.ID)
    94  			m.container.daemon.volumes.Delete(v.Path)
    95  		}
    96  	}
    97  
    98  	// This is the full path to container fs + mntToPath
    99  	containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	m.container.VolumesRW[m.MountToPath] = m.Writable
   104  	m.container.Volumes[m.MountToPath] = m.volume.Path
   105  	m.volume.AddContainer(m.container.ID)
   106  	if m.Writable && m.copyData {
   107  		// Copy whatever is in the container at the mntToPath to the volume
   108  		copyExistingContents(containerMntPath, m.volume.Path)
   109  	}
   110  
   111  	return nil
   112  }
   113  
   114  func (container *Container) VolumePaths() map[string]struct{} {
   115  	var paths = make(map[string]struct{})
   116  	for _, path := range container.Volumes {
   117  		paths[path] = struct{}{}
   118  	}
   119  	return paths
   120  }
   121  
   122  func (container *Container) registerVolumes() {
   123  	for path := range container.VolumePaths() {
   124  		if v := container.daemon.volumes.Get(path); v != nil {
   125  			v.AddContainer(container.ID)
   126  			continue
   127  		}
   128  
   129  		// if container was created with an old daemon, this volume may not be registered so we need to make sure it gets registered
   130  		writable := true
   131  		if rw, exists := container.VolumesRW[path]; exists {
   132  			writable = rw
   133  		}
   134  		v, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
   135  		if err != nil {
   136  			log.Debugf("error registering volume %s: %v", path, err)
   137  			continue
   138  		}
   139  		v.AddContainer(container.ID)
   140  	}
   141  }
   142  
   143  func (container *Container) derefVolumes() {
   144  	for path := range container.VolumePaths() {
   145  		vol := container.daemon.volumes.Get(path)
   146  		if vol == nil {
   147  			log.Debugf("Volume %s was not found and could not be dereferenced", path)
   148  			continue
   149  		}
   150  		vol.RemoveContainer(container.ID)
   151  	}
   152  }
   153  
   154  func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
   155  	var mounts = make(map[string]*Mount)
   156  	// Get all the bind mounts
   157  	for _, spec := range container.hostConfig.Binds {
   158  		path, mountToPath, writable, err := parseBindMountSpec(spec)
   159  		if err != nil {
   160  			return nil, err
   161  		}
   162  		// Check if a bind mount has already been specified for the same container path
   163  		if m, exists := mounts[mountToPath]; exists {
   164  			return nil, fmt.Errorf("Duplicate volume %q: %q already in use, mounted from %q", path, mountToPath, m.volume.Path)
   165  		}
   166  		// Check if a volume already exists for this and use it
   167  		vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  		mounts[mountToPath] = &Mount{
   172  			container:   container,
   173  			volume:      vol,
   174  			MountToPath: mountToPath,
   175  			Writable:    writable,
   176  			isBind:      true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here
   177  		}
   178  	}
   179  
   180  	// Get the rest of the volumes
   181  	for path := range container.Config.Volumes {
   182  		// Check if this is already added as a bind-mount
   183  		path = filepath.Clean(path)
   184  		if _, exists := mounts[path]; exists {
   185  			continue
   186  		}
   187  
   188  		// Check if this has already been created
   189  		if _, exists := container.Volumes[path]; exists {
   190  			continue
   191  		}
   192  
   193  		if stat, err := os.Stat(filepath.Join(container.basefs, path)); err == nil {
   194  			if !stat.IsDir() {
   195  				return nil, fmt.Errorf("file exists at %s, can't create volume there")
   196  			}
   197  		}
   198  
   199  		vol, err := container.daemon.volumes.FindOrCreateVolume("", true)
   200  		if err != nil {
   201  			return nil, err
   202  		}
   203  		mounts[path] = &Mount{
   204  			container:   container,
   205  			MountToPath: path,
   206  			volume:      vol,
   207  			Writable:    true,
   208  			copyData:    true,
   209  		}
   210  	}
   211  
   212  	return mounts, nil
   213  }
   214  
   215  func parseBindMountSpec(spec string) (string, string, bool, error) {
   216  	var (
   217  		path, mountToPath string
   218  		writable          bool
   219  		arr               = strings.Split(spec, ":")
   220  	)
   221  
   222  	switch len(arr) {
   223  	case 2:
   224  		path = arr[0]
   225  		mountToPath = arr[1]
   226  		writable = true
   227  	case 3:
   228  		path = arr[0]
   229  		mountToPath = arr[1]
   230  		writable = validMountMode(arr[2]) && arr[2] == "rw"
   231  	default:
   232  		return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
   233  	}
   234  
   235  	if !filepath.IsAbs(path) {
   236  		return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
   237  	}
   238  
   239  	path = filepath.Clean(path)
   240  	mountToPath = filepath.Clean(mountToPath)
   241  	return path, mountToPath, writable, nil
   242  }
   243  
   244  func parseVolumesFromSpec(spec string) (string, string, error) {
   245  	specParts := strings.SplitN(spec, ":", 2)
   246  	if len(specParts) == 0 {
   247  		return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec)
   248  	}
   249  
   250  	var (
   251  		id   = specParts[0]
   252  		mode = "rw"
   253  	)
   254  	if len(specParts) == 2 {
   255  		mode = specParts[1]
   256  		if !validMountMode(mode) {
   257  			return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode)
   258  		}
   259  	}
   260  	return id, mode, nil
   261  }
   262  
   263  func (container *Container) applyVolumesFrom() error {
   264  	volumesFrom := container.hostConfig.VolumesFrom
   265  	if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil {
   266  		container.AppliedVolumesFrom = make(map[string]struct{})
   267  	}
   268  
   269  	mountGroups := make(map[string][]*Mount)
   270  
   271  	for _, spec := range volumesFrom {
   272  		id, mode, err := parseVolumesFromSpec(spec)
   273  		if err != nil {
   274  			return err
   275  		}
   276  		if _, exists := container.AppliedVolumesFrom[id]; exists {
   277  			// Don't try to apply these since they've already been applied
   278  			continue
   279  		}
   280  
   281  		c, err := container.daemon.Get(id)
   282  		if err != nil {
   283  			return fmt.Errorf("Could not apply volumes of non-existent container %q.", id)
   284  		}
   285  
   286  		var (
   287  			fromMounts = c.VolumeMounts()
   288  			mounts     []*Mount
   289  		)
   290  
   291  		for _, mnt := range fromMounts {
   292  			mnt.Writable = mnt.Writable && (mode == "rw")
   293  			mounts = append(mounts, mnt)
   294  		}
   295  		mountGroups[id] = mounts
   296  	}
   297  
   298  	for id, mounts := range mountGroups {
   299  		for _, mnt := range mounts {
   300  			mnt.from = mnt.container
   301  			mnt.container = container
   302  			if err := mnt.initialize(); err != nil {
   303  				return err
   304  			}
   305  		}
   306  		container.AppliedVolumesFrom[id] = struct{}{}
   307  	}
   308  	return nil
   309  }
   310  
   311  func validMountMode(mode string) bool {
   312  	validModes := map[string]bool{
   313  		"rw": true,
   314  		"ro": true,
   315  	}
   316  
   317  	return validModes[mode]
   318  }
   319  
   320  func (container *Container) setupMounts() error {
   321  	mounts := []execdriver.Mount{}
   322  
   323  	// Mount user specified volumes
   324  	// Note, these are not private because you may want propagation of (un)mounts from host
   325  	// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
   326  	// want this new mount in the container
   327  	// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
   328  	for _, path := range container.sortedVolumeMounts() {
   329  		mounts = append(mounts, execdriver.Mount{
   330  			Source:      container.Volumes[path],
   331  			Destination: path,
   332  			Writable:    container.VolumesRW[path],
   333  		})
   334  	}
   335  
   336  	if container.ResolvConfPath != "" {
   337  		mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true})
   338  	}
   339  
   340  	if container.HostnamePath != "" {
   341  		mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
   342  	}
   343  
   344  	if container.HostsPath != "" {
   345  		mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true})
   346  	}
   347  
   348  	container.command.Mounts = mounts
   349  	return nil
   350  }
   351  
   352  func (container *Container) VolumeMounts() map[string]*Mount {
   353  	mounts := make(map[string]*Mount)
   354  
   355  	for mountToPath, path := range container.Volumes {
   356  		if v := container.daemon.volumes.Get(path); v != nil {
   357  			mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
   358  		}
   359  	}
   360  
   361  	return mounts
   362  }
   363  
   364  func copyExistingContents(source, destination string) error {
   365  	volList, err := ioutil.ReadDir(source)
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	if len(volList) > 0 {
   371  		srcList, err := ioutil.ReadDir(destination)
   372  		if err != nil {
   373  			return err
   374  		}
   375  
   376  		if len(srcList) == 0 {
   377  			// If the source volume is empty copy files from the root into the volume
   378  			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
   379  				return err
   380  			}
   381  		}
   382  	}
   383  
   384  	return copyOwnership(source, destination)
   385  }
   386  
   387  // copyOwnership copies the permissions and uid:gid of the source file
   388  // into the destination file
   389  func copyOwnership(source, destination string) error {
   390  	stat, err := system.Stat(source)
   391  	if err != nil {
   392  		return err
   393  	}
   394  
   395  	if err := os.Chown(destination, int(stat.Uid()), int(stat.Gid())); err != nil {
   396  		return err
   397  	}
   398  
   399  	return os.Chmod(destination, os.FileMode(stat.Mode()))
   400  }