Skip to content

Commit

Permalink
Go through layers twice to resolve hardlinks
Browse files Browse the repository at this point in the history
  • Loading branch information
Priya Wadhwa committed Aug 14, 2018
1 parent 84787f4 commit 3dc172a
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 35 deletions.
4 changes: 0 additions & 4 deletions integration/dockerfiles/Dockerfile_hardlink_base
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
FROM alpine@sha256:5ce5f501c457015c4b91f91a15ac69157d9b06f1a75cf9107bf2b62e0843983a AS stage1
RUN apk --no-cache add git
# Test removing a file and symlinking it
RUN rm /usr/bin/git && ln -s /usr/libexec/git-core/git /usr/bin/git

# Test changing a file
RUN rm /usr/libexec/git-core/git-diff && echo "something" > /usr/libexec/git-core/git-diff
6 changes: 6 additions & 0 deletions integration/dockerfiles/Dockerfile_test_hardlink
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
FROM gcr.io/kaniko-test/hardlink-base:latest
RUN ls -al /usr/libexec/git-core/git /usr/bin/git /usr/libexec/git-core/git-diff
RUN stat /usr/bin/git
RUN stat /usr/libexec/git-core/git
RUN git --version
WORKDIR /temp/dir
RUN git init
ADD context/foo foo
RUN git add foo
2 changes: 1 addition & 1 deletion integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ func TestMain(m *testing.M) {
}

fmt.Println("Building hardlink base image")
buildHardlinkBase := exec.Command("docker", "build", "-t", config.hardlinkBaseImage, "-f", "dockerfiles/Dockerfile_onbuild_base", ".")
buildHardlinkBase := exec.Command("docker", "build", "-t", config.hardlinkBaseImage, "-f", "dockerfiles/Dockerfile_hardlink_base", ".")
if err := buildHardlinkBase.Run(); err != nil {
fmt.Printf("error building hardlink base: %v", err)
os.Exit(1)
Expand Down
2 changes: 0 additions & 2 deletions pkg/dockerfile/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (

"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"
"github.com/sirupsen/logrus"
)

// Stages reads the Dockerfile, validates it's contents, and returns stages
Expand Down Expand Up @@ -113,7 +112,6 @@ func ParseCommands(cmdArray []string) ([]instructions.Command, error) {

// SaveStage returns true if the current stage will be needed later in the Dockerfile
func SaveStage(index int, stages []instructions.Stage) bool {
logrus.Infof("looking into saving stage %d", index)
for stageIndex, stage := range stages {
if stageIndex <= index {
continue
Expand Down
119 changes: 91 additions & 28 deletions pkg/util/fs_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package util
import (
"archive/tar"
"bufio"
"bytes"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
Expand All @@ -43,8 +45,8 @@ var whitelist = []string{
var volumeWhitelist = []string{}

type hardlink struct {
links []*tar.Header
reader io.Reader
links []*tar.Header
contents []byte
}

func GetFSFromImage(root string, img v1.Image) error {
Expand All @@ -60,7 +62,10 @@ func GetFSFromImage(root string, img v1.Image) error {

fs := map[string]struct{}{}
whiteouts := map[string]struct{}{}
hardlinks := map[string]*hardlink{}
hardlinks, err := retrieveHardlinks(layers)
if err != nil {
return err
}

for i := len(layers) - 1; i >= 0; i-- {
logrus.Infof("Unpacking layer: %d", i)
Expand All @@ -78,6 +83,10 @@ func GetFSFromImage(root string, img v1.Image) error {
if err != nil {
return err
}
contents, err := ioutil.ReadAll(tr)
if err != nil {
return err
}
path := filepath.Join(root, filepath.Clean(hdr.Name))
base := filepath.Base(path)
dir := filepath.Dir(path)
Expand All @@ -87,6 +96,9 @@ func GetFSFromImage(root string, img v1.Image) error {
whiteouts[filepath.Join(dir, name)] = struct{}{}
continue
}
if err := resolveHardlink(hdr, contents, hardlinks); err != nil {
return err
}

if checkWhiteouts(path, whiteouts) {
logrus.Infof("Not adding %s because it is whited out", path)
Expand All @@ -107,43 +119,36 @@ func GetFSFromImage(root string, img v1.Image) error {
}
}
if hdr.Typeflag == tar.TypeLink {
// If linkname no longer exists, extract hardlink as regular file
linkname := filepath.Clean(filepath.Join("/", hdr.Linkname))
if CheckWhitelist(linkname) {
logrus.Debugf("skipping hardlink from %s to %s because %s is whitelisted", linkname, path, linkname)
continue
}
_, previouslyAdded := fs[linkname]
if previouslyAdded || checkWhiteouts(linkname, whiteouts) {
if h, ok := hardlinks[linkname]; ok {
h.links = append(h.links, hdr)
continue
}
hardlinks[linkname] = &hardlink{
links: []*tar.Header{hdr},
reader: tr,
}
continue
}
logrus.Infof("normally adding hardlink for %s", hdr)
continue
}

fs[path] = struct{}{}

if err := extractFile(root, hdr, tr); err != nil {
if err := extractFile(root, hdr, bytes.NewReader(contents)); err != nil {
return err
}
}
}
// Process hardlinks
for _, h := range hardlinks {
for k, h := range hardlinks {
if regularFile(k) {
for _, link := range h.links {
if err := extractFile(root, link, bytes.NewReader(h.contents)); err != nil {
return err
}
}
continue
}
original := h.links[0]
original.Typeflag = tar.TypeReg
if err := extractFile(root, original, h.reader); err != nil {
if err := extractFile(root, original, bytes.NewReader(h.contents)); err != nil {
return err
}
logrus.Infof("num links for %s is %s", original.Name, len(h.links))
for _, link := range h.links[1:] {
link.Linkname = original.Name
if FilepathExists(filepath.Clean(filepath.Join("/", link.Name))) {
continue
}
if err := extractFile(root, link, nil); err != nil {
return err
}
Expand All @@ -152,6 +157,66 @@ func GetFSFromImage(root string, img v1.Image) error {
return nil
}

func regularFile(fp string) bool {
fi, err := os.Stat(fp)
if err != nil {
return false
}
return fi.Mode().IsRegular()
}

func retrieveHardlinks(layers []v1.Layer) (map[string]*hardlink, error) {
hardlinks := map[string]*hardlink{}
for i := len(layers) - 1; i >= 0; i-- {
l := layers[i]
r, err := l.Uncompressed()
if err != nil {
return nil, err
}
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if hdr.Typeflag == tar.TypeLink {
// If linkname no longer exists, extract hardlink as regular file
linkname := filepath.Clean(filepath.Join("/", hdr.Linkname))
if CheckWhitelist(linkname) {
continue
}
if h, ok := hardlinks[linkname]; ok {
h.links = append(h.links, hdr)
continue
}
hardlinks[linkname] = &hardlink{
links: []*tar.Header{hdr},
}
continue
}
}
}
return hardlinks, nil
}

func resolveHardlink(hdr *tar.Header, contents []byte, hardlinks map[string]*hardlink) error {
for k, h := range hardlinks {
if h.contents != nil {
return nil
}
if hdr.Typeflag != tar.TypeReg {
continue
}
if k == filepath.Clean(filepath.Join("/", hdr.Name)) {
h.contents = contents
}
}
return nil
}

// DeleteFilesystem deletes the extracted image file system
func DeleteFilesystem() error {
logrus.Info("Deleting filesystem...")
Expand Down Expand Up @@ -254,8 +319,6 @@ func extractFile(dest string, hdr *tar.Header, tr io.Reader) error {
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
logrus.Infof("linking %s to %s", filepath.Clean(filepath.Join("/", hdr.Linkname)), path)

if err := os.Link(filepath.Clean(filepath.Join("/", hdr.Linkname)), path); err != nil {
return err
}
Expand Down

0 comments on commit 3dc172a

Please sign in to comment.