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

Don't shell out to tar for ExportChanges
This commit is contained in:
Guillaume J. Charmes 2014-01-20 11:08:16 -08:00
commit b563c0c02b
1 changed files with 108 additions and 20 deletions

View File

@ -1,7 +1,10 @@
package archive package archive
import ( import (
"archive/tar"
"fmt" "fmt"
"github.com/dotcloud/docker/utils"
"io"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -310,24 +313,109 @@ func ChangesSize(newDir string, changes []Change) int64 {
return size return size
} }
func ExportChanges(dir string, changes []Change) (Archive, error) { func major(device uint64) uint64 {
files := make([]string, 0) return (device >> 8) & 0xfff
deletions := make([]string, 0) }
for _, change := range changes {
if change.Kind == ChangeModify || change.Kind == ChangeAdd { func minor(device uint64) uint64 {
files = append(files, change.Path) return (device & 0xff) | ((device >> 12) & 0xfff00)
} }
if change.Kind == ChangeDelete {
base := filepath.Base(change.Path) func ExportChanges(dir string, changes []Change) (Archive, error) {
dir := filepath.Dir(change.Path) reader, writer := io.Pipe()
deletions = append(deletions, filepath.Join(dir, ".wh."+base)) tw := tar.NewWriter(writer)
}
} go func() {
// FIXME: Why do we create whiteout files inside Tar code ? // In general we log errors here but ignore them because
return TarFilter(dir, &TarOptions{ // during e.g. a diff operation the container can continue
Compression: Uncompressed, // mutating the filesystem and we can see transient errors
Includes: files, // from this
Recursive: false, for _, change := range changes {
CreateFiles: deletions, 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()
}
}
}
}
// Make sure to check the error on Close.
if err := tw.Close(); err != nil {
utils.Debugf("Can't close layer: %s\n", err)
}
writer.Close()
}()
return reader, nil
} }