github.com/lmars/docker@v1.6.0-rc2/pkg/archive/changes.go (about)

     1  package archive
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"sort"
    10  	"strings"
    11  	"syscall"
    12  	"time"
    13  
    14  	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
    15  
    16  	log "github.com/Sirupsen/logrus"
    17  	"github.com/docker/docker/pkg/pools"
    18  	"github.com/docker/docker/pkg/system"
    19  )
    20  
    21  type ChangeType int
    22  
    23  const (
    24  	ChangeModify = iota
    25  	ChangeAdd
    26  	ChangeDelete
    27  )
    28  
    29  type Change struct {
    30  	Path string
    31  	Kind ChangeType
    32  }
    33  
    34  func (change *Change) String() string {
    35  	var kind string
    36  	switch change.Kind {
    37  	case ChangeModify:
    38  		kind = "C"
    39  	case ChangeAdd:
    40  		kind = "A"
    41  	case ChangeDelete:
    42  		kind = "D"
    43  	}
    44  	return fmt.Sprintf("%s %s", kind, change.Path)
    45  }
    46  
    47  // for sort.Sort
    48  type changesByPath []Change
    49  
    50  func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path }
    51  func (c changesByPath) Len() int           { return len(c) }
    52  func (c changesByPath) Swap(i, j int)      { c[j], c[i] = c[i], c[j] }
    53  
    54  // Gnu tar and the go tar writer don't have sub-second mtime
    55  // precision, which is problematic when we apply changes via tar
    56  // files, we handle this by comparing for exact times, *or* same
    57  // second count and either a or b having exactly 0 nanoseconds
    58  func sameFsTime(a, b time.Time) bool {
    59  	return a == b ||
    60  		(a.Unix() == b.Unix() &&
    61  			(a.Nanosecond() == 0 || b.Nanosecond() == 0))
    62  }
    63  
    64  func sameFsTimeSpec(a, b syscall.Timespec) bool {
    65  	return a.Sec == b.Sec &&
    66  		(a.Nsec == b.Nsec || a.Nsec == 0 || b.Nsec == 0)
    67  }
    68  
    69  // Changes walks the path rw and determines changes for the files in the path,
    70  // with respect to the parent layers
    71  func Changes(layers []string, rw string) ([]Change, error) {
    72  	var changes []Change
    73  	err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
    74  		if err != nil {
    75  			return err
    76  		}
    77  
    78  		// Rebase path
    79  		path, err = filepath.Rel(rw, path)
    80  		if err != nil {
    81  			return err
    82  		}
    83  		path = filepath.Join("/", path)
    84  
    85  		// Skip root
    86  		if path == "/" {
    87  			return nil
    88  		}
    89  
    90  		// Skip AUFS metadata
    91  		if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
    92  			return err
    93  		}
    94  
    95  		change := Change{
    96  			Path: path,
    97  		}
    98  
    99  		// Find out what kind of modification happened
   100  		file := filepath.Base(path)
   101  		// If there is a whiteout, then the file was removed
   102  		if strings.HasPrefix(file, ".wh.") {
   103  			originalFile := file[len(".wh."):]
   104  			change.Path = filepath.Join(filepath.Dir(path), originalFile)
   105  			change.Kind = ChangeDelete
   106  		} else {
   107  			// Otherwise, the file was added
   108  			change.Kind = ChangeAdd
   109  
   110  			// ...Unless it already existed in a top layer, in which case, it's a modification
   111  			for _, layer := range layers {
   112  				stat, err := os.Stat(filepath.Join(layer, path))
   113  				if err != nil && !os.IsNotExist(err) {
   114  					return err
   115  				}
   116  				if err == nil {
   117  					// The file existed in the top layer, so that's a modification
   118  
   119  					// However, if it's a directory, maybe it wasn't actually modified.
   120  					// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
   121  					if stat.IsDir() && f.IsDir() {
   122  						if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
   123  							// Both directories are the same, don't record the change
   124  							return nil
   125  						}
   126  					}
   127  					change.Kind = ChangeModify
   128  					break
   129  				}
   130  			}
   131  		}
   132  
   133  		// Record change
   134  		changes = append(changes, change)
   135  		return nil
   136  	})
   137  	if err != nil && !os.IsNotExist(err) {
   138  		return nil, err
   139  	}
   140  	return changes, nil
   141  }
   142  
   143  type FileInfo struct {
   144  	parent     *FileInfo
   145  	name       string
   146  	stat       *system.Stat_t
   147  	children   map[string]*FileInfo
   148  	capability []byte
   149  	added      bool
   150  }
   151  
   152  func (root *FileInfo) LookUp(path string) *FileInfo {
   153  	parent := root
   154  	if path == "/" {
   155  		return root
   156  	}
   157  
   158  	pathElements := strings.Split(path, "/")
   159  	for _, elem := range pathElements {
   160  		if elem != "" {
   161  			child := parent.children[elem]
   162  			if child == nil {
   163  				return nil
   164  			}
   165  			parent = child
   166  		}
   167  	}
   168  	return parent
   169  }
   170  
   171  func (info *FileInfo) path() string {
   172  	if info.parent == nil {
   173  		return "/"
   174  	}
   175  	return filepath.Join(info.parent.path(), info.name)
   176  }
   177  
   178  func (info *FileInfo) isDir() bool {
   179  	return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR == syscall.S_IFDIR
   180  }
   181  
   182  func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
   183  
   184  	sizeAtEntry := len(*changes)
   185  
   186  	if oldInfo == nil {
   187  		// add
   188  		change := Change{
   189  			Path: info.path(),
   190  			Kind: ChangeAdd,
   191  		}
   192  		*changes = append(*changes, change)
   193  		info.added = true
   194  	}
   195  
   196  	// We make a copy so we can modify it to detect additions
   197  	// also, we only recurse on the old dir if the new info is a directory
   198  	// otherwise any previous delete/change is considered recursive
   199  	oldChildren := make(map[string]*FileInfo)
   200  	if oldInfo != nil && info.isDir() {
   201  		for k, v := range oldInfo.children {
   202  			oldChildren[k] = v
   203  		}
   204  	}
   205  
   206  	for name, newChild := range info.children {
   207  		oldChild, _ := oldChildren[name]
   208  		if oldChild != nil {
   209  			// change?
   210  			oldStat := oldChild.stat
   211  			newStat := newChild.stat
   212  			// Note: We can't compare inode or ctime or blocksize here, because these change
   213  			// when copying a file into a container. However, that is not generally a problem
   214  			// because any content change will change mtime, and any status change should
   215  			// be visible when actually comparing the stat fields. The only time this
   216  			// breaks down is if some code intentionally hides a change by setting
   217  			// back mtime
   218  			if oldStat.Mode() != newStat.Mode() ||
   219  				oldStat.Uid() != newStat.Uid() ||
   220  				oldStat.Gid() != newStat.Gid() ||
   221  				oldStat.Rdev() != newStat.Rdev() ||
   222  				// Don't look at size for dirs, its not a good measure of change
   223  				(oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
   224  					(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) ||
   225  				bytes.Compare(oldChild.capability, newChild.capability) != 0 {
   226  				change := Change{
   227  					Path: newChild.path(),
   228  					Kind: ChangeModify,
   229  				}
   230  				*changes = append(*changes, change)
   231  				newChild.added = true
   232  			}
   233  
   234  			// Remove from copy so we can detect deletions
   235  			delete(oldChildren, name)
   236  		}
   237  
   238  		newChild.addChanges(oldChild, changes)
   239  	}
   240  	for _, oldChild := range oldChildren {
   241  		// delete
   242  		change := Change{
   243  			Path: oldChild.path(),
   244  			Kind: ChangeDelete,
   245  		}
   246  		*changes = append(*changes, change)
   247  	}
   248  
   249  	// If there were changes inside this directory, we need to add it, even if the directory
   250  	// itself wasn't changed. This is needed to properly save and restore filesystem permissions.
   251  	if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != "/" {
   252  		change := Change{
   253  			Path: info.path(),
   254  			Kind: ChangeModify,
   255  		}
   256  		// Let's insert the directory entry before the recently added entries located inside this dir
   257  		*changes = append(*changes, change) // just to resize the slice, will be overwritten
   258  		copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:])
   259  		(*changes)[sizeAtEntry] = change
   260  	}
   261  
   262  }
   263  
   264  func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
   265  	var changes []Change
   266  
   267  	info.addChanges(oldInfo, &changes)
   268  
   269  	return changes
   270  }
   271  
   272  func newRootFileInfo() *FileInfo {
   273  	root := &FileInfo{
   274  		name:     "/",
   275  		children: make(map[string]*FileInfo),
   276  	}
   277  	return root
   278  }
   279  
   280  func collectFileInfo(sourceDir string) (*FileInfo, error) {
   281  	root := newRootFileInfo()
   282  
   283  	err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
   284  		if err != nil {
   285  			return err
   286  		}
   287  
   288  		// Rebase path
   289  		relPath, err := filepath.Rel(sourceDir, path)
   290  		if err != nil {
   291  			return err
   292  		}
   293  		relPath = filepath.Join("/", relPath)
   294  
   295  		if relPath == "/" {
   296  			return nil
   297  		}
   298  
   299  		parent := root.LookUp(filepath.Dir(relPath))
   300  		if parent == nil {
   301  			return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
   302  		}
   303  
   304  		info := &FileInfo{
   305  			name:     filepath.Base(relPath),
   306  			children: make(map[string]*FileInfo),
   307  			parent:   parent,
   308  		}
   309  
   310  		s, err := system.Lstat(path)
   311  		if err != nil {
   312  			return err
   313  		}
   314  		info.stat = s
   315  
   316  		info.capability, _ = system.Lgetxattr(path, "security.capability")
   317  
   318  		parent.children[info.name] = info
   319  
   320  		return nil
   321  	})
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	return root, nil
   326  }
   327  
   328  // ChangesDirs compares two directories and generates an array of Change objects describing the changes.
   329  // If oldDir is "", then all files in newDir will be Add-Changes.
   330  func ChangesDirs(newDir, oldDir string) ([]Change, error) {
   331  	var (
   332  		oldRoot, newRoot *FileInfo
   333  		err1, err2       error
   334  		errs             = make(chan error, 2)
   335  	)
   336  	go func() {
   337  		if oldDir != "" {
   338  			oldRoot, err1 = collectFileInfo(oldDir)
   339  		}
   340  		errs <- err1
   341  	}()
   342  	go func() {
   343  		newRoot, err2 = collectFileInfo(newDir)
   344  		errs <- err2
   345  	}()
   346  
   347  	// block until both routines have returned
   348  	for i := 0; i < 2; i++ {
   349  		if err := <-errs; err != nil {
   350  			return nil, err
   351  		}
   352  	}
   353  
   354  	return newRoot.Changes(oldRoot), nil
   355  }
   356  
   357  // ChangesSize calculates the size in bytes of the provided changes, based on newDir.
   358  func ChangesSize(newDir string, changes []Change) int64 {
   359  	var size int64
   360  	for _, change := range changes {
   361  		if change.Kind == ChangeModify || change.Kind == ChangeAdd {
   362  			file := filepath.Join(newDir, change.Path)
   363  			fileInfo, _ := os.Lstat(file)
   364  			if fileInfo != nil && !fileInfo.IsDir() {
   365  				size += fileInfo.Size()
   366  			}
   367  		}
   368  	}
   369  	return size
   370  }
   371  
   372  // ExportChanges produces an Archive from the provided changes, relative to dir.
   373  func ExportChanges(dir string, changes []Change) (Archive, error) {
   374  	reader, writer := io.Pipe()
   375  	go func() {
   376  		ta := &tarAppender{
   377  			TarWriter: tar.NewWriter(writer),
   378  			Buffer:    pools.BufioWriter32KPool.Get(nil),
   379  			SeenFiles: make(map[uint64]string),
   380  		}
   381  		// this buffer is needed for the duration of this piped stream
   382  		defer pools.BufioWriter32KPool.Put(ta.Buffer)
   383  
   384  		sort.Sort(changesByPath(changes))
   385  
   386  		// In general we log errors here but ignore them because
   387  		// during e.g. a diff operation the container can continue
   388  		// mutating the filesystem and we can see transient errors
   389  		// from this
   390  		for _, change := range changes {
   391  			if change.Kind == ChangeDelete {
   392  				whiteOutDir := filepath.Dir(change.Path)
   393  				whiteOutBase := filepath.Base(change.Path)
   394  				whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase)
   395  				timestamp := time.Now()
   396  				hdr := &tar.Header{
   397  					Name:       whiteOut[1:],
   398  					Size:       0,
   399  					ModTime:    timestamp,
   400  					AccessTime: timestamp,
   401  					ChangeTime: timestamp,
   402  				}
   403  				if err := ta.TarWriter.WriteHeader(hdr); err != nil {
   404  					log.Debugf("Can't write whiteout header: %s", err)
   405  				}
   406  			} else {
   407  				path := filepath.Join(dir, change.Path)
   408  				if err := ta.addTarFile(path, change.Path[1:]); err != nil {
   409  					log.Debugf("Can't add file %s to tar: %s", path, err)
   410  				}
   411  			}
   412  		}
   413  
   414  		// Make sure to check the error on Close.
   415  		if err := ta.TarWriter.Close(); err != nil {
   416  			log.Debugf("Can't close layer: %s", err)
   417  		}
   418  		if err := writer.Close(); err != nil {
   419  			log.Debugf("failed close Changes writer: %s", err)
   420  		}
   421  	}()
   422  	return reader, nil
   423  }