github.com/jiasir/docker@v1.3.3-0.20170609024000-252e610103e7/daemon/volumes_unix.go (about) 1 // +build !windows 2 3 // TODO(amitkris): We need to split this file for solaris. 4 5 package daemon 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "os" 11 "path/filepath" 12 "sort" 13 "strconv" 14 "strings" 15 16 "github.com/docker/docker/container" 17 "github.com/docker/docker/pkg/fileutils" 18 "github.com/docker/docker/pkg/mount" 19 "github.com/docker/docker/volume" 20 "github.com/docker/docker/volume/drivers" 21 "github.com/docker/docker/volume/local" 22 "github.com/pkg/errors" 23 ) 24 25 // setupMounts iterates through each of the mount points for a container and 26 // calls Setup() on each. It also looks to see if is a network mount such as 27 // /etc/resolv.conf, and if it is not, appends it to the array of mounts. 28 func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) { 29 var mounts []container.Mount 30 // TODO: tmpfs mounts should be part of Mountpoints 31 tmpfsMounts := make(map[string]bool) 32 tmpfsMountInfo, err := c.TmpfsMounts() 33 if err != nil { 34 return nil, err 35 } 36 for _, m := range tmpfsMountInfo { 37 tmpfsMounts[m.Destination] = true 38 } 39 for _, m := range c.MountPoints { 40 if tmpfsMounts[m.Destination] { 41 continue 42 } 43 if err := daemon.lazyInitializeVolume(c.ID, m); err != nil { 44 return nil, err 45 } 46 // If the daemon is being shutdown, we should not let a container start if it is trying to 47 // mount the socket the daemon is listening on. During daemon shutdown, the socket 48 // (/var/run/docker.sock by default) doesn't exist anymore causing the call to m.Setup to 49 // create at directory instead. This in turn will prevent the daemon to restart. 50 checkfunc := func(m *volume.MountPoint) error { 51 if _, exist := daemon.hosts[m.Source]; exist && daemon.IsShuttingDown() { 52 return fmt.Errorf("Could not mount %q to container while the daemon is shutting down", m.Source) 53 } 54 return nil 55 } 56 57 path, err := m.Setup(c.MountLabel, daemon.idMappings.RootPair(), checkfunc) 58 if err != nil { 59 return nil, err 60 } 61 if !c.TrySetNetworkMount(m.Destination, path) { 62 mnt := container.Mount{ 63 Source: path, 64 Destination: m.Destination, 65 Writable: m.RW, 66 Propagation: string(m.Propagation), 67 } 68 if m.Volume != nil { 69 attributes := map[string]string{ 70 "driver": m.Volume.DriverName(), 71 "container": c.ID, 72 "destination": m.Destination, 73 "read/write": strconv.FormatBool(m.RW), 74 "propagation": string(m.Propagation), 75 } 76 daemon.LogVolumeEvent(m.Volume.Name(), "mount", attributes) 77 } 78 mounts = append(mounts, mnt) 79 } 80 } 81 82 mounts = sortMounts(mounts) 83 netMounts := c.NetworkMounts() 84 // if we are going to mount any of the network files from container 85 // metadata, the ownership must be set properly for potential container 86 // remapped root (user namespaces) 87 rootIDs := daemon.idMappings.RootPair() 88 for _, mount := range netMounts { 89 if err := os.Chown(mount.Source, rootIDs.UID, rootIDs.GID); err != nil { 90 return nil, err 91 } 92 } 93 return append(mounts, netMounts...), nil 94 } 95 96 // sortMounts sorts an array of mounts in lexicographic order. This ensure that 97 // when mounting, the mounts don't shadow other mounts. For example, if mounting 98 // /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first. 99 func sortMounts(m []container.Mount) []container.Mount { 100 sort.Sort(mounts(m)) 101 return m 102 } 103 104 // setBindModeIfNull is platform specific processing to ensure the 105 // shared mode is set to 'z' if it is null. This is called in the case 106 // of processing a named volume and not a typical bind. 107 func setBindModeIfNull(bind *volume.MountPoint) { 108 if bind.Mode == "" { 109 bind.Mode = "z" 110 } 111 } 112 113 // migrateVolume links the contents of a volume created pre Docker 1.7 114 // into the location expected by the local driver. 115 // It creates a symlink from DOCKER_ROOT/vfs/dir/VOLUME_ID to DOCKER_ROOT/volumes/VOLUME_ID/_container_data. 116 // It preserves the volume json configuration generated pre Docker 1.7 to be able to 117 // downgrade from Docker 1.7 to Docker 1.6 without losing volume compatibility. 118 func migrateVolume(id, vfs string) error { 119 l, err := volumedrivers.GetDriver(volume.DefaultDriverName) 120 if err != nil { 121 return err 122 } 123 124 newDataPath := l.(*local.Root).DataPath(id) 125 fi, err := os.Stat(newDataPath) 126 if err != nil && !os.IsNotExist(err) { 127 return err 128 } 129 130 if fi != nil && fi.IsDir() { 131 return nil 132 } 133 134 return os.Symlink(vfs, newDataPath) 135 } 136 137 // verifyVolumesInfo ports volumes configured for the containers pre docker 1.7. 138 // It reads the container configuration and creates valid mount points for the old volumes. 139 func (daemon *Daemon) verifyVolumesInfo(container *container.Container) error { 140 // Inspect old structures only when we're upgrading from old versions 141 // to versions >= 1.7 and the MountPoints has not been populated with volumes data. 142 type volumes struct { 143 Volumes map[string]string 144 VolumesRW map[string]bool 145 } 146 cfgPath, err := container.ConfigPath() 147 if err != nil { 148 return err 149 } 150 f, err := os.Open(cfgPath) 151 if err != nil { 152 return errors.Wrap(err, "could not open container config") 153 } 154 defer f.Close() 155 var cv volumes 156 if err := json.NewDecoder(f).Decode(&cv); err != nil { 157 return errors.Wrap(err, "could not decode container config") 158 } 159 160 if len(container.MountPoints) == 0 && len(cv.Volumes) > 0 { 161 for destination, hostPath := range cv.Volumes { 162 vfsPath := filepath.Join(daemon.root, "vfs", "dir") 163 rw := cv.VolumesRW != nil && cv.VolumesRW[destination] 164 165 if strings.HasPrefix(hostPath, vfsPath) { 166 id := filepath.Base(hostPath) 167 v, err := daemon.volumes.CreateWithRef(id, volume.DefaultDriverName, container.ID, nil, nil) 168 if err != nil { 169 return err 170 } 171 if err := migrateVolume(id, hostPath); err != nil { 172 return err 173 } 174 container.AddMountPointWithVolume(destination, v, true) 175 } else { // Bind mount 176 m := volume.MountPoint{Source: hostPath, Destination: destination, RW: rw} 177 container.MountPoints[destination] = &m 178 } 179 } 180 return container.ToDisk() 181 } 182 return nil 183 } 184 185 func (daemon *Daemon) mountVolumes(container *container.Container) error { 186 mounts, err := daemon.setupMounts(container) 187 if err != nil { 188 return err 189 } 190 191 for _, m := range mounts { 192 dest, err := container.GetResourcePath(m.Destination) 193 if err != nil { 194 return err 195 } 196 197 var stat os.FileInfo 198 stat, err = os.Stat(m.Source) 199 if err != nil { 200 return err 201 } 202 if err = fileutils.CreateIfNotExists(dest, stat.IsDir()); err != nil { 203 return err 204 } 205 206 opts := "rbind,ro" 207 if m.Writable { 208 opts = "rbind,rw" 209 } 210 211 if err := mount.Mount(m.Source, dest, bindMountType, opts); err != nil { 212 return err 213 } 214 215 // mountVolumes() seems to be called for temporary mounts 216 // outside the container. Soon these will be unmounted with 217 // lazy unmount option and given we have mounted the rbind, 218 // all the submounts will propagate if these are shared. If 219 // daemon is running in host namespace and has / as shared 220 // then these unmounts will propagate and unmount original 221 // mount as well. So make all these mounts rprivate. 222 // Do not use propagation property of volume as that should 223 // apply only when mounting happen inside the container. 224 if err := mount.MakeRPrivate(dest); err != nil { 225 return err 226 } 227 } 228 229 return nil 230 }