github.com/projectatomic/docker@v1.8.2/daemon/volumes.go (about) 1 package daemon 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/Sirupsen/logrus" 12 "github.com/docker/docker/pkg/chrootarchive" 13 "github.com/docker/docker/pkg/system" 14 "github.com/docker/docker/runconfig" 15 "github.com/docker/docker/volume" 16 "github.com/docker/docker/volume/drivers" 17 "github.com/docker/docker/volume/local" 18 "github.com/opencontainers/runc/libcontainer/label" 19 ) 20 21 // ErrVolumeReadonly is used to signal an error when trying to copy data into 22 // a volume mount that is not writable. 23 var ErrVolumeReadonly = errors.New("mounted volume is marked read-only") 24 25 type mountPoint struct { 26 Name string 27 Destination string 28 Driver string 29 RW bool 30 Volume volume.Volume `json:"-"` 31 Source string 32 Relabel string 33 } 34 35 func (m *mountPoint) Setup() (string, error) { 36 if m.Volume != nil { 37 return m.Volume.Mount() 38 } 39 40 if len(m.Source) > 0 { 41 if _, err := os.Stat(m.Source); err != nil { 42 if !os.IsNotExist(err) { 43 return "", err 44 } 45 if err := system.MkdirAll(m.Source, 0755); err != nil { 46 return "", err 47 } 48 } 49 return m.Source, nil 50 } 51 52 return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined") 53 } 54 55 // hasResource checks whether the given absolute path for a container is in 56 // this mount point. If the relative path starts with `../` then the resource 57 // is outside of this mount point, but we can't simply check for this prefix 58 // because it misses `..` which is also outside of the mount, so check both. 59 func (m *mountPoint) hasResource(absolutePath string) bool { 60 relPath, err := filepath.Rel(m.Destination, absolutePath) 61 62 return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator)) 63 } 64 65 func (m *mountPoint) Path() string { 66 if m.Volume != nil { 67 return m.Volume.Path() 68 } 69 70 return m.Source 71 } 72 73 // BackwardsCompatible decides whether this mount point can be 74 // used in old versions of Docker or not. 75 // Only bind mounts and local volumes can be used in old versions of Docker. 76 func (m *mountPoint) BackwardsCompatible() bool { 77 return len(m.Source) > 0 || m.Driver == volume.DefaultDriverName 78 } 79 80 func parseBindMount(spec string, mountLabel string, config *runconfig.Config) (*mountPoint, error) { 81 bind := &mountPoint{ 82 RW: true, 83 } 84 arr := strings.Split(spec, ":") 85 86 switch len(arr) { 87 case 2: 88 bind.Destination = arr[1] 89 case 3: 90 bind.Destination = arr[1] 91 mode := arr[2] 92 isValid, isRw := volume.ValidateMountMode(mode) 93 if !isValid { 94 return nil, fmt.Errorf("invalid mode for volumes-from: %s", mode) 95 } 96 bind.RW = isRw 97 // Relabel will apply a SELinux label, if necessary 98 bind.Relabel = mode 99 default: 100 return nil, fmt.Errorf("Invalid volume specification: %s", spec) 101 } 102 103 name, source, err := parseVolumeSource(arr[0]) 104 if err != nil { 105 return nil, err 106 } 107 108 if len(source) == 0 { 109 bind.Driver = config.VolumeDriver 110 if len(bind.Driver) == 0 { 111 bind.Driver = volume.DefaultDriverName 112 } 113 } else { 114 bind.Source = filepath.Clean(source) 115 } 116 117 bind.Name = name 118 bind.Destination = filepath.Clean(bind.Destination) 119 return bind, nil 120 } 121 122 func parseVolumesFrom(spec string) (string, string, error) { 123 if len(spec) == 0 { 124 return "", "", fmt.Errorf("malformed volumes-from specification: %s", spec) 125 } 126 127 specParts := strings.SplitN(spec, ":", 2) 128 id := specParts[0] 129 mode := "rw" 130 131 if len(specParts) == 2 { 132 mode = specParts[1] 133 if isValid, _ := volume.ValidateMountMode(mode); !isValid { 134 return "", "", fmt.Errorf("invalid mode for volumes-from: %s", mode) 135 } 136 } 137 return id, mode, nil 138 } 139 140 func copyExistingContents(source, destination string) error { 141 volList, err := ioutil.ReadDir(source) 142 if err != nil { 143 return err 144 } 145 if len(volList) > 0 { 146 srcList, err := ioutil.ReadDir(destination) 147 if err != nil { 148 return err 149 } 150 if len(srcList) == 0 { 151 // If the source volume is empty copy files from the root into the volume 152 if err := chrootarchive.CopyWithTar(source, destination); err != nil { 153 return err 154 } 155 } 156 } 157 return copyOwnership(source, destination) 158 } 159 160 // registerMountPoints initializes the container mount points with the configured volumes and bind mounts. 161 // It follows the next sequence to decide what to mount in each final destination: 162 // 163 // 1. Select the previously configured mount points for the containers, if any. 164 // 2. Select the volumes mounted from another containers. Overrides previously configured mount point destination. 165 // 3. Select the bind mounts set by the client. Overrides previously configured mount point destinations. 166 func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runconfig.HostConfig) error { 167 binds := map[string]bool{} 168 mountPoints := map[string]*mountPoint{} 169 170 // 1. Read already configured mount points. 171 for name, point := range container.MountPoints { 172 mountPoints[name] = point 173 } 174 175 // 2. Read volumes from other containers. 176 for _, v := range hostConfig.VolumesFrom { 177 containerID, mode, err := parseVolumesFrom(v) 178 if err != nil { 179 return err 180 } 181 182 c, err := daemon.Get(containerID) 183 if err != nil { 184 return err 185 } 186 187 for _, m := range c.MountPoints { 188 cp := &mountPoint{ 189 Name: m.Name, 190 Source: m.Source, 191 RW: m.RW && volume.ReadWrite(mode), 192 Driver: m.Driver, 193 Destination: m.Destination, 194 } 195 196 if len(cp.Source) == 0 { 197 v, err := createVolume(cp.Name, cp.Driver) 198 if err != nil { 199 return err 200 } 201 cp.Volume = v 202 } 203 204 mountPoints[cp.Destination] = cp 205 } 206 } 207 208 // 3. Read bind mounts 209 for _, b := range hostConfig.Binds { 210 // #10618 211 bind, err := parseBindMount(b, container.MountLabel, container.Config) 212 if err != nil { 213 return err 214 } 215 216 if binds[bind.Destination] { 217 return fmt.Errorf("Duplicate bind mount %s", bind.Destination) 218 } 219 220 if len(bind.Name) > 0 && len(bind.Driver) > 0 { 221 // create the volume 222 v, err := createVolume(bind.Name, bind.Driver) 223 if err != nil { 224 return err 225 } 226 bind.Volume = v 227 bind.Source = v.Path() 228 // Since this is just a named volume and not a typical bind, set to shared mode `z` 229 if bind.Relabel == "" { 230 bind.Relabel = "z" 231 } 232 } 233 234 if err := label.Relabel(bind.Source, container.MountLabel, bind.Relabel); err != nil { 235 return err 236 } 237 binds[bind.Destination] = true 238 mountPoints[bind.Destination] = bind 239 } 240 241 // Keep backwards compatible structures 242 bcVolumes := map[string]string{} 243 bcVolumesRW := map[string]bool{} 244 for _, m := range mountPoints { 245 if m.BackwardsCompatible() { 246 bcVolumes[m.Destination] = m.Path() 247 bcVolumesRW[m.Destination] = m.RW 248 } 249 } 250 251 container.Lock() 252 container.MountPoints = mountPoints 253 container.Volumes = bcVolumes 254 container.VolumesRW = bcVolumesRW 255 container.Unlock() 256 257 return nil 258 } 259 260 // TODO Windows. Factor out as not relevant (as Windows daemon support not in pre-1.7) 261 // verifyVolumesInfo ports volumes configured for the containers pre docker 1.7. 262 // It reads the container configuration and creates valid mount points for the old volumes. 263 func (daemon *Daemon) verifyVolumesInfo(container *Container) error { 264 // Inspect old structures only when we're upgrading from old versions 265 // to versions >= 1.7 and the MountPoints has not been populated with volumes data. 266 if len(container.MountPoints) == 0 && len(container.Volumes) > 0 { 267 for destination, hostPath := range container.Volumes { 268 vfsPath := filepath.Join(daemon.root, "vfs", "dir") 269 rw := container.VolumesRW != nil && container.VolumesRW[destination] 270 271 if strings.HasPrefix(hostPath, vfsPath) { 272 id := filepath.Base(hostPath) 273 if err := migrateVolume(id, hostPath); err != nil { 274 return err 275 } 276 container.addLocalMountPoint(id, destination, rw) 277 } else { // Bind mount 278 id, source, err := parseVolumeSource(hostPath) 279 // We should not find an error here coming 280 // from the old configuration, but who knows. 281 if err != nil { 282 return err 283 } 284 container.addBindMountPoint(id, source, destination, rw) 285 } 286 } 287 } else if len(container.MountPoints) > 0 { 288 // Volumes created with a Docker version >= 1.7. We verify integrity in case of data created 289 // with Docker 1.7 RC versions that put the information in 290 // DOCKER_ROOT/volumes/VOLUME_ID rather than DOCKER_ROOT/volumes/VOLUME_ID/_container_data. 291 l, err := getVolumeDriver(volume.DefaultDriverName) 292 if err != nil { 293 return err 294 } 295 296 for _, m := range container.MountPoints { 297 if m.Driver != volume.DefaultDriverName { 298 continue 299 } 300 dataPath := l.(*local.Root).DataPath(m.Name) 301 volumePath := filepath.Dir(dataPath) 302 303 d, err := ioutil.ReadDir(volumePath) 304 if err != nil { 305 // If the volume directory doesn't exist yet it will be recreated, 306 // so we only return the error when there is a different issue. 307 if !os.IsNotExist(err) { 308 return err 309 } 310 // Do not check when the volume directory does not exist. 311 continue 312 } 313 if validVolumeLayout(d) { 314 continue 315 } 316 317 if err := os.Mkdir(dataPath, 0755); err != nil { 318 return err 319 } 320 321 // Move data inside the data directory 322 for _, f := range d { 323 oldp := filepath.Join(volumePath, f.Name()) 324 newp := filepath.Join(dataPath, f.Name()) 325 if err := os.Rename(oldp, newp); err != nil { 326 logrus.Errorf("Unable to move %s to %s\n", oldp, newp) 327 } 328 } 329 } 330 331 return container.ToDisk() 332 } 333 334 return nil 335 } 336 337 func createVolume(name, driverName string) (volume.Volume, error) { 338 vd, err := getVolumeDriver(driverName) 339 if err != nil { 340 return nil, err 341 } 342 return vd.Create(name) 343 } 344 345 func removeVolume(v volume.Volume) error { 346 vd, err := getVolumeDriver(v.DriverName()) 347 if err != nil { 348 return nil 349 } 350 return vd.Remove(v) 351 } 352 353 func getVolumeDriver(name string) (volume.Driver, error) { 354 if name == "" { 355 name = volume.DefaultDriverName 356 } 357 return volumedrivers.Lookup(name) 358 } 359 360 func parseVolumeSource(spec string) (string, string, error) { 361 if !filepath.IsAbs(spec) { 362 return spec, "", nil 363 } 364 365 return "", spec, nil 366 }