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