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