Преглед изворни кода

Merge pull request #3292 from alexlarsson/export-changes-manual

Don't shell out to tar for ExportChanges
Guillaume J. Charmes пре 11 година
родитељ
комит
b563c0c02b
1 измењених фајлова са 105 додато и 17 уклоњено
  1. 105 17
      archive/changes.go

+ 105 - 17
archive/changes.go

@@ -1,7 +1,10 @@
 package archive
 
 import (
+	"archive/tar"
 	"fmt"
+	"github.com/dotcloud/docker/utils"
+	"io"
 	"os"
 	"path/filepath"
 	"strings"
@@ -310,24 +313,109 @@ func ChangesSize(newDir string, changes []Change) int64 {
 	return size
 }
 
+func major(device uint64) uint64 {
+	return (device >> 8) & 0xfff
+}
+
+func minor(device uint64) uint64 {
+	return (device & 0xff) | ((device >> 12) & 0xfff00)
+}
+
 func ExportChanges(dir string, changes []Change) (Archive, error) {
-	files := make([]string, 0)
-	deletions := make([]string, 0)
-	for _, change := range changes {
-		if change.Kind == ChangeModify || change.Kind == ChangeAdd {
-			files = append(files, change.Path)
+	reader, writer := io.Pipe()
+	tw := tar.NewWriter(writer)
+
+	go func() {
+		// In general we log errors here but ignore them because
+		// during e.g. a diff operation the container can continue
+		// mutating the filesystem and we can see transient errors
+		// from this
+		for _, change := range changes {
+			if change.Kind == ChangeDelete {
+				whiteOutDir := filepath.Dir(change.Path)
+				whiteOutBase := filepath.Base(change.Path)
+				whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase)
+				hdr := &tar.Header{
+					Name:       whiteOut[1:],
+					Size:       0,
+					ModTime:    time.Now(),
+					AccessTime: time.Now(),
+					ChangeTime: time.Now(),
+				}
+				if err := tw.WriteHeader(hdr); err != nil {
+					utils.Debugf("Can't write whiteout header: %s\n", err)
+				}
+			} else {
+				path := filepath.Join(dir, change.Path)
+
+				var stat syscall.Stat_t
+				if err := syscall.Lstat(path, &stat); err != nil {
+					utils.Debugf("Can't stat source file: %s\n", err)
+					continue
+				}
+
+				mtim := getLastModification(&stat)
+				atim := getLastAccess(&stat)
+				hdr := &tar.Header{
+					Name:       change.Path[1:],
+					Mode:       int64(stat.Mode & 07777),
+					Uid:        int(stat.Uid),
+					Gid:        int(stat.Gid),
+					ModTime:    time.Unix(int64(mtim.Sec), int64(mtim.Nsec)),
+					AccessTime: time.Unix(int64(atim.Sec), int64(atim.Nsec)),
+				}
+
+				if stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR {
+					hdr.Typeflag = tar.TypeDir
+				} else if stat.Mode&syscall.S_IFLNK == syscall.S_IFLNK {
+					hdr.Typeflag = tar.TypeSymlink
+					if link, err := os.Readlink(path); err != nil {
+						utils.Debugf("Can't readlink source file: %s\n", err)
+						continue
+					} else {
+						hdr.Linkname = link
+					}
+				} else if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
+					stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
+					if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK {
+						hdr.Typeflag = tar.TypeBlock
+					} else {
+						hdr.Typeflag = tar.TypeChar
+					}
+					hdr.Devmajor = int64(major(uint64(stat.Rdev)))
+					hdr.Devminor = int64(minor(uint64(stat.Rdev)))
+				} else if stat.Mode&syscall.S_IFIFO == syscall.S_IFIFO ||
+					stat.Mode&syscall.S_IFSOCK == syscall.S_IFSOCK {
+					hdr.Typeflag = tar.TypeFifo
+				} else if stat.Mode&syscall.S_IFREG == syscall.S_IFREG {
+					hdr.Typeflag = tar.TypeReg
+					hdr.Size = stat.Size
+				} else {
+					utils.Debugf("Unknown file type: %s\n", path)
+					continue
+				}
+
+				if err := tw.WriteHeader(hdr); err != nil {
+					utils.Debugf("Can't write tar header: %s\n", err)
+				}
+				if hdr.Typeflag == tar.TypeReg {
+					if file, err := os.Open(path); err != nil {
+						utils.Debugf("Can't open file: %s\n", err)
+					} else {
+						_, err := io.Copy(tw, file)
+						if err != nil {
+							utils.Debugf("Can't copy file: %s\n", err)
+						}
+						file.Close()
+					}
+				}
+			}
 		}
-		if change.Kind == ChangeDelete {
-			base := filepath.Base(change.Path)
-			dir := filepath.Dir(change.Path)
-			deletions = append(deletions, filepath.Join(dir, ".wh."+base))
+		// Make sure to check the error on Close.
+		if err := tw.Close(); err != nil {
+			utils.Debugf("Can't close layer: %s\n", err)
 		}
-	}
-	// FIXME: Why do we create whiteout files inside Tar code ?
-	return TarFilter(dir, &TarOptions{
-		Compression: Uncompressed,
-		Includes:    files,
-		Recursive:   false,
-		CreateFiles: deletions,
-	})
+		writer.Close()
+	}()
+	return reader, nil
 }