github.com/ojongerius/docker@v1.11.2/daemon/graphdriver/overlay/overlay.go (about) 1 // +build linux 2 3 package overlay 4 5 import ( 6 "bufio" 7 "fmt" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "path" 12 "sync" 13 "syscall" 14 15 "github.com/Sirupsen/logrus" 16 17 "github.com/docker/docker/daemon/graphdriver" 18 "github.com/docker/docker/pkg/archive" 19 "github.com/docker/docker/pkg/chrootarchive" 20 "github.com/docker/docker/pkg/idtools" 21 22 "github.com/opencontainers/runc/libcontainer/label" 23 ) 24 25 // This is a small wrapper over the NaiveDiffWriter that lets us have a custom 26 // implementation of ApplyDiff() 27 28 var ( 29 // ErrApplyDiffFallback is returned to indicate that a normal ApplyDiff is applied as a fallback from Naive diff writer. 30 ErrApplyDiffFallback = fmt.Errorf("Fall back to normal ApplyDiff") 31 ) 32 33 // ApplyDiffProtoDriver wraps the ProtoDriver by extending the interface with ApplyDiff method. 34 type ApplyDiffProtoDriver interface { 35 graphdriver.ProtoDriver 36 // ApplyDiff writes the diff to the archive for the given id and parent id. 37 // It returns the size in bytes written if successful, an error ErrApplyDiffFallback is returned otherwise. 38 ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error) 39 } 40 41 type naiveDiffDriverWithApply struct { 42 graphdriver.Driver 43 applyDiff ApplyDiffProtoDriver 44 } 45 46 // NaiveDiffDriverWithApply returns a NaiveDiff driver with custom ApplyDiff. 47 func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver, uidMaps, gidMaps []idtools.IDMap) graphdriver.Driver { 48 return &naiveDiffDriverWithApply{ 49 Driver: graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), 50 applyDiff: driver, 51 } 52 } 53 54 // ApplyDiff creates a diff layer with either the NaiveDiffDriver or with a fallback. 55 func (d *naiveDiffDriverWithApply) ApplyDiff(id, parent string, diff archive.Reader) (int64, error) { 56 b, err := d.applyDiff.ApplyDiff(id, parent, diff) 57 if err == ErrApplyDiffFallback { 58 return d.Driver.ApplyDiff(id, parent, diff) 59 } 60 return b, err 61 } 62 63 // This backend uses the overlay union filesystem for containers 64 // plus hard link file sharing for images. 65 66 // Each container/image can have a "root" subdirectory which is a plain 67 // filesystem hierarchy, or they can use overlay. 68 69 // If they use overlay there is a "upper" directory and a "lower-id" 70 // file, as well as "merged" and "work" directories. The "upper" 71 // directory has the upper layer of the overlay, and "lower-id" contains 72 // the id of the parent whose "root" directory shall be used as the lower 73 // layer in the overlay. The overlay itself is mounted in the "merged" 74 // directory, and the "work" dir is needed for overlay to work. 75 76 // When a overlay layer is created there are two cases, either the 77 // parent has a "root" dir, then we start out with a empty "upper" 78 // directory overlaid on the parents root. This is typically the 79 // case with the init layer of a container which is based on an image. 80 // If there is no "root" in the parent, we inherit the lower-id from 81 // the parent and start by making a copy in the parent's "upper" dir. 82 // This is typically the case for a container layer which copies 83 // its parent -init upper layer. 84 85 // Additionally we also have a custom implementation of ApplyLayer 86 // which makes a recursive copy of the parent "root" layer using 87 // hardlinks to share file data, and then applies the layer on top 88 // of that. This means all child images share file (but not directory) 89 // data with the parent. 90 91 // Driver contains information about the home directory and the list of active mounts that are created using this driver. 92 type Driver struct { 93 home string 94 pathCacheLock sync.Mutex 95 pathCache map[string]string 96 uidMaps []idtools.IDMap 97 gidMaps []idtools.IDMap 98 ctr *graphdriver.RefCounter 99 } 100 101 var backingFs = "<unknown>" 102 103 func init() { 104 graphdriver.Register("overlay", Init) 105 } 106 107 // Init returns the NaiveDiffDriver, a native diff driver for overlay filesystem. 108 // If overlay filesystem is not supported on the host, graphdriver.ErrNotSupported is returned as error. 109 // If a overlay filesystem is not supported over a existing filesystem then error graphdriver.ErrIncompatibleFS is returned. 110 func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { 111 112 if err := supportsOverlay(); err != nil { 113 return nil, graphdriver.ErrNotSupported 114 } 115 116 fsMagic, err := graphdriver.GetFSMagic(home) 117 if err != nil { 118 return nil, err 119 } 120 if fsName, ok := graphdriver.FsNames[fsMagic]; ok { 121 backingFs = fsName 122 } 123 124 // check if they are running over btrfs or aufs 125 switch fsMagic { 126 case graphdriver.FsMagicBtrfs: 127 logrus.Error("'overlay' is not supported over btrfs.") 128 return nil, graphdriver.ErrIncompatibleFS 129 case graphdriver.FsMagicAufs: 130 logrus.Error("'overlay' is not supported over aufs.") 131 return nil, graphdriver.ErrIncompatibleFS 132 case graphdriver.FsMagicZfs: 133 logrus.Error("'overlay' is not supported over zfs.") 134 return nil, graphdriver.ErrIncompatibleFS 135 } 136 137 rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) 138 if err != nil { 139 return nil, err 140 } 141 // Create the driver home dir 142 if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) { 143 return nil, err 144 } 145 146 d := &Driver{ 147 home: home, 148 pathCache: make(map[string]string), 149 uidMaps: uidMaps, 150 gidMaps: gidMaps, 151 ctr: graphdriver.NewRefCounter(), 152 } 153 154 return NaiveDiffDriverWithApply(d, uidMaps, gidMaps), nil 155 } 156 157 func supportsOverlay() error { 158 // We can try to modprobe overlay first before looking at 159 // proc/filesystems for when overlay is supported 160 exec.Command("modprobe", "overlay").Run() 161 162 f, err := os.Open("/proc/filesystems") 163 if err != nil { 164 return err 165 } 166 defer f.Close() 167 168 s := bufio.NewScanner(f) 169 for s.Scan() { 170 if s.Text() == "nodev\toverlay" { 171 return nil 172 } 173 } 174 logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.") 175 return graphdriver.ErrNotSupported 176 } 177 178 func (d *Driver) String() string { 179 return "overlay" 180 } 181 182 // Status returns current driver information in a two dimensional string array. 183 // Output contains "Backing Filesystem" used in this implementation. 184 func (d *Driver) Status() [][2]string { 185 return [][2]string{ 186 {"Backing Filesystem", backingFs}, 187 } 188 } 189 190 // GetMetadata returns meta data about the overlay driver such as root, LowerDir, UpperDir, WorkDir and MergeDir used to store data. 191 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 192 dir := d.dir(id) 193 if _, err := os.Stat(dir); err != nil { 194 return nil, err 195 } 196 197 metadata := make(map[string]string) 198 199 // If id has a root, it is an image 200 rootDir := path.Join(dir, "root") 201 if _, err := os.Stat(rootDir); err == nil { 202 metadata["RootDir"] = rootDir 203 return metadata, nil 204 } 205 206 lowerID, err := ioutil.ReadFile(path.Join(dir, "lower-id")) 207 if err != nil { 208 return nil, err 209 } 210 211 metadata["LowerDir"] = path.Join(d.dir(string(lowerID)), "root") 212 metadata["UpperDir"] = path.Join(dir, "upper") 213 metadata["WorkDir"] = path.Join(dir, "work") 214 metadata["MergedDir"] = path.Join(dir, "merged") 215 216 return metadata, nil 217 } 218 219 // Cleanup simply returns nil and do not change the existing filesystem. 220 // This is required to satisfy the graphdriver.Driver interface. 221 func (d *Driver) Cleanup() error { 222 return nil 223 } 224 225 // Create is used to create the upper, lower, and merge directories required for overlay fs for a given id. 226 // The parent filesystem is used to configure these directories for the overlay. 227 func (d *Driver) Create(id, parent, mountLabel string) (retErr error) { 228 dir := d.dir(id) 229 230 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 231 if err != nil { 232 return err 233 } 234 if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil { 235 return err 236 } 237 if err := idtools.MkdirAs(dir, 0700, rootUID, rootGID); err != nil { 238 return err 239 } 240 241 defer func() { 242 // Clean up on failure 243 if retErr != nil { 244 os.RemoveAll(dir) 245 } 246 }() 247 248 // Toplevel images are just a "root" dir 249 if parent == "" { 250 if err := idtools.MkdirAs(path.Join(dir, "root"), 0755, rootUID, rootGID); err != nil { 251 return err 252 } 253 return nil 254 } 255 256 parentDir := d.dir(parent) 257 258 // Ensure parent exists 259 if _, err := os.Lstat(parentDir); err != nil { 260 return err 261 } 262 263 // If parent has a root, just do a overlay to it 264 parentRoot := path.Join(parentDir, "root") 265 266 if s, err := os.Lstat(parentRoot); err == nil { 267 if err := idtools.MkdirAs(path.Join(dir, "upper"), s.Mode(), rootUID, rootGID); err != nil { 268 return err 269 } 270 if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil { 271 return err 272 } 273 if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil { 274 return err 275 } 276 if err := ioutil.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0666); err != nil { 277 return err 278 } 279 return nil 280 } 281 282 // Otherwise, copy the upper and the lower-id from the parent 283 284 lowerID, err := ioutil.ReadFile(path.Join(parentDir, "lower-id")) 285 if err != nil { 286 return err 287 } 288 289 if err := ioutil.WriteFile(path.Join(dir, "lower-id"), lowerID, 0666); err != nil { 290 return err 291 } 292 293 parentUpperDir := path.Join(parentDir, "upper") 294 s, err := os.Lstat(parentUpperDir) 295 if err != nil { 296 return err 297 } 298 299 upperDir := path.Join(dir, "upper") 300 if err := idtools.MkdirAs(upperDir, s.Mode(), rootUID, rootGID); err != nil { 301 return err 302 } 303 if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil { 304 return err 305 } 306 if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil { 307 return err 308 } 309 310 return copyDir(parentUpperDir, upperDir, 0) 311 } 312 313 func (d *Driver) dir(id string) string { 314 return path.Join(d.home, id) 315 } 316 317 // Remove cleans the directories that are created for this id. 318 func (d *Driver) Remove(id string) error { 319 if err := os.RemoveAll(d.dir(id)); err != nil && !os.IsNotExist(err) { 320 return err 321 } 322 d.pathCacheLock.Lock() 323 delete(d.pathCache, id) 324 d.pathCacheLock.Unlock() 325 return nil 326 } 327 328 // Get creates and mounts the required file system for the given id and returns the mount path. 329 func (d *Driver) Get(id string, mountLabel string) (string, error) { 330 dir := d.dir(id) 331 if _, err := os.Stat(dir); err != nil { 332 return "", err 333 } 334 335 // If id has a root, just return it 336 rootDir := path.Join(dir, "root") 337 if _, err := os.Stat(rootDir); err == nil { 338 d.pathCacheLock.Lock() 339 d.pathCache[id] = rootDir 340 d.pathCacheLock.Unlock() 341 return rootDir, nil 342 } 343 344 lowerID, err := ioutil.ReadFile(path.Join(dir, "lower-id")) 345 if err != nil { 346 return "", err 347 } 348 lowerDir := path.Join(d.dir(string(lowerID)), "root") 349 upperDir := path.Join(dir, "upper") 350 workDir := path.Join(dir, "work") 351 mergedDir := path.Join(dir, "merged") 352 353 if count := d.ctr.Increment(id); count > 1 { 354 return mergedDir, nil 355 } 356 357 opts := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerDir, upperDir, workDir) 358 359 // if it's mounted already, just return 360 mounted, err := d.mounted(mergedDir) 361 if err != nil { 362 d.ctr.Decrement(id) 363 return "", err 364 } 365 if mounted { 366 d.ctr.Decrement(id) 367 return mergedDir, nil 368 } 369 370 if err := syscall.Mount("overlay", mergedDir, "overlay", 0, label.FormatMountLabel(opts, mountLabel)); err != nil { 371 d.ctr.Decrement(id) 372 return "", fmt.Errorf("error creating overlay mount to %s: %v", mergedDir, err) 373 } 374 // chown "workdir/work" to the remapped root UID/GID. Overlay fs inside a 375 // user namespace requires this to move a directory from lower to upper. 376 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 377 if err != nil { 378 d.ctr.Decrement(id) 379 syscall.Unmount(mergedDir, 0) 380 return "", err 381 } 382 383 if err := os.Chown(path.Join(workDir, "work"), rootUID, rootGID); err != nil { 384 d.ctr.Decrement(id) 385 syscall.Unmount(mergedDir, 0) 386 return "", err 387 } 388 389 d.pathCacheLock.Lock() 390 d.pathCache[id] = mergedDir 391 d.pathCacheLock.Unlock() 392 393 return mergedDir, nil 394 } 395 396 func (d *Driver) mounted(dir string) (bool, error) { 397 return graphdriver.Mounted(graphdriver.FsMagicOverlay, dir) 398 } 399 400 // Put unmounts the mount path created for the give id. 401 func (d *Driver) Put(id string) error { 402 if count := d.ctr.Decrement(id); count > 0 { 403 return nil 404 } 405 d.pathCacheLock.Lock() 406 mountpoint, exists := d.pathCache[id] 407 d.pathCacheLock.Unlock() 408 409 if !exists { 410 logrus.Debugf("Put on a non-mounted device %s", id) 411 // but it might be still here 412 if d.Exists(id) { 413 mountpoint = path.Join(d.dir(id), "merged") 414 } 415 416 d.pathCacheLock.Lock() 417 d.pathCache[id] = mountpoint 418 d.pathCacheLock.Unlock() 419 } 420 421 if mounted, err := d.mounted(mountpoint); mounted || err != nil { 422 if err = syscall.Unmount(mountpoint, 0); err != nil { 423 logrus.Debugf("Failed to unmount %s overlay: %v", id, err) 424 } 425 return err 426 } 427 return nil 428 } 429 430 // ApplyDiff applies the new layer on top of the root, if parent does not exist with will return a ErrApplyDiffFallback error. 431 func (d *Driver) ApplyDiff(id string, parent string, diff archive.Reader) (size int64, err error) { 432 dir := d.dir(id) 433 434 if parent == "" { 435 return 0, ErrApplyDiffFallback 436 } 437 438 parentRootDir := path.Join(d.dir(parent), "root") 439 if _, err := os.Stat(parentRootDir); err != nil { 440 return 0, ErrApplyDiffFallback 441 } 442 443 // We now know there is a parent, and it has a "root" directory containing 444 // the full root filesystem. We can just hardlink it and apply the 445 // layer. This relies on two things: 446 // 1) ApplyDiff is only run once on a clean (no writes to upper layer) container 447 // 2) ApplyDiff doesn't do any in-place writes to files (would break hardlinks) 448 // These are all currently true and are not expected to break 449 450 tmpRootDir, err := ioutil.TempDir(dir, "tmproot") 451 if err != nil { 452 return 0, err 453 } 454 defer func() { 455 if err != nil { 456 os.RemoveAll(tmpRootDir) 457 } else { 458 os.RemoveAll(path.Join(dir, "upper")) 459 os.RemoveAll(path.Join(dir, "work")) 460 os.RemoveAll(path.Join(dir, "merged")) 461 os.RemoveAll(path.Join(dir, "lower-id")) 462 } 463 }() 464 465 if err = copyDir(parentRootDir, tmpRootDir, copyHardlink); err != nil { 466 return 0, err 467 } 468 469 options := &archive.TarOptions{UIDMaps: d.uidMaps, GIDMaps: d.gidMaps} 470 if size, err = chrootarchive.ApplyUncompressedLayer(tmpRootDir, diff, options); err != nil { 471 return 0, err 472 } 473 474 rootDir := path.Join(dir, "root") 475 if err := os.Rename(tmpRootDir, rootDir); err != nil { 476 return 0, err 477 } 478 479 return 480 } 481 482 // Exists checks to see if the id is already mounted. 483 func (d *Driver) Exists(id string) bool { 484 _, err := os.Stat(d.dir(id)) 485 return err == nil 486 }