github.com/leoh0/docker@v1.6.0-rc4/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  		realPath, err := container.getResourcePath(path)
   193  		if err != nil {
   194  			return nil, fmt.Errorf("failed to evaluate the absolute path of symlink")
   195  		}
   196  		if stat, err := os.Stat(realPath); err == nil {
   197  			if !stat.IsDir() {
   198  				return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath)
   199  			}
   200  		}
   201  
   202  		vol, err := container.daemon.volumes.FindOrCreateVolume("", true)
   203  		if err != nil {
   204  			return nil, err
   205  		}
   206  		mounts[path] = &Mount{
   207  			container:   container,
   208  			MountToPath: path,
   209  			volume:      vol,
   210  			Writable:    true,
   211  			copyData:    true,
   212  		}
   213  	}
   214  
   215  	return mounts, nil
   216  }
   217  
   218  func parseBindMountSpec(spec string) (string, string, bool, error) {
   219  	var (
   220  		path, mountToPath string
   221  		writable          bool
   222  		arr               = strings.Split(spec, ":")
   223  	)
   224  
   225  	switch len(arr) {
   226  	case 2:
   227  		path = arr[0]
   228  		mountToPath = arr[1]
   229  		writable = true
   230  	case 3:
   231  		path = arr[0]
   232  		mountToPath = arr[1]
   233  		writable = validMountMode(arr[2]) && arr[2] == "rw"
   234  	default:
   235  		return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
   236  	}
   237  
   238  	if !filepath.IsAbs(path) {
   239  		return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
   240  	}
   241  
   242  	path = filepath.Clean(path)
   243  	mountToPath = filepath.Clean(mountToPath)
   244  	return path, mountToPath, writable, nil
   245  }
   246  
   247  func parseVolumesFromSpec(spec string) (string, string, error) {
   248  	specParts := strings.SplitN(spec, ":", 2)
   249  	if len(specParts) == 0 {
   250  		return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec)
   251  	}
   252  
   253  	var (
   254  		id   = specParts[0]
   255  		mode = "rw"
   256  	)
   257  	if len(specParts) == 2 {
   258  		mode = specParts[1]
   259  		if !validMountMode(mode) {
   260  			return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode)
   261  		}
   262  	}
   263  	return id, mode, nil
   264  }
   265  
   266  func (container *Container) applyVolumesFrom() error {
   267  	volumesFrom := container.hostConfig.VolumesFrom
   268  	if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil {
   269  		container.AppliedVolumesFrom = make(map[string]struct{})
   270  	}
   271  
   272  	mountGroups := make(map[string][]*Mount)
   273  
   274  	for _, spec := range volumesFrom {
   275  		id, mode, err := parseVolumesFromSpec(spec)
   276  		if err != nil {
   277  			return err
   278  		}
   279  		if _, exists := container.AppliedVolumesFrom[id]; exists {
   280  			// Don't try to apply these since they've already been applied
   281  			continue
   282  		}
   283  
   284  		c, err := container.daemon.Get(id)
   285  		if err != nil {
   286  			return fmt.Errorf("Could not apply volumes of non-existent container %q.", id)
   287  		}
   288  
   289  		var (
   290  			fromMounts = c.VolumeMounts()
   291  			mounts     []*Mount
   292  		)
   293  
   294  		for _, mnt := range fromMounts {
   295  			mnt.Writable = mnt.Writable && (mode == "rw")
   296  			mounts = append(mounts, mnt)
   297  		}
   298  		mountGroups[id] = mounts
   299  	}
   300  
   301  	for id, mounts := range mountGroups {
   302  		for _, mnt := range mounts {
   303  			mnt.from = mnt.container
   304  			mnt.container = container
   305  			if err := mnt.initialize(); err != nil {
   306  				return err
   307  			}
   308  		}
   309  		container.AppliedVolumesFrom[id] = struct{}{}
   310  	}
   311  	return nil
   312  }
   313  
   314  func validMountMode(mode string) bool {
   315  	validModes := map[string]bool{
   316  		"rw": true,
   317  		"ro": true,
   318  	}
   319  
   320  	return validModes[mode]
   321  }
   322  
   323  func (container *Container) setupMounts() error {
   324  	mounts := []execdriver.Mount{}
   325  
   326  	// Mount user specified volumes
   327  	// Note, these are not private because you may want propagation of (un)mounts from host
   328  	// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
   329  	// want this new mount in the container
   330  	// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
   331  	for _, path := range container.sortedVolumeMounts() {
   332  		mounts = append(mounts, execdriver.Mount{
   333  			Source:      container.Volumes[path],
   334  			Destination: path,
   335  			Writable:    container.VolumesRW[path],
   336  		})
   337  	}
   338  
   339  	if container.ResolvConfPath != "" {
   340  		mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true})
   341  	}
   342  
   343  	if container.HostnamePath != "" {
   344  		mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
   345  	}
   346  
   347  	if container.HostsPath != "" {
   348  		mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true})
   349  	}
   350  
   351  	container.command.Mounts = mounts
   352  	return nil
   353  }
   354  
   355  func (container *Container) VolumeMounts() map[string]*Mount {
   356  	mounts := make(map[string]*Mount)
   357  
   358  	for mountToPath, path := range container.Volumes {
   359  		if v := container.daemon.volumes.Get(path); v != nil {
   360  			mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
   361  		}
   362  	}
   363  
   364  	return mounts
   365  }
   366  
   367  func copyExistingContents(source, destination string) error {
   368  	volList, err := ioutil.ReadDir(source)
   369  	if err != nil {
   370  		return err
   371  	}
   372  
   373  	if len(volList) > 0 {
   374  		srcList, err := ioutil.ReadDir(destination)
   375  		if err != nil {
   376  			return err
   377  		}
   378  
   379  		if len(srcList) == 0 {
   380  			// If the source volume is empty copy files from the root into the volume
   381  			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
   382  				return err
   383  			}
   384  		}
   385  	}
   386  
   387  	return copyOwnership(source, destination)
   388  }
   389  
   390  // copyOwnership copies the permissions and uid:gid of the source file
   391  // into the destination file
   392  func copyOwnership(source, destination string) error {
   393  	stat, err := system.Stat(source)
   394  	if err != nil {
   395  		return err
   396  	}
   397  
   398  	if err := os.Chown(destination, int(stat.Uid()), int(stat.Gid())); err != nil {
   399  		return err
   400  	}
   401  
   402  	return os.Chmod(destination, os.FileMode(stat.Mode()))
   403  }