github.com/scorpionis/docker@v1.6.0-rc7/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 realPath, err := container.getResourcePath(path) 193 if err != nil { 194 return nil, fmt.Errorf("failed to evaluate the absolute path of symlink") 195 } 196 if stat, err := os.Stat(realPath); err == nil { 197 if !stat.IsDir() { 198 return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath) 199 } 200 } 201 202 vol, err := container.daemon.volumes.FindOrCreateVolume("", true) 203 if err != nil { 204 return nil, err 205 } 206 mounts[path] = &Mount{ 207 container: container, 208 MountToPath: path, 209 volume: vol, 210 Writable: true, 211 copyData: true, 212 } 213 } 214 215 return mounts, nil 216 } 217 218 func parseBindMountSpec(spec string) (string, string, bool, error) { 219 var ( 220 path, mountToPath string 221 writable bool 222 arr = strings.Split(spec, ":") 223 ) 224 225 switch len(arr) { 226 case 2: 227 path = arr[0] 228 mountToPath = arr[1] 229 writable = true 230 case 3: 231 path = arr[0] 232 mountToPath = arr[1] 233 writable = validMountMode(arr[2]) && arr[2] == "rw" 234 default: 235 return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec) 236 } 237 238 if !filepath.IsAbs(path) { 239 return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path) 240 } 241 242 path = filepath.Clean(path) 243 mountToPath = filepath.Clean(mountToPath) 244 return path, mountToPath, writable, nil 245 } 246 247 func parseVolumesFromSpec(spec string) (string, string, error) { 248 specParts := strings.SplitN(spec, ":", 2) 249 if len(specParts) == 0 { 250 return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec) 251 } 252 253 var ( 254 id = specParts[0] 255 mode = "rw" 256 ) 257 if len(specParts) == 2 { 258 mode = specParts[1] 259 if !validMountMode(mode) { 260 return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode) 261 } 262 } 263 return id, mode, nil 264 } 265 266 func (container *Container) applyVolumesFrom() error { 267 volumesFrom := container.hostConfig.VolumesFrom 268 if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil { 269 container.AppliedVolumesFrom = make(map[string]struct{}) 270 } 271 272 mountGroups := make(map[string][]*Mount) 273 274 for _, spec := range volumesFrom { 275 id, mode, err := parseVolumesFromSpec(spec) 276 if err != nil { 277 return err 278 } 279 if _, exists := container.AppliedVolumesFrom[id]; exists { 280 // Don't try to apply these since they've already been applied 281 continue 282 } 283 284 c, err := container.daemon.Get(id) 285 if err != nil { 286 return fmt.Errorf("Could not apply volumes of non-existent container %q.", id) 287 } 288 289 var ( 290 fromMounts = c.VolumeMounts() 291 mounts []*Mount 292 ) 293 294 for _, mnt := range fromMounts { 295 mnt.Writable = mnt.Writable && (mode == "rw") 296 mounts = append(mounts, mnt) 297 } 298 mountGroups[id] = mounts 299 } 300 301 for id, mounts := range mountGroups { 302 for _, mnt := range mounts { 303 mnt.from = mnt.container 304 mnt.container = container 305 if err := mnt.initialize(); err != nil { 306 return err 307 } 308 } 309 container.AppliedVolumesFrom[id] = struct{}{} 310 } 311 return nil 312 } 313 314 func validMountMode(mode string) bool { 315 validModes := map[string]bool{ 316 "rw": true, 317 "ro": true, 318 } 319 320 return validModes[mode] 321 } 322 323 func (container *Container) setupMounts() error { 324 mounts := []execdriver.Mount{} 325 326 // Mount user specified volumes 327 // Note, these are not private because you may want propagation of (un)mounts from host 328 // volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you 329 // want this new mount in the container 330 // These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic) 331 for _, path := range container.sortedVolumeMounts() { 332 mounts = append(mounts, execdriver.Mount{ 333 Source: container.Volumes[path], 334 Destination: path, 335 Writable: container.VolumesRW[path], 336 }) 337 } 338 339 if container.ResolvConfPath != "" { 340 mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) 341 } 342 343 if container.HostnamePath != "" { 344 mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) 345 } 346 347 if container.HostsPath != "" { 348 mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) 349 } 350 351 container.command.Mounts = mounts 352 return nil 353 } 354 355 func (container *Container) VolumeMounts() map[string]*Mount { 356 mounts := make(map[string]*Mount) 357 358 for mountToPath, path := range container.Volumes { 359 if v := container.daemon.volumes.Get(path); v != nil { 360 mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]} 361 } 362 } 363 364 return mounts 365 } 366 367 func copyExistingContents(source, destination string) error { 368 volList, err := ioutil.ReadDir(source) 369 if err != nil { 370 return err 371 } 372 373 if len(volList) > 0 { 374 srcList, err := ioutil.ReadDir(destination) 375 if err != nil { 376 return err 377 } 378 379 if len(srcList) == 0 { 380 // If the source volume is empty copy files from the root into the volume 381 if err := chrootarchive.CopyWithTar(source, destination); err != nil { 382 return err 383 } 384 } 385 } 386 387 return copyOwnership(source, destination) 388 } 389 390 // copyOwnership copies the permissions and uid:gid of the source file 391 // into the destination file 392 func copyOwnership(source, destination string) error { 393 stat, err := system.Stat(source) 394 if err != nil { 395 return err 396 } 397 398 if err := os.Chown(destination, int(stat.Uid()), int(stat.Gid())); err != nil { 399 return err 400 } 401 402 return os.Chmod(destination, os.FileMode(stat.Mode())) 403 }