github.com/fcwu/docker@v1.4.2-0.20150115145920-2a69ca89f0df/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 _, mnt := range container.VolumeMounts() { 123 mnt.volume.AddContainer(container.ID) 124 } 125 } 126 127 func (container *Container) derefVolumes() { 128 for path := range container.VolumePaths() { 129 vol := container.daemon.volumes.Get(path) 130 if vol == nil { 131 log.Debugf("Volume %s was not found and could not be dereferenced", path) 132 continue 133 } 134 vol.RemoveContainer(container.ID) 135 } 136 } 137 138 func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) { 139 var mounts = make(map[string]*Mount) 140 // Get all the bind mounts 141 for _, spec := range container.hostConfig.Binds { 142 path, mountToPath, writable, err := parseBindMountSpec(spec) 143 if err != nil { 144 return nil, err 145 } 146 // Check if a volume already exists for this and use it 147 vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable) 148 if err != nil { 149 return nil, err 150 } 151 mounts[mountToPath] = &Mount{ 152 container: container, 153 volume: vol, 154 MountToPath: mountToPath, 155 Writable: writable, 156 } 157 } 158 159 // Get the rest of the volumes 160 for path := range container.Config.Volumes { 161 // Check if this is already added as a bind-mount 162 path = filepath.Clean(path) 163 if _, exists := mounts[path]; exists { 164 continue 165 } 166 167 // Check if this has already been created 168 if _, exists := container.Volumes[path]; exists { 169 continue 170 } 171 172 vol, err := container.daemon.volumes.FindOrCreateVolume("", true) 173 if err != nil { 174 return nil, err 175 } 176 mounts[path] = &Mount{ 177 container: container, 178 MountToPath: path, 179 volume: vol, 180 Writable: true, 181 copyData: true, 182 } 183 } 184 185 return mounts, nil 186 } 187 188 func parseBindMountSpec(spec string) (string, string, bool, error) { 189 var ( 190 path, mountToPath string 191 writable bool 192 arr = strings.Split(spec, ":") 193 ) 194 195 switch len(arr) { 196 case 2: 197 path = arr[0] 198 mountToPath = arr[1] 199 writable = true 200 case 3: 201 path = arr[0] 202 mountToPath = arr[1] 203 writable = validMountMode(arr[2]) && arr[2] == "rw" 204 default: 205 return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec) 206 } 207 208 if !filepath.IsAbs(path) { 209 return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path) 210 } 211 212 path = filepath.Clean(path) 213 mountToPath = filepath.Clean(mountToPath) 214 return path, mountToPath, writable, nil 215 } 216 217 func (container *Container) applyVolumesFrom() error { 218 volumesFrom := container.hostConfig.VolumesFrom 219 220 mountGroups := make([]map[string]*Mount, 0, len(volumesFrom)) 221 222 for _, spec := range volumesFrom { 223 mountGroup, err := parseVolumesFromSpec(container.daemon, spec) 224 if err != nil { 225 return err 226 } 227 mountGroups = append(mountGroups, mountGroup) 228 } 229 230 for _, mounts := range mountGroups { 231 for _, mnt := range mounts { 232 mnt.from = mnt.container 233 mnt.container = container 234 if err := mnt.initialize(); err != nil { 235 return err 236 } 237 } 238 } 239 return nil 240 } 241 242 func validMountMode(mode string) bool { 243 validModes := map[string]bool{ 244 "rw": true, 245 "ro": true, 246 } 247 248 return validModes[mode] 249 } 250 251 func (container *Container) setupMounts() error { 252 mounts := []execdriver.Mount{ 253 {Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}, 254 } 255 256 if container.HostnamePath != "" { 257 mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) 258 } 259 260 if container.HostsPath != "" { 261 mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) 262 } 263 264 for _, m := range mounts { 265 if err := label.SetFileLabel(m.Source, container.MountLabel); err != nil { 266 return err 267 } 268 } 269 270 // Mount user specified volumes 271 // Note, these are not private because you may want propagation of (un)mounts from host 272 // volumes. For instance if you use -v /usr:/usr and the host later mounts /usr/share you 273 // want this new mount in the container 274 // These mounts must be ordered based on the length of the path that it is being mounted to (lexicographic) 275 for _, path := range container.sortedVolumeMounts() { 276 mounts = append(mounts, execdriver.Mount{ 277 Source: container.Volumes[path], 278 Destination: path, 279 Writable: container.VolumesRW[path], 280 }) 281 } 282 283 container.command.Mounts = mounts 284 return nil 285 } 286 287 func parseVolumesFromSpec(daemon *Daemon, spec string) (map[string]*Mount, error) { 288 specParts := strings.SplitN(spec, ":", 2) 289 if len(specParts) == 0 { 290 return nil, fmt.Errorf("Malformed volumes-from specification: %s", spec) 291 } 292 293 c := daemon.Get(specParts[0]) 294 if c == nil { 295 return nil, fmt.Errorf("Container %s not found. Impossible to mount its volumes", specParts[0]) 296 } 297 298 mounts := c.VolumeMounts() 299 300 if len(specParts) == 2 { 301 mode := specParts[1] 302 if !validMountMode(mode) { 303 return nil, fmt.Errorf("Invalid mode for volumes-from: %s", mode) 304 } 305 306 // Set the mode for the inheritted volume 307 for _, mnt := range mounts { 308 // Ensure that if the inherited volume is not writable, that we don't make 309 // it writable here 310 mnt.Writable = mnt.Writable && (mode == "rw") 311 } 312 } 313 314 return mounts, nil 315 } 316 317 func (container *Container) VolumeMounts() map[string]*Mount { 318 mounts := make(map[string]*Mount) 319 320 for mountToPath, path := range container.Volumes { 321 if v := container.daemon.volumes.Get(path); v != nil { 322 mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]} 323 } 324 } 325 326 return mounts 327 } 328 329 func copyExistingContents(source, destination string) error { 330 volList, err := ioutil.ReadDir(source) 331 if err != nil { 332 return err 333 } 334 335 if len(volList) > 0 { 336 srcList, err := ioutil.ReadDir(destination) 337 if err != nil { 338 return err 339 } 340 341 if len(srcList) == 0 { 342 // If the source volume is empty copy files from the root into the volume 343 if err := chrootarchive.CopyWithTar(source, destination); err != nil { 344 return err 345 } 346 } 347 } 348 349 return copyOwnership(source, destination) 350 } 351 352 // copyOwnership copies the permissions and uid:gid of the source file 353 // into the destination file 354 func copyOwnership(source, destination string) error { 355 var stat syscall.Stat_t 356 357 if err := syscall.Stat(source, &stat); err != nil { 358 return err 359 } 360 361 if err := os.Chown(destination, int(stat.Uid), int(stat.Gid)); err != nil { 362 return err 363 } 364 365 return os.Chmod(destination, os.FileMode(stat.Mode)) 366 }