123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- // +build linux
- package overlay
- import (
- "bufio"
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path"
- "syscall"
- "github.com/Sirupsen/logrus"
- "github.com/docker/docker/daemon/graphdriver"
- "github.com/docker/docker/pkg/archive"
- "github.com/docker/docker/pkg/idtools"
- "github.com/docker/docker/pkg/mount"
- "github.com/opencontainers/runc/libcontainer/label"
- )
- // This is a small wrapper over the NaiveDiffWriter that lets us have a custom
- // implementation of ApplyDiff()
- var (
- // ErrApplyDiffFallback is returned to indicate that a normal ApplyDiff is applied as a fallback from Naive diff writer.
- ErrApplyDiffFallback = fmt.Errorf("Fall back to normal ApplyDiff")
- backingFs = "<unknown>"
- )
- // ApplyDiffProtoDriver wraps the ProtoDriver by extending the interface with ApplyDiff method.
- type ApplyDiffProtoDriver interface {
- graphdriver.ProtoDriver
- // ApplyDiff writes the diff to the archive for the given id and parent id.
- // It returns the size in bytes written if successful, an error ErrApplyDiffFallback is returned otherwise.
- ApplyDiff(id, parent string, diff archive.Reader) (size int64, err error)
- }
- type naiveDiffDriverWithApply struct {
- graphdriver.Driver
- applyDiff ApplyDiffProtoDriver
- }
- // NaiveDiffDriverWithApply returns a NaiveDiff driver with custom ApplyDiff.
- func NaiveDiffDriverWithApply(driver ApplyDiffProtoDriver, uidMaps, gidMaps []idtools.IDMap) graphdriver.Driver {
- return &naiveDiffDriverWithApply{
- Driver: graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps),
- applyDiff: driver,
- }
- }
- // ApplyDiff creates a diff layer with either the NaiveDiffDriver or with a fallback.
- func (d *naiveDiffDriverWithApply) ApplyDiff(id, parent string, diff archive.Reader) (int64, error) {
- b, err := d.applyDiff.ApplyDiff(id, parent, diff)
- if err == ErrApplyDiffFallback {
- return d.Driver.ApplyDiff(id, parent, diff)
- }
- return b, err
- }
- // This backend uses the overlay union filesystem for containers
- // plus hard link file sharing for images.
- // Each container/image can have a "root" subdirectory which is a plain
- // filesystem hierarchy, or they can use overlay.
- // If they use overlay there is a "upper" directory and a "lower-id"
- // file, as well as "merged" and "work" directories. The "upper"
- // directory has the upper layer of the overlay, and "lower-id" contains
- // the id of the parent whose "root" directory shall be used as the lower
- // layer in the overlay. The overlay itself is mounted in the "merged"
- // directory, and the "work" dir is needed for overlay to work.
- // When an overlay layer is created there are two cases, either the
- // parent has a "root" dir, then we start out with an empty "upper"
- // directory overlaid on the parents root. This is typically the
- // case with the init layer of a container which is based on an image.
- // If there is no "root" in the parent, we inherit the lower-id from
- // the parent and start by making a copy in the parent's "upper" dir.
- // This is typically the case for a container layer which copies
- // its parent -init upper layer.
- // Additionally we also have a custom implementation of ApplyLayer
- // which makes a recursive copy of the parent "root" layer using
- // hardlinks to share file data, and then applies the layer on top
- // of that. This means all child images share file (but not directory)
- // data with the parent.
- // Driver contains information about the home directory and the list of active mounts that are created using this driver.
- type Driver struct {
- home string
- uidMaps []idtools.IDMap
- gidMaps []idtools.IDMap
- ctr *graphdriver.RefCounter
- }
- func init() {
- graphdriver.Register("overlay", Init)
- }
- // Init returns the NaiveDiffDriver, a native diff driver for overlay filesystem.
- // If overlay filesystem is not supported on the host, graphdriver.ErrNotSupported is returned as error.
- // If an overlay filesystem is not supported over an existing filesystem then error graphdriver.ErrIncompatibleFS is returned.
- func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
- if err := supportsOverlay(); err != nil {
- return nil, graphdriver.ErrNotSupported
- }
- fsMagic, err := graphdriver.GetFSMagic(home)
- if err != nil {
- return nil, err
- }
- if fsName, ok := graphdriver.FsNames[fsMagic]; ok {
- backingFs = fsName
- }
- switch fsMagic {
- case graphdriver.FsMagicAufs, graphdriver.FsMagicBtrfs, graphdriver.FsMagicOverlay, graphdriver.FsMagicZfs, graphdriver.FsMagicEcryptfs:
- logrus.Errorf("'overlay' is not supported over %s", backingFs)
- return nil, graphdriver.ErrIncompatibleFS
- }
- rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
- if err != nil {
- return nil, err
- }
- // Create the driver home dir
- if err := idtools.MkdirAllAs(home, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
- return nil, err
- }
- if err := mount.MakePrivate(home); err != nil {
- return nil, err
- }
- d := &Driver{
- home: home,
- uidMaps: uidMaps,
- gidMaps: gidMaps,
- ctr: graphdriver.NewRefCounter(graphdriver.NewFsChecker(graphdriver.FsMagicOverlay)),
- }
- return NaiveDiffDriverWithApply(d, uidMaps, gidMaps), nil
- }
- func supportsOverlay() error {
- // We can try to modprobe overlay first before looking at
- // proc/filesystems for when overlay is supported
- exec.Command("modprobe", "overlay").Run()
- f, err := os.Open("/proc/filesystems")
- if err != nil {
- return err
- }
- defer f.Close()
- s := bufio.NewScanner(f)
- for s.Scan() {
- if s.Text() == "nodev\toverlay" {
- return nil
- }
- }
- logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.")
- return graphdriver.ErrNotSupported
- }
- func (d *Driver) String() string {
- return "overlay"
- }
- // Status returns current driver information in a two dimensional string array.
- // Output contains "Backing Filesystem" used in this implementation.
- func (d *Driver) Status() [][2]string {
- return [][2]string{
- {"Backing Filesystem", backingFs},
- }
- }
- // GetMetadata returns meta data about the overlay driver such as root, LowerDir, UpperDir, WorkDir and MergeDir used to store data.
- func (d *Driver) GetMetadata(id string) (map[string]string, error) {
- dir := d.dir(id)
- if _, err := os.Stat(dir); err != nil {
- return nil, err
- }
- metadata := make(map[string]string)
- // If id has a root, it is an image
- rootDir := path.Join(dir, "root")
- if _, err := os.Stat(rootDir); err == nil {
- metadata["RootDir"] = rootDir
- return metadata, nil
- }
- lowerID, err := ioutil.ReadFile(path.Join(dir, "lower-id"))
- if err != nil {
- return nil, err
- }
- metadata["LowerDir"] = path.Join(d.dir(string(lowerID)), "root")
- metadata["UpperDir"] = path.Join(dir, "upper")
- metadata["WorkDir"] = path.Join(dir, "work")
- metadata["MergedDir"] = path.Join(dir, "merged")
- return metadata, nil
- }
- // Cleanup any state created by overlay which should be cleaned when daemon
- // is being shutdown. For now, we just have to unmount the bind mounted
- // we had created.
- func (d *Driver) Cleanup() error {
- return mount.Unmount(d.home)
- }
- // CreateReadWrite creates a layer that is writable for use as a container
- // file system.
- func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[string]string) error {
- return d.Create(id, parent, mountLabel, storageOpt)
- }
- // Create is used to create the upper, lower, and merge directories required for overlay fs for a given id.
- // The parent filesystem is used to configure these directories for the overlay.
- func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) (retErr error) {
- if len(storageOpt) != 0 {
- return fmt.Errorf("--storage-opt is not supported for overlay")
- }
- dir := d.dir(id)
- rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
- if err != nil {
- return err
- }
- if err := idtools.MkdirAllAs(path.Dir(dir), 0700, rootUID, rootGID); err != nil {
- return err
- }
- if err := idtools.MkdirAs(dir, 0700, rootUID, rootGID); err != nil {
- return err
- }
- defer func() {
- // Clean up on failure
- if retErr != nil {
- os.RemoveAll(dir)
- }
- }()
- // Toplevel images are just a "root" dir
- if parent == "" {
- if err := idtools.MkdirAs(path.Join(dir, "root"), 0755, rootUID, rootGID); err != nil {
- return err
- }
- return nil
- }
- parentDir := d.dir(parent)
- // Ensure parent exists
- if _, err := os.Lstat(parentDir); err != nil {
- return err
- }
- // If parent has a root, just do an overlay to it
- parentRoot := path.Join(parentDir, "root")
- if s, err := os.Lstat(parentRoot); err == nil {
- if err := idtools.MkdirAs(path.Join(dir, "upper"), s.Mode(), rootUID, rootGID); err != nil {
- return err
- }
- if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil {
- return err
- }
- if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil {
- return err
- }
- if err := ioutil.WriteFile(path.Join(dir, "lower-id"), []byte(parent), 0666); err != nil {
- return err
- }
- return nil
- }
- // Otherwise, copy the upper and the lower-id from the parent
- lowerID, err := ioutil.ReadFile(path.Join(parentDir, "lower-id"))
- if err != nil {
- return err
- }
- if err := ioutil.WriteFile(path.Join(dir, "lower-id"), lowerID, 0666); err != nil {
- return err
- }
- parentUpperDir := path.Join(parentDir, "upper")
- s, err := os.Lstat(parentUpperDir)
- if err != nil {
- return err
- }
- upperDir := path.Join(dir, "upper")
- if err := idtools.MkdirAs(upperDir, s.Mode(), rootUID, rootGID); err != nil {
- return err
- }
- if err := idtools.MkdirAs(path.Join(dir, "work"), 0700, rootUID, rootGID); err != nil {
- return err
- }
- if err := idtools.MkdirAs(path.Join(dir, "merged"), 0700, rootUID, rootGID); err != nil {
- return err
- }
- return copyDir(parentUpperDir, upperDir, 0)
- }
- func (d *Driver) dir(id string) string {
- return path.Join(d.home, id)
- }
- // Remove cleans the directories that are created for this id.
- func (d *Driver) Remove(id string) error {
- if err := os.RemoveAll(d.dir(id)); err != nil && !os.IsNotExist(err) {
- return err
- }
- return nil
- }
- // Get creates and mounts the required file system for the given id and returns the mount path.
- func (d *Driver) Get(id string, mountLabel string) (s string, err error) {
- dir := d.dir(id)
- if _, err := os.Stat(dir); err != nil {
- return "", err
- }
- // If id has a root, just return it
- rootDir := path.Join(dir, "root")
- if _, err := os.Stat(rootDir); err == nil {
- return rootDir, nil
- }
- mergedDir := path.Join(dir, "merged")
- if count := d.ctr.Increment(mergedDir); count > 1 {
- return mergedDir, nil
- }
- defer func() {
- if err != nil {
- if c := d.ctr.Decrement(mergedDir); c <= 0 {
- syscall.Unmount(mergedDir, 0)
- }
- }
- }()
- lowerID, err := ioutil.ReadFile(path.Join(dir, "lower-id"))
- if err != nil {
- return "", err
- }
- var (
- lowerDir = path.Join(d.dir(string(lowerID)), "root")
- upperDir = path.Join(dir, "upper")
- workDir = path.Join(dir, "work")
- opts = fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lowerDir, upperDir, workDir)
- )
- if err := syscall.Mount("overlay", mergedDir, "overlay", 0, label.FormatMountLabel(opts, mountLabel)); err != nil {
- return "", fmt.Errorf("error creating overlay mount to %s: %v", mergedDir, err)
- }
- // chown "workdir/work" to the remapped root UID/GID. Overlay fs inside a
- // user namespace requires this to move a directory from lower to upper.
- rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
- if err != nil {
- return "", err
- }
- if err := os.Chown(path.Join(workDir, "work"), rootUID, rootGID); err != nil {
- return "", err
- }
- return mergedDir, nil
- }
- // Put unmounts the mount path created for the give id.
- func (d *Driver) Put(id string) error {
- mountpoint := path.Join(d.dir(id), "merged")
- if count := d.ctr.Decrement(mountpoint); count > 0 {
- return nil
- }
- if err := syscall.Unmount(mountpoint, 0); err != nil {
- logrus.Debugf("Failed to unmount %s overlay: %v", id, err)
- }
- return nil
- }
- // ApplyDiff applies the new layer on top of the root, if parent does not exist with will return an ErrApplyDiffFallback error.
- func (d *Driver) ApplyDiff(id string, parent string, diff archive.Reader) (size int64, err error) {
- dir := d.dir(id)
- if parent == "" {
- return 0, ErrApplyDiffFallback
- }
- parentRootDir := path.Join(d.dir(parent), "root")
- if _, err := os.Stat(parentRootDir); err != nil {
- return 0, ErrApplyDiffFallback
- }
- // We now know there is a parent, and it has a "root" directory containing
- // the full root filesystem. We can just hardlink it and apply the
- // layer. This relies on two things:
- // 1) ApplyDiff is only run once on a clean (no writes to upper layer) container
- // 2) ApplyDiff doesn't do any in-place writes to files (would break hardlinks)
- // These are all currently true and are not expected to break
- tmpRootDir, err := ioutil.TempDir(dir, "tmproot")
- if err != nil {
- return 0, err
- }
- defer func() {
- if err != nil {
- os.RemoveAll(tmpRootDir)
- } else {
- os.RemoveAll(path.Join(dir, "upper"))
- os.RemoveAll(path.Join(dir, "work"))
- os.RemoveAll(path.Join(dir, "merged"))
- os.RemoveAll(path.Join(dir, "lower-id"))
- }
- }()
- if err = copyDir(parentRootDir, tmpRootDir, copyHardlink); err != nil {
- return 0, err
- }
- options := &archive.TarOptions{UIDMaps: d.uidMaps, GIDMaps: d.gidMaps}
- if size, err = graphdriver.ApplyUncompressedLayer(tmpRootDir, diff, options); err != nil {
- return 0, err
- }
- rootDir := path.Join(dir, "root")
- if err := os.Rename(tmpRootDir, rootDir); err != nil {
- return 0, err
- }
- return
- }
- // Exists checks to see if the id is already mounted.
- func (d *Driver) Exists(id string) bool {
- _, err := os.Stat(d.dir(id))
- return err == nil
- }
|