dotcloud/docker

View on GitHub
pkg/archive/changes_linux.go

Summary

Maintainability
C
1 day
Test Coverage
package archive // import "github.com/docker/docker/pkg/archive"

import (
    "bytes"
    "fmt"
    "os"
    "path/filepath"
    "sort"
    "syscall"
    "unsafe"

    "github.com/docker/docker/pkg/system"
    "golang.org/x/sys/unix"
)

// walker is used to implement collectFileInfoForChanges on linux. Where this
// method in general returns the entire contents of two directory trees, we
// optimize some FS calls out on linux. In particular, we take advantage of the
// fact that getdents(2) returns the inode of each file in the directory being
// walked, which, when walking two trees in parallel to generate a list of
// changes, can be used to prune subtrees without ever having to lstat(2) them
// directly. Eliminating stat calls in this way can save up to seconds on large
// images.
type walker struct {
    dir1  string
    dir2  string
    root1 *FileInfo
    root2 *FileInfo
}

// collectFileInfoForChanges returns a complete representation of the trees
// rooted at dir1 and dir2, with one important exception: any subtree or
// leaf where the inode and device numbers are an exact match between dir1
// and dir2 will be pruned from the results. This method is *only* to be used
// to generating a list of changes between the two directories, as it does not
// reflect the full contents.
func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) {
    w := &walker{
        dir1:  dir1,
        dir2:  dir2,
        root1: newRootFileInfo(),
        root2: newRootFileInfo(),
    }

    i1, err := os.Lstat(w.dir1)
    if err != nil {
        return nil, nil, err
    }
    i2, err := os.Lstat(w.dir2)
    if err != nil {
        return nil, nil, err
    }

    if err := w.walk("/", i1, i2); err != nil {
        return nil, nil, err
    }

    return w.root1, w.root2, nil
}

// Given a FileInfo, its path info, and a reference to the root of the tree
// being constructed, register this file with the tree.
func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error {
    if fi == nil {
        return nil
    }
    parent := root.LookUp(filepath.Dir(path))
    if parent == nil {
        return fmt.Errorf("walkchunk: Unexpectedly no parent for %s", path)
    }
    info := &FileInfo{
        name:     filepath.Base(path),
        children: make(map[string]*FileInfo),
        parent:   parent,
    }
    cpath := filepath.Join(dir, path)
    stat, err := system.FromStatT(fi.Sys().(*syscall.Stat_t))
    if err != nil {
        return err
    }
    info.stat = stat
    info.capability, _ = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access
    parent.children[info.name] = info
    return nil
}

// Walk a subtree rooted at the same path in both trees being iterated. For
// example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d
func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) {
    // Register these nodes with the return trees, unless we're still at the
    // (already-created) roots:
    if path != "/" {
        if err := walkchunk(path, i1, w.dir1, w.root1); err != nil {
            return err
        }
        if err := walkchunk(path, i2, w.dir2, w.root2); err != nil {
            return err
        }
    }

    is1Dir := i1 != nil && i1.IsDir()
    is2Dir := i2 != nil && i2.IsDir()

    sameDevice := false
    if i1 != nil && i2 != nil {
        si1 := i1.Sys().(*syscall.Stat_t)
        si2 := i2.Sys().(*syscall.Stat_t)
        if si1.Dev == si2.Dev {
            sameDevice = true
        }
    }

    // If these files are both non-existent, or leaves (non-dirs), we are done.
    if !is1Dir && !is2Dir {
        return nil
    }

    // Fetch the names of all the files contained in both directories being walked:
    var names1, names2 []nameIno
    if is1Dir {
        names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access
        if err != nil {
            return err
        }
    }
    if is2Dir {
        names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access
        if err != nil {
            return err
        }
    }

    // We have lists of the files contained in both parallel directories, sorted
    // in the same order. Walk them in parallel, generating a unique merged list
    // of all items present in either or both directories.
    var names []string
    ix1 := 0
    ix2 := 0

    for {
        if ix1 >= len(names1) {
            break
        }
        if ix2 >= len(names2) {
            break
        }

        ni1 := names1[ix1]
        ni2 := names2[ix2]

        switch bytes.Compare([]byte(ni1.name), []byte(ni2.name)) {
        case -1: // ni1 < ni2 -- advance ni1
            // we will not encounter ni1 in names2
            names = append(names, ni1.name)
            ix1++
        case 0: // ni1 == ni2
            if ni1.ino != ni2.ino || !sameDevice {
                names = append(names, ni1.name)
            }
            ix1++
            ix2++
        case 1: // ni1 > ni2 -- advance ni2
            // we will not encounter ni2 in names1
            names = append(names, ni2.name)
            ix2++
        }
    }
    for ix1 < len(names1) {
        names = append(names, names1[ix1].name)
        ix1++
    }
    for ix2 < len(names2) {
        names = append(names, names2[ix2].name)
        ix2++
    }

    // For each of the names present in either or both of the directories being
    // iterated, stat the name under each root, and recurse the pair of them:
    for _, name := range names {
        fname := filepath.Join(path, name)
        var cInfo1, cInfo2 os.FileInfo
        if is1Dir {
            cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access
            if err != nil && !os.IsNotExist(err) {
                return err
            }
        }
        if is2Dir {
            cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access
            if err != nil && !os.IsNotExist(err) {
                return err
            }
        }
        if err = w.walk(fname, cInfo1, cInfo2); err != nil {
            return err
        }
    }
    return nil
}

// {name,inode} pairs used to support the early-pruning logic of the walker type
type nameIno struct {
    name string
    ino  uint64
}

type nameInoSlice []nameIno

func (s nameInoSlice) Len() int           { return len(s) }
func (s nameInoSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name }

// readdirnames is a hacked-apart version of the Go stdlib code, exposing inode
// numbers further up the stack when reading directory contents. Unlike
// os.Readdirnames, which returns a list of filenames, this function returns a
// list of {filename,inode} pairs.
func readdirnames(dirname string) (names []nameIno, err error) {
    var (
        size = 100
        buf  = make([]byte, 4096)
        nbuf int
        bufp int
        nb   int
    )

    f, err := os.Open(dirname)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    names = make([]nameIno, 0, size) // Empty with room to grow.
    for {
        // Refill the buffer if necessary
        if bufp >= nbuf {
            bufp = 0
            nbuf, err = unix.ReadDirent(int(f.Fd()), buf) // getdents on linux
            if nbuf < 0 {
                nbuf = 0
            }
            if err != nil {
                return nil, os.NewSyscallError("readdirent", err)
            }
            if nbuf <= 0 {
                break // EOF
            }
        }

        // Drain the buffer
        nb, names = parseDirent(buf[bufp:nbuf], names)
        bufp += nb
    }

    sl := nameInoSlice(names)
    sort.Sort(sl)
    return sl, nil
}

// parseDirent is a minor modification of unix.ParseDirent (linux version)
// which returns {name,inode} pairs instead of just names.
func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) {
    origlen := len(buf)
    for len(buf) > 0 {
        dirent := (*unix.Dirent)(unsafe.Pointer(&buf[0]))
        buf = buf[dirent.Reclen:]
        if dirent.Ino == 0 { // File absent in directory.
            continue
        }
        bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
        name := string(bytes[0:clen(bytes[:])])
        if name == "." || name == ".." { // Useless names
            continue
        }
        names = append(names, nameIno{name, dirent.Ino})
    }
    return origlen - len(buf), names
}

func clen(n []byte) int {
    for i := 0; i < len(n); i++ {
        if n[i] == 0 {
            return i
        }
    }
    return len(n)
}