diff --git a/ext4/internal/compactext4/compact.go b/ext4/internal/compactext4/compact.go index f40ac8f989..e35536b9fa 100644 --- a/ext4/internal/compactext4/compact.go +++ b/ext4/internal/compactext4/compact.go @@ -603,6 +603,8 @@ func (w *Writer) Create(name string, f *File) error { } // Link adds a hard link to the file system. +// We support creating hardlinks to symlinks themselves instead of what +// the symlinks link to, as this is what containerd does upstream. func (w *Writer) Link(oldname, newname string) error { if err := w.finishInode(); err != nil { return err @@ -620,8 +622,8 @@ func (w *Writer) Link(oldname, newname string) error { return err } switch oldfile.Mode & format.TypeMask { - case format.S_IFDIR, format.S_IFLNK: - return fmt.Errorf("%s: link target cannot be a directory or symlink: %s", newname, oldname) + case format.S_IFDIR: + return fmt.Errorf("%s: link target cannot be a directory: %s", newname, oldname) } if existing != oldfile && oldfile.LinkCount >= format.MaxLinks { diff --git a/ext4/tar2ext4/tar2ext4_test.go b/ext4/tar2ext4/tar2ext4_test.go index 9156326398..b5117b0419 100644 --- a/ext4/tar2ext4/tar2ext4_test.go +++ b/ext4/tar2ext4/tar2ext4_test.go @@ -88,3 +88,76 @@ func Test_UnorderedTarExpansion(t *testing.T) { t.Fatalf("failed to convert tar to layer vhd: %s", err) } } + +func Test_TarHardlinkToSymlink(t *testing.T) { + tmpTarFilePath := filepath.Join(os.TempDir(), "test-layer.tar") + layerTar, err := os.Create(tmpTarFilePath) + if err != nil { + t.Fatalf("failed to create output file: %s", err) + } + defer os.Remove(tmpTarFilePath) + + tw := tar.NewWriter(layerTar) + + var files = []struct { + path string + typeFlag byte + linkName string + body string + }{ + { + path: "/tmp/zzz.txt", + body: "inside /tmp/zzz.txt", + }, + { + path: "/tmp/xxx.txt", + linkName: "/tmp/zzz.txt", + typeFlag: tar.TypeSymlink, + }, + { + path: "/tmp/yyy.txt", + linkName: "/tmp/xxx.txt", + typeFlag: tar.TypeLink, + }, + } + for _, file := range files { + hdr := &tar.Header{ + Name: file.path, + Typeflag: file.typeFlag, + Linkname: file.linkName, + Mode: 0777, + Size: int64(len(file.body)), + ModTime: time.Now(), + AccessTime: time.Now(), + ChangeTime: time.Now(), + } + if err := tw.WriteHeader(hdr); err != nil { + t.Fatal(err) + } + if file.body != "" { + if _, err := tw.Write([]byte(file.body)); err != nil { + t.Fatal(err) + } + } + } + if err := tw.Close(); err != nil { + t.Fatal(err) + } + + // Now try to import this tar and verify that there is no failure. + if _, err := layerTar.Seek(0, 0); err != nil { + t.Fatalf("failed to seek file: %s", err) + } + + opts := []Option{AppendVhdFooter, ConvertWhiteout} + tmpVhdPath := filepath.Join(os.TempDir(), "test-vhd.vhdx") + layerVhd, err := os.Create(tmpVhdPath) + if err != nil { + t.Fatalf("failed to create output VHD: %s", err) + } + defer os.Remove(tmpVhdPath) + + if err := Convert(layerTar, layerVhd, opts...); err != nil { + t.Fatalf("failed to convert tar to layer vhd: %s", err) + } +} diff --git a/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go b/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go index f40ac8f989..e35536b9fa 100644 --- a/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go +++ b/test/vendor/github.com/Microsoft/hcsshim/ext4/internal/compactext4/compact.go @@ -603,6 +603,8 @@ func (w *Writer) Create(name string, f *File) error { } // Link adds a hard link to the file system. +// We support creating hardlinks to symlinks themselves instead of what +// the symlinks link to, as this is what containerd does upstream. func (w *Writer) Link(oldname, newname string) error { if err := w.finishInode(); err != nil { return err @@ -620,8 +622,8 @@ func (w *Writer) Link(oldname, newname string) error { return err } switch oldfile.Mode & format.TypeMask { - case format.S_IFDIR, format.S_IFLNK: - return fmt.Errorf("%s: link target cannot be a directory or symlink: %s", newname, oldname) + case format.S_IFDIR: + return fmt.Errorf("%s: link target cannot be a directory: %s", newname, oldname) } if existing != oldfile && oldfile.LinkCount >= format.MaxLinks {