github.com/nguyentm83/docker@v1.5.0/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  	"syscall"
    12  
    13  	log "github.com/Sirupsen/logrus"
    14  	"github.com/docker/docker/daemon/execdriver"
    15  	"github.com/docker/docker/pkg/chrootarchive"
    16  	"github.com/docker/docker/pkg/symlink"
    17  	"github.com/docker/docker/volumes"
    18  	"github.com/docker/libcontainer/label"
    19  )
    20  
    21  type Mount struct {
    22  	MountToPath string
    23  	container   *Container
    24  	volume      *volumes.Volume
    25  	Writable    bool
    26  	copyData    bool
    27  	from        *Container
    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.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  		v := m.container.daemon.volumes.Get(hostPath)
    93  		v.RemoveContainer(m.container.ID)
    94  		m.container.daemon.volumes.Delete(v.Path)
    95  	}
    96  
    97  	// This is the full path to container fs + mntToPath
    98  	containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	m.container.VolumesRW[m.MountToPath] = m.Writable
   103  	m.container.Volumes[m.MountToPath] = m.volume.Path
   104  	m.volume.AddContainer(m.container.ID)
   105  	if m.Writable && m.copyData {
   106  		// Copy whatever is in the container at the mntToPath to the volume
   107  		copyExistingContents(containerMntPath, m.volume.Path)
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  func (container *Container) VolumePaths() map[string]struct{} {
   114  	var paths = make(map[string]struct{})
   115  	for _, path := range container.Volumes {
   116  		paths[path] = struct{}{}
   117  	}
   118  	return paths
   119  }
   120  
   121  func (container *Container) registerVolumes() {
   122  	for path := range container.VolumePaths() {
   123  		if v := container.daemon.volumes.Get(path); v != nil {
   124  			v.AddContainer(container.ID)
   125  			continue
   126  		}
   127  
   128  		// if container was created with an old daemon, this volume may not be registered so we need to make sure it gets registered
   129  		writable := true
   130  		if rw, exists := container.VolumesRW[path]; exists {
   131  			writable = rw
   132  		}
   133  		v, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
   134  		if err != nil {
   135  			log.Debugf("error registering volume %s: %v", path, err)
   136  			continue
   137  		}
   138  		v.AddContainer(container.ID)
   139  	}
   140  }
   141  
   142  func (container *Container) derefVolumes() {
   143  	for path := range container.VolumePaths() {
   144  		vol := container.daemon.volumes.Get(path)
   145  		if vol == nil {
   146  			log.Debugf("Volume %s was not found and could not be dereferenced", path)
   147  			continue
   148  		}
   149  		vol.RemoveContainer(container.ID)
   150  	}
   151  }
   152  
   153  func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
   154  	var mounts = make(map[string]*Mount)
   155  	// Get all the bind mounts
   156  	for _, spec := range container.hostConfig.Binds {
   157  		path, mountToPath, writable, err := parseBindMountSpec(spec)
   158  		if err != nil {
   159  			return nil, err
   160  		}
   161  		// Check if a volume already exists for this and use it
   162  		vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
   163  		if err != nil {
   164  			return nil, err
   165  		}
   166  		mounts[mountToPath] = &Mount{
   167  			container:   container,
   168  			volume:      vol,
   169  			MountToPath: mountToPath,
   170  			Writable:    writable,
   171  		}
   172  	}
   173  
   174  	// Get the rest of the volumes
   175  	for path := range container.Config.Volumes {
   176  		// Check if this is already added as a bind-mount
   177  		path = filepath.Clean(path)
   178  		if _, exists := mounts[path]; exists {
   179  			continue
   180  		}
   181  
   182  		// Check if this has already been created
   183  		if _, exists := container.Volumes[path]; exists {
   184  			continue
   185  		}
   186  
   187  		vol, err := container.daemon.volumes.FindOrCreateVolume("", true)
   188  		if err != nil {
   189  			return nil, err
   190  		}
   191  		mounts[path] = &Mount{
   192  			container:   container,
   193  			MountToPath: path,
   194  			volume:      vol,
   195  			Writable:    true,
   196  			copyData:    true,
   197  		}
   198  	}
   199  
   200  	return mounts, nil
   201  }
   202  
   203  func parseBindMountSpec(spec string) (string, string, bool, error) {
   204  	var (
   205  		path, mountToPath string
   206  		writable          bool
   207  		arr               = strings.Split(spec, ":")
   208  	)
   209  
   210  	switch len(arr) {
   211  	case 2:
   212  		path = arr[0]
   213  		mountToPath = arr[1]
   214  		writable = true
   215  	case 3:
   216  		path = arr[0]
   217  		mountToPath = arr[1]
   218  		writable = validMountMode(arr[2]) && arr[2] == "rw"
   219  	default:
   220  		return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
   221  	}
   222  
   223  	if !filepath.IsAbs(path) {
   224  		return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
   225  	}
   226  
   227  	path = filepath.Clean(path)
   228  	mountToPath = filepath.Clean(mountToPath)
   229  	return path, mountToPath, writable, nil
   230  }
   231  
   232  func parseVolumesFromSpec(spec string) (string, string, error) {
   233  	specParts := strings.SplitN(spec, ":", 2)
   234  	if len(specParts) == 0 {
   235  		return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec)
   236  	}
   237  
   238  	var (
   239  		id   = specParts[0]
   240  		mode = "rw"
   241  	)
   242  	if len(specParts) == 2 {
   243  		mode = specParts[1]
   244  		if !validMountMode(mode) {
   245  			return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode)
   246  		}
   247  	}
   248  	return id, mode, nil
   249  }
   250  
   251  func (container *Container) applyVolumesFrom() error {
   252  	volumesFrom := container.hostConfig.VolumesFrom
   253  	if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil {
   254  		container.AppliedVolumesFrom = make(map[string]struct{})
   255  	}
   256  
   257  	mountGroups := make(map[string][]*Mount)
   258  
   259  	for _, spec := range volumesFrom {
   260  		id, mode, err := parseVolumesFromSpec(spec)
   261  		if err != nil {
   262  			return err
   263  		}
   264  		if _, exists := container.AppliedVolumesFrom[id]; exists {
   265  			// Don't try to apply these since they've already been applied
   266  			continue
   267  		}
   268  
   269  		c := container.daemon.Get(id)
   270  		if c == nil {
   271  			return fmt.Errorf("container %s not found, impossible to mount its volumes", id)
   272  		}
   273  
   274  		var (
   275  			fromMounts = c.VolumeMounts()
   276  			mounts     []*Mount
   277  		)
   278  
   279  		for _, mnt := range fromMounts {
   280  			mnt.Writable = mnt.Writable && (mode == "rw")
   281  			mounts = append(mounts, mnt)
   282  		}
   283  		mountGroups[id] = mounts
   284  	}
   285  
   286  	for id, mounts := range mountGroups {
   287  		for _, mnt := range mounts {
   288  			mnt.from = mnt.container
   289  			mnt.container = container
   290  			if err := mnt.initialize(); err != nil {
   291  				return err
   292  			}
   293  		}
   294  		container.AppliedVolumesFrom[id] = struct{}{}
   295  	}
   296  	return nil
   297  }
   298  
   299  func validMountMode(mode string) bool {
   300  	validModes := map[string]bool{
   301  		"rw": true,
   302  		"ro": true,
   303  	}
   304  
   305  	return validModes[mode]
   306  }
   307  
   308  func (container *Container) setupMounts() error {
   309  	mounts := []execdriver.Mount{
   310  		{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true},
   311  	}
   312  
   313  	if container.HostnamePath != "" {
   314  		mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
   315  	}
   316  
   317  	if container.HostsPath != "" {
   318  		mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true})
   319  	}
   320  
   321  	for _, m := range mounts {
   322  		if err := label.SetFileLabel(m.Source, container.MountLabel); err != nil {
   323  			return err
   324  		}
   325  	}
   326  
   327  	// Mount user specified volumes
   328  	// Note, these are not private because you may want propagation of (un)mounts from host
   329  	// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
   330  	// want this new mount in the container
   331  	// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
   332  	for _, path := range container.sortedVolumeMounts() {
   333  		mounts = append(mounts, execdriver.Mount{
   334  			Source:      container.Volumes[path],
   335  			Destination: path,
   336  			Writable:    container.VolumesRW[path],
   337  		})
   338  	}
   339  
   340  	container.command.Mounts = mounts
   341  	return nil
   342  }
   343  
   344  func (container *Container) VolumeMounts() map[string]*Mount {
   345  	mounts := make(map[string]*Mount)
   346  
   347  	for mountToPath, path := range container.Volumes {
   348  		if v := container.daemon.volumes.Get(path); v != nil {
   349  			mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
   350  		}
   351  	}
   352  
   353  	return mounts
   354  }
   355  
   356  func copyExistingContents(source, destination string) error {
   357  	volList, err := ioutil.ReadDir(source)
   358  	if err != nil {
   359  		return err
   360  	}
   361  
   362  	if len(volList) > 0 {
   363  		srcList, err := ioutil.ReadDir(destination)
   364  		if err != nil {
   365  			return err
   366  		}
   367  
   368  		if len(srcList) == 0 {
   369  			// If the source volume is empty copy files from the root into the volume
   370  			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
   371  				return err
   372  			}
   373  		}
   374  	}
   375  
   376  	return copyOwnership(source, destination)
   377  }
   378  
   379  // copyOwnership copies the permissions and uid:gid of the source file
   380  // into the destination file
   381  func copyOwnership(source, destination string) error {
   382  	var stat syscall.Stat_t
   383  
   384  	if err := syscall.Stat(source, &stat); err != nil {
   385  		return err
   386  	}
   387  
   388  	if err := os.Chown(destination, int(stat.Uid), int(stat.Gid)); err != nil {
   389  		return err
   390  	}
   391  
   392  	return os.Chmod(destination, os.FileMode(stat.Mode))
   393  }