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