github.com/fcwu/docker@v1.4.2-0.20150115145920-2a69ca89f0df/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 _, mnt := range container.VolumeMounts() {
   123  		mnt.volume.AddContainer(container.ID)
   124  	}
   125  }
   126  
   127  func (container *Container) derefVolumes() {
   128  	for path := range container.VolumePaths() {
   129  		vol := container.daemon.volumes.Get(path)
   130  		if vol == nil {
   131  			log.Debugf("Volume %s was not found and could not be dereferenced", path)
   132  			continue
   133  		}
   134  		vol.RemoveContainer(container.ID)
   135  	}
   136  }
   137  
   138  func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) {
   139  	var mounts = make(map[string]*Mount)
   140  	// Get all the bind mounts
   141  	for _, spec := range container.hostConfig.Binds {
   142  		path, mountToPath, writable, err := parseBindMountSpec(spec)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  		// Check if a volume already exists for this and use it
   147  		vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable)
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  		mounts[mountToPath] = &Mount{
   152  			container:   container,
   153  			volume:      vol,
   154  			MountToPath: mountToPath,
   155  			Writable:    writable,
   156  		}
   157  	}
   158  
   159  	// Get the rest of the volumes
   160  	for path := range container.Config.Volumes {
   161  		// Check if this is already added as a bind-mount
   162  		path = filepath.Clean(path)
   163  		if _, exists := mounts[path]; exists {
   164  			continue
   165  		}
   166  
   167  		// Check if this has already been created
   168  		if _, exists := container.Volumes[path]; exists {
   169  			continue
   170  		}
   171  
   172  		vol, err := container.daemon.volumes.FindOrCreateVolume("", true)
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  		mounts[path] = &Mount{
   177  			container:   container,
   178  			MountToPath: path,
   179  			volume:      vol,
   180  			Writable:    true,
   181  			copyData:    true,
   182  		}
   183  	}
   184  
   185  	return mounts, nil
   186  }
   187  
   188  func parseBindMountSpec(spec string) (string, string, bool, error) {
   189  	var (
   190  		path, mountToPath string
   191  		writable          bool
   192  		arr               = strings.Split(spec, ":")
   193  	)
   194  
   195  	switch len(arr) {
   196  	case 2:
   197  		path = arr[0]
   198  		mountToPath = arr[1]
   199  		writable = true
   200  	case 3:
   201  		path = arr[0]
   202  		mountToPath = arr[1]
   203  		writable = validMountMode(arr[2]) && arr[2] == "rw"
   204  	default:
   205  		return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec)
   206  	}
   207  
   208  	if !filepath.IsAbs(path) {
   209  		return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path)
   210  	}
   211  
   212  	path = filepath.Clean(path)
   213  	mountToPath = filepath.Clean(mountToPath)
   214  	return path, mountToPath, writable, nil
   215  }
   216  
   217  func (container *Container) applyVolumesFrom() error {
   218  	volumesFrom := container.hostConfig.VolumesFrom
   219  
   220  	mountGroups := make([]map[string]*Mount, 0, len(volumesFrom))
   221  
   222  	for _, spec := range volumesFrom {
   223  		mountGroup, err := parseVolumesFromSpec(container.daemon, spec)
   224  		if err != nil {
   225  			return err
   226  		}
   227  		mountGroups = append(mountGroups, mountGroup)
   228  	}
   229  
   230  	for _, mounts := range mountGroups {
   231  		for _, mnt := range mounts {
   232  			mnt.from = mnt.container
   233  			mnt.container = container
   234  			if err := mnt.initialize(); err != nil {
   235  				return err
   236  			}
   237  		}
   238  	}
   239  	return nil
   240  }
   241  
   242  func validMountMode(mode string) bool {
   243  	validModes := map[string]bool{
   244  		"rw": true,
   245  		"ro": true,
   246  	}
   247  
   248  	return validModes[mode]
   249  }
   250  
   251  func (container *Container) setupMounts() error {
   252  	mounts := []execdriver.Mount{
   253  		{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true},
   254  	}
   255  
   256  	if container.HostnamePath != "" {
   257  		mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true})
   258  	}
   259  
   260  	if container.HostsPath != "" {
   261  		mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true})
   262  	}
   263  
   264  	for _, m := range mounts {
   265  		if err := label.SetFileLabel(m.Source, container.MountLabel); err != nil {
   266  			return err
   267  		}
   268  	}
   269  
   270  	// Mount user specified volumes
   271  	// Note, these are not private because you may want propagation of (un)mounts from host
   272  	// volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you
   273  	// want this new mount in the container
   274  	// These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic)
   275  	for _, path := range container.sortedVolumeMounts() {
   276  		mounts = append(mounts, execdriver.Mount{
   277  			Source:      container.Volumes[path],
   278  			Destination: path,
   279  			Writable:    container.VolumesRW[path],
   280  		})
   281  	}
   282  
   283  	container.command.Mounts = mounts
   284  	return nil
   285  }
   286  
   287  func parseVolumesFromSpec(daemon *Daemon, spec string) (map[string]*Mount, error) {
   288  	specParts := strings.SplitN(spec, ":", 2)
   289  	if len(specParts) == 0 {
   290  		return nil, fmt.Errorf("Malformed volumes-from specification: %s", spec)
   291  	}
   292  
   293  	c := daemon.Get(specParts[0])
   294  	if c == nil {
   295  		return nil, fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0])
   296  	}
   297  
   298  	mounts := c.VolumeMounts()
   299  
   300  	if len(specParts) == 2 {
   301  		mode := specParts[1]
   302  		if !validMountMode(mode) {
   303  			return nil, fmt.Errorf("Invalid mode for volumes-from: %s", mode)
   304  		}
   305  
   306  		// Set the mode for the inheritted volume
   307  		for _, mnt := range mounts {
   308  			// Ensure that if the inherited volume is not writable, that we don't make
   309  			// it writable here
   310  			mnt.Writable = mnt.Writable && (mode == "rw")
   311  		}
   312  	}
   313  
   314  	return mounts, nil
   315  }
   316  
   317  func (container *Container) VolumeMounts() map[string]*Mount {
   318  	mounts := make(map[string]*Mount)
   319  
   320  	for mountToPath, path := range container.Volumes {
   321  		if v := container.daemon.volumes.Get(path); v != nil {
   322  			mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]}
   323  		}
   324  	}
   325  
   326  	return mounts
   327  }
   328  
   329  func copyExistingContents(source, destination string) error {
   330  	volList, err := ioutil.ReadDir(source)
   331  	if err != nil {
   332  		return err
   333  	}
   334  
   335  	if len(volList) > 0 {
   336  		srcList, err := ioutil.ReadDir(destination)
   337  		if err != nil {
   338  			return err
   339  		}
   340  
   341  		if len(srcList) == 0 {
   342  			// If the source volume is empty copy files from the root into the volume
   343  			if err := chrootarchive.CopyWithTar(source, destination); err != nil {
   344  				return err
   345  			}
   346  		}
   347  	}
   348  
   349  	return copyOwnership(source, destination)
   350  }
   351  
   352  // copyOwnership copies the permissions and uid:gid of the source file
   353  // into the destination file
   354  func copyOwnership(source, destination string) error {
   355  	var stat syscall.Stat_t
   356  
   357  	if err := syscall.Stat(source, &stat); err != nil {
   358  		return err
   359  	}
   360  
   361  	if err := os.Chown(destination, int(stat.Uid), int(stat.Gid)); err != nil {
   362  		return err
   363  	}
   364  
   365  	return os.Chmod(destination, os.FileMode(stat.Mode))
   366  }