github.com/mheon/docker@v0.11.2-0.20150922122814-44f47903a831/daemon/volumes_unix.go (about)

     1  // +build !windows
     2  
     3  package daemon
     4  
     5  import (
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	"github.com/docker/docker/daemon/execdriver"
    14  	derr "github.com/docker/docker/errors"
    15  	"github.com/docker/docker/pkg/system"
    16  	"github.com/docker/docker/runconfig"
    17  	"github.com/docker/docker/volume"
    18  	volumedrivers "github.com/docker/docker/volume/drivers"
    19  	"github.com/docker/docker/volume/local"
    20  	"github.com/opencontainers/runc/libcontainer/label"
    21  )
    22  
    23  // copyOwnership copies the permissions and uid:gid of the source file
    24  // to the destination file
    25  func copyOwnership(source, destination string) error {
    26  	stat, err := system.Stat(source)
    27  	if err != nil {
    28  		return err
    29  	}
    30  
    31  	if err := os.Chown(destination, int(stat.UID()), int(stat.Gid())); err != nil {
    32  		return err
    33  	}
    34  
    35  	return os.Chmod(destination, os.FileMode(stat.Mode()))
    36  }
    37  
    38  // setupMounts iterates through each of the mount points for a container and
    39  // calls Setup() on each. It also looks to see if is a network mount such as
    40  // /etc/resolv.conf, and if it is not, appends it to the array of mounts.
    41  func (container *Container) setupMounts() ([]execdriver.Mount, error) {
    42  	var mounts []execdriver.Mount
    43  	for _, m := range container.MountPoints {
    44  		path, err := m.Setup()
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  		if !container.trySetNetworkMount(m.Destination, path) {
    49  			mounts = append(mounts, execdriver.Mount{
    50  				Source:      path,
    51  				Destination: m.Destination,
    52  				Writable:    m.RW,
    53  			})
    54  		}
    55  	}
    56  
    57  	mounts = sortMounts(mounts)
    58  	return append(mounts, container.networkMounts()...), nil
    59  }
    60  
    61  // parseBindMount validates the configuration of mount information in runconfig is valid.
    62  func parseBindMount(spec, volumeDriver string) (*mountPoint, error) {
    63  	bind := &mountPoint{
    64  		RW: true,
    65  	}
    66  	arr := strings.Split(spec, ":")
    67  
    68  	switch len(arr) {
    69  	case 2:
    70  		bind.Destination = arr[1]
    71  	case 3:
    72  		bind.Destination = arr[1]
    73  		mode := arr[2]
    74  		if !volume.ValidMountMode(mode) {
    75  			return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mode)
    76  		}
    77  		bind.RW = volume.ReadWrite(mode)
    78  		// Mode field is used by SELinux to decide whether to apply label
    79  		bind.Mode = mode
    80  	default:
    81  		return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec)
    82  	}
    83  
    84  	//validate the volumes destination path
    85  	if !filepath.IsAbs(bind.Destination) {
    86  		return nil, derr.ErrorCodeVolumeAbs.WithArgs(bind.Destination)
    87  	}
    88  
    89  	name, source, err := parseVolumeSource(arr[0])
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	if len(source) == 0 {
    95  		bind.Driver = volumeDriver
    96  		if len(bind.Driver) == 0 {
    97  			bind.Driver = volume.DefaultDriverName
    98  		}
    99  	} else {
   100  		bind.Source = filepath.Clean(source)
   101  	}
   102  
   103  	bind.Name = name
   104  	bind.Destination = filepath.Clean(bind.Destination)
   105  	return bind, nil
   106  }
   107  
   108  // sortMounts sorts an array of mounts in lexicographic order. This ensure that
   109  // when mounting, the mounts don't shadow other mounts. For example, if mounting
   110  // /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first.
   111  func sortMounts(m []execdriver.Mount) []execdriver.Mount {
   112  	sort.Sort(mounts(m))
   113  	return m
   114  }
   115  
   116  type mounts []execdriver.Mount
   117  
   118  // Len returns the number of mounts
   119  func (m mounts) Len() int {
   120  	return len(m)
   121  }
   122  
   123  // Less returns true if the number of parts (a/b/c would be 3 parts) in the
   124  // mount indexed by parameter 1 is less than that of the mount indexed by
   125  // parameter 2.
   126  func (m mounts) Less(i, j int) bool {
   127  	return m.parts(i) < m.parts(j)
   128  }
   129  
   130  // Swap swaps two items in an array of mounts.
   131  func (m mounts) Swap(i, j int) {
   132  	m[i], m[j] = m[j], m[i]
   133  }
   134  
   135  // parts returns the number of parts in the destination of a mount.
   136  func (m mounts) parts(i int) int {
   137  	return len(strings.Split(filepath.Clean(m[i].Destination), string(os.PathSeparator)))
   138  }
   139  
   140  // migrateVolume links the contents of a volume created pre Docker 1.7
   141  // into the location expected by the local driver.
   142  // It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
   143  // It preserves the volume json configuration generated pre Docker 1.7 to be able to
   144  // downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility.
   145  func migrateVolume(id, vfs string) error {
   146  	l, err := volumedrivers.Lookup(volume.DefaultDriverName)
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	newDataPath := l.(*local.Root).DataPath(id)
   152  	fi, err := os.Stat(newDataPath)
   153  	if err != nil && !os.IsNotExist(err) {
   154  		return err
   155  	}
   156  
   157  	if fi != nil && fi.IsDir() {
   158  		return nil
   159  	}
   160  
   161  	return os.Symlink(vfs, newDataPath)
   162  }
   163  
   164  // validVolumeLayout checks whether the volume directory layout
   165  // is valid to work with Docker post 1.7 or not.
   166  func validVolumeLayout(files []os.FileInfo) bool {
   167  	if len(files) == 1 && files[0].Name() == local.VolumeDataPathName && files[0].IsDir() {
   168  		return true
   169  	}
   170  
   171  	if len(files) != 2 {
   172  		return false
   173  	}
   174  
   175  	for _, f := range files {
   176  		if f.Name() == "config.json" ||
   177  			(f.Name() == local.VolumeDataPathName && f.Mode()&os.ModeSymlink == os.ModeSymlink) {
   178  			// Old volume configuration, we ignore it
   179  			continue
   180  		}
   181  		return false
   182  	}
   183  
   184  	return true
   185  }
   186  
   187  // verifyVolumesInfo ports volumes configured for the containers pre docker 1.7.
   188  // It reads the container configuration and creates valid mount points for the old volumes.
   189  func (daemon *Daemon) verifyVolumesInfo(container *Container) error {
   190  	// Inspect old structures only when we're upgrading from old versions
   191  	// to versions >= 1.7 and the MountPoints has not been populated with volumes data.
   192  	if len(container.MountPoints) == 0 && len(container.Volumes) > 0 {
   193  		for destination, hostPath := range container.Volumes {
   194  			vfsPath := filepath.Join(daemon.root, "vfs", "dir")
   195  			rw := container.VolumesRW != nil && container.VolumesRW[destination]
   196  
   197  			if strings.HasPrefix(hostPath, vfsPath) {
   198  				id := filepath.Base(hostPath)
   199  				if err := migrateVolume(id, hostPath); err != nil {
   200  					return err
   201  				}
   202  				container.addLocalMountPoint(id, destination, rw)
   203  			} else { // Bind mount
   204  				id, source, err := parseVolumeSource(hostPath)
   205  				// We should not find an error here coming
   206  				// from the old configuration, but who knows.
   207  				if err != nil {
   208  					return err
   209  				}
   210  				container.addBindMountPoint(id, source, destination, rw)
   211  			}
   212  		}
   213  	} else if len(container.MountPoints) > 0 {
   214  		// Volumes created with a Docker version >= 1.7. We verify integrity in case of data created
   215  		// with Docker 1.7 RC versions that put the information in
   216  		// DOCKER_ROOT/volumes/VOLUME_ID rather than DOCKER_ROOT/volumes/VOLUME_ID/_container_data.
   217  		l, err := volumedrivers.Lookup(volume.DefaultDriverName)
   218  		if err != nil {
   219  			return err
   220  		}
   221  
   222  		for _, m := range container.MountPoints {
   223  			if m.Driver != volume.DefaultDriverName {
   224  				continue
   225  			}
   226  			dataPath := l.(*local.Root).DataPath(m.Name)
   227  			volumePath := filepath.Dir(dataPath)
   228  
   229  			d, err := ioutil.ReadDir(volumePath)
   230  			if err != nil {
   231  				// If the volume directory doesn't exist yet it will be recreated,
   232  				// so we only return the error when there is a different issue.
   233  				if !os.IsNotExist(err) {
   234  					return err
   235  				}
   236  				// Do not check when the volume directory does not exist.
   237  				continue
   238  			}
   239  			if validVolumeLayout(d) {
   240  				continue
   241  			}
   242  
   243  			if err := os.Mkdir(dataPath, 0755); err != nil {
   244  				return err
   245  			}
   246  
   247  			// Move data inside the data directory
   248  			for _, f := range d {
   249  				oldp := filepath.Join(volumePath, f.Name())
   250  				newp := filepath.Join(dataPath, f.Name())
   251  				if err := os.Rename(oldp, newp); err != nil {
   252  					logrus.Errorf("Unable to move %s to %s\n", oldp, newp)
   253  				}
   254  			}
   255  		}
   256  
   257  		return container.toDiskLocking()
   258  	}
   259  
   260  	return nil
   261  }
   262  
   263  // parseVolumesFrom ensure that the supplied volumes-from is valid.
   264  func parseVolumesFrom(spec string) (string, string, error) {
   265  	if len(spec) == 0 {
   266  		return "", "", derr.ErrorCodeVolumeFromBlank.WithArgs(spec)
   267  	}
   268  
   269  	specParts := strings.SplitN(spec, ":", 2)
   270  	id := specParts[0]
   271  	mode := "rw"
   272  
   273  	if len(specParts) == 2 {
   274  		mode = specParts[1]
   275  		if !volume.ValidMountMode(mode) {
   276  			return "", "", derr.ErrorCodeVolumeMode.WithArgs(mode)
   277  		}
   278  	}
   279  	return id, mode, nil
   280  }
   281  
   282  // registerMountPoints initializes the container mount points with the configured volumes and bind mounts.
   283  // It follows the next sequence to decide what to mount in each final destination:
   284  //
   285  // 1. Select the previously configured mount points for the containers, if any.
   286  // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination.
   287  // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations.
   288  func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error {
   289  	binds := map[string]bool{}
   290  	mountPoints := map[string]*mountPoint{}
   291  
   292  	// 1. Read already configured mount points.
   293  	for name, point := range container.MountPoints {
   294  		mountPoints[name] = point
   295  	}
   296  
   297  	// 2. Read volumes from other containers.
   298  	for _, v := range hostConfig.VolumesFrom {
   299  		containerID, mode, err := parseVolumesFrom(v)
   300  		if err != nil {
   301  			return err
   302  		}
   303  
   304  		c, err := daemon.Get(containerID)
   305  		if err != nil {
   306  			return err
   307  		}
   308  
   309  		for _, m := range c.MountPoints {
   310  			cp := &mountPoint{
   311  				Name:        m.Name,
   312  				Source:      m.Source,
   313  				RW:          m.RW && volume.ReadWrite(mode),
   314  				Driver:      m.Driver,
   315  				Destination: m.Destination,
   316  			}
   317  
   318  			if len(cp.Source) == 0 {
   319  				v, err := daemon.createVolume(cp.Name, cp.Driver, nil)
   320  				if err != nil {
   321  					return err
   322  				}
   323  				cp.Volume = v
   324  			}
   325  
   326  			mountPoints[cp.Destination] = cp
   327  		}
   328  	}
   329  
   330  	// 3. Read bind mounts
   331  	for _, b := range hostConfig.Binds {
   332  		// #10618
   333  		bind, err := parseBindMount(b, hostConfig.VolumeDriver)
   334  		if err != nil {
   335  			return err
   336  		}
   337  
   338  		if binds[bind.Destination] {
   339  			return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination)
   340  		}
   341  
   342  		if len(bind.Name) > 0 && len(bind.Driver) > 0 {
   343  			// create the volume
   344  			v, err := daemon.createVolume(bind.Name, bind.Driver, nil)
   345  			if err != nil {
   346  				return err
   347  			}
   348  			bind.Volume = v
   349  			bind.Source = v.Path()
   350  			// bind.Name is an already existing volume, we need to use that here
   351  			bind.Driver = v.DriverName()
   352  			// Since this is just a named volume and not a typical bind, set to shared mode `z`
   353  			if bind.Mode == "" {
   354  				bind.Mode = "z"
   355  			}
   356  		}
   357  
   358  		shared := label.IsShared(bind.Mode)
   359  		if err := label.Relabel(bind.Source, container.MountLabel, shared); err != nil {
   360  			return err
   361  		}
   362  		binds[bind.Destination] = true
   363  		mountPoints[bind.Destination] = bind
   364  	}
   365  
   366  	// Keep backwards compatible structures
   367  	bcVolumes := map[string]string{}
   368  	bcVolumesRW := map[string]bool{}
   369  	for _, m := range mountPoints {
   370  		if m.BackwardsCompatible() {
   371  			bcVolumes[m.Destination] = m.Path()
   372  			bcVolumesRW[m.Destination] = m.RW
   373  
   374  			// This mountpoint is replacing an existing one, so the count needs to be decremented
   375  			if mp, exists := container.MountPoints[m.Destination]; exists && mp.Volume != nil {
   376  				daemon.volumes.Decrement(mp.Volume)
   377  			}
   378  		}
   379  	}
   380  
   381  	container.Lock()
   382  	container.MountPoints = mountPoints
   383  	container.Volumes = bcVolumes
   384  	container.VolumesRW = bcVolumesRW
   385  	container.Unlock()
   386  
   387  	return nil
   388  }
   389  
   390  // createVolume creates a volume.
   391  func (daemon *Daemon) createVolume(name, driverName string, opts map[string]string) (volume.Volume, error) {
   392  	v, err := daemon.volumes.Create(name, driverName, opts)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  	daemon.volumes.Increment(v)
   397  	return v, nil
   398  }
   399  
   400  // parseVolumeSource parses the origin sources that's mounted into the container.
   401  func parseVolumeSource(spec string) (string, string, error) {
   402  	if !filepath.IsAbs(spec) {
   403  		return spec, "", nil
   404  	}
   405  
   406  	return "", spec, nil
   407  }
   408  
   409  // BackwardsCompatible decides whether this mount point can be
   410  // used in old versions of Docker or not.
   411  // Only bind mounts and local volumes can be used in old versions of Docker.
   412  func (m *mountPoint) BackwardsCompatible() bool {
   413  	return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName
   414  }