github.com/miqui/docker@v1.9.1/pkg/archive/changes_linux.go (about)

     1  package archive
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"syscall"
    10  	"unsafe"
    11  
    12  	"github.com/docker/docker/pkg/system"
    13  )
    14  
    15  // walker is used to implement collectFileInfoForChanges on linux. Where this
    16  // method in general returns the entire contents of two directory trees, we
    17  // optimize some FS calls out on linux. In particular, we take advantage of the
    18  // fact that getdents(2) returns the inode of each file in the directory being
    19  // walked, which, when walking two trees in parallel to generate a list of
    20  // changes, can be used to prune subtrees without ever having to lstat(2) them
    21  // directly. Eliminating stat calls in this way can save up to seconds on large
    22  // images.
    23  type walker struct {
    24  	dir1  string
    25  	dir2  string
    26  	root1 *FileInfo
    27  	root2 *FileInfo
    28  }
    29  
    30  // collectFileInfoForChanges returns a complete representation of the trees
    31  // rooted at dir1 and dir2, with one important exception: any subtree or
    32  // leaf where the inode and device numbers are an exact match between dir1
    33  // and dir2 will be pruned from the results. This method is *only* to be used
    34  // to generating a list of changes between the two directories, as it does not
    35  // reflect the full contents.
    36  func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) {
    37  	w := &walker{
    38  		dir1:  dir1,
    39  		dir2:  dir2,
    40  		root1: newRootFileInfo(),
    41  		root2: newRootFileInfo(),
    42  	}
    43  
    44  	i1, err := os.Lstat(w.dir1)
    45  	if err != nil {
    46  		return nil, nil, err
    47  	}
    48  	i2, err := os.Lstat(w.dir2)
    49  	if err != nil {
    50  		return nil, nil, err
    51  	}
    52  
    53  	if err := w.walk("/", i1, i2); err != nil {
    54  		return nil, nil, err
    55  	}
    56  
    57  	return w.root1, w.root2, nil
    58  }
    59  
    60  // Given a FileInfo, its path info, and a reference to the root of the tree
    61  // being constructed, register this file with the tree.
    62  func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error {
    63  	if fi == nil {
    64  		return nil
    65  	}
    66  	parent := root.LookUp(filepath.Dir(path))
    67  	if parent == nil {
    68  		return fmt.Errorf("collectFileInfoForChanges: Unexpectedly no parent for %s", path)
    69  	}
    70  	info := &FileInfo{
    71  		name:     filepath.Base(path),
    72  		children: make(map[string]*FileInfo),
    73  		parent:   parent,
    74  	}
    75  	cpath := filepath.Join(dir, path)
    76  	stat, err := system.FromStatT(fi.Sys().(*syscall.Stat_t))
    77  	if err != nil {
    78  		return err
    79  	}
    80  	info.stat = stat
    81  	info.capability, _ = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access
    82  	parent.children[info.name] = info
    83  	return nil
    84  }
    85  
    86  // Walk a subtree rooted at the same path in both trees being iterated. For
    87  // example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d
    88  func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) {
    89  	// Register these nodes with the return trees, unless we're still at the
    90  	// (already-created) roots:
    91  	if path != "/" {
    92  		if err := walkchunk(path, i1, w.dir1, w.root1); err != nil {
    93  			return err
    94  		}
    95  		if err := walkchunk(path, i2, w.dir2, w.root2); err != nil {
    96  			return err
    97  		}
    98  	}
    99  
   100  	is1Dir := i1 != nil && i1.IsDir()
   101  	is2Dir := i2 != nil && i2.IsDir()
   102  
   103  	sameDevice := false
   104  	if i1 != nil && i2 != nil {
   105  		si1 := i1.Sys().(*syscall.Stat_t)
   106  		si2 := i2.Sys().(*syscall.Stat_t)
   107  		if si1.Dev == si2.Dev {
   108  			sameDevice = true
   109  		}
   110  	}
   111  
   112  	// If these files are both non-existent, or leaves (non-dirs), we are done.
   113  	if !is1Dir && !is2Dir {
   114  		return nil
   115  	}
   116  
   117  	// Fetch the names of all the files contained in both directories being walked:
   118  	var names1, names2 []nameIno
   119  	if is1Dir {
   120  		names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access
   121  		if err != nil {
   122  			return err
   123  		}
   124  	}
   125  	if is2Dir {
   126  		names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access
   127  		if err != nil {
   128  			return err
   129  		}
   130  	}
   131  
   132  	// We have lists of the files contained in both parallel directories, sorted
   133  	// in the same order. Walk them in parallel, generating a unique merged list
   134  	// of all items present in either or both directories.
   135  	var names []string
   136  	ix1 := 0
   137  	ix2 := 0
   138  
   139  	for {
   140  		if ix1 >= len(names1) {
   141  			break
   142  		}
   143  		if ix2 >= len(names2) {
   144  			break
   145  		}
   146  
   147  		ni1 := names1[ix1]
   148  		ni2 := names2[ix2]
   149  
   150  		switch bytes.Compare([]byte(ni1.name), []byte(ni2.name)) {
   151  		case -1: // ni1 < ni2 -- advance ni1
   152  			// we will not encounter ni1 in names2
   153  			names = append(names, ni1.name)
   154  			ix1++
   155  		case 0: // ni1 == ni2
   156  			if ni1.ino != ni2.ino || !sameDevice {
   157  				names = append(names, ni1.name)
   158  			}
   159  			ix1++
   160  			ix2++
   161  		case 1: // ni1 > ni2 -- advance ni2
   162  			// we will not encounter ni2 in names1
   163  			names = append(names, ni2.name)
   164  			ix2++
   165  		}
   166  	}
   167  	for ix1 < len(names1) {
   168  		names = append(names, names1[ix1].name)
   169  		ix1++
   170  	}
   171  	for ix2 < len(names2) {
   172  		names = append(names, names2[ix2].name)
   173  		ix2++
   174  	}
   175  
   176  	// For each of the names present in either or both of the directories being
   177  	// iterated, stat the name under each root, and recurse the pair of them:
   178  	for _, name := range names {
   179  		fname := filepath.Join(path, name)
   180  		var cInfo1, cInfo2 os.FileInfo
   181  		if is1Dir {
   182  			cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access
   183  			if err != nil && !os.IsNotExist(err) {
   184  				return err
   185  			}
   186  		}
   187  		if is2Dir {
   188  			cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access
   189  			if err != nil && !os.IsNotExist(err) {
   190  				return err
   191  			}
   192  		}
   193  		if err = w.walk(fname, cInfo1, cInfo2); err != nil {
   194  			return err
   195  		}
   196  	}
   197  	return nil
   198  }
   199  
   200  // {name,inode} pairs used to support the early-pruning logic of the walker type
   201  type nameIno struct {
   202  	name string
   203  	ino  uint64
   204  }
   205  
   206  type nameInoSlice []nameIno
   207  
   208  func (s nameInoSlice) Len() int           { return len(s) }
   209  func (s nameInoSlice) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
   210  func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name }
   211  
   212  // readdirnames is a hacked-apart version of the Go stdlib code, exposing inode
   213  // numbers further up the stack when reading directory contents. Unlike
   214  // os.Readdirnames, which returns a list of filenames, this function returns a
   215  // list of {filename,inode} pairs.
   216  func readdirnames(dirname string) (names []nameIno, err error) {
   217  	var (
   218  		size = 100
   219  		buf  = make([]byte, 4096)
   220  		nbuf int
   221  		bufp int
   222  		nb   int
   223  	)
   224  
   225  	f, err := os.Open(dirname)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	defer f.Close()
   230  
   231  	names = make([]nameIno, 0, size) // Empty with room to grow.
   232  	for {
   233  		// Refill the buffer if necessary
   234  		if bufp >= nbuf {
   235  			bufp = 0
   236  			nbuf, err = syscall.ReadDirent(int(f.Fd()), buf) // getdents on linux
   237  			if nbuf < 0 {
   238  				nbuf = 0
   239  			}
   240  			if err != nil {
   241  				return nil, os.NewSyscallError("readdirent", err)
   242  			}
   243  			if nbuf <= 0 {
   244  				break // EOF
   245  			}
   246  		}
   247  
   248  		// Drain the buffer
   249  		nb, names = parseDirent(buf[bufp:nbuf], names)
   250  		bufp += nb
   251  	}
   252  
   253  	sl := nameInoSlice(names)
   254  	sort.Sort(sl)
   255  	return sl, nil
   256  }
   257  
   258  // parseDirent is a minor modification of syscall.ParseDirent (linux version)
   259  // which returns {name,inode} pairs instead of just names.
   260  func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) {
   261  	origlen := len(buf)
   262  	for len(buf) > 0 {
   263  		dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
   264  		buf = buf[dirent.Reclen:]
   265  		if dirent.Ino == 0 { // File absent in directory.
   266  			continue
   267  		}
   268  		bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
   269  		var name = string(bytes[0:clen(bytes[:])])
   270  		if name == "." || name == ".." { // Useless names
   271  			continue
   272  		}
   273  		names = append(names, nameIno{name, dirent.Ino})
   274  	}
   275  	return origlen - len(buf), names
   276  }
   277  
   278  func clen(n []byte) int {
   279  	for i := 0; i < len(n); i++ {
   280  		if n[i] == 0 {
   281  			return i
   282  		}
   283  	}
   284  	return len(n)
   285  }