From b6d49d28d9c52c90fccf1f77ee3b5b53351e4a40 Mon Sep 17 00:00:00 2001 From: Forest Eckhardt Date: Thu, 9 Sep 2021 14:33:57 +0000 Subject: [PATCH 1/3] Removes sort in favor of map - Build a quasi-graph using a map that can be traversed and create symlinks that are not dependent on other symlinks --- vacation/tar_archive.go | 80 +++++++++++++++++++++++------------------ vacation/zip_archive.go | 80 +++++++++++++++++++++++------------------ 2 files changed, 90 insertions(+), 70 deletions(-) diff --git a/vacation/tar_archive.go b/vacation/tar_archive.go index e0a30ca2..3fb64192 100644 --- a/vacation/tar_archive.go +++ b/vacation/tar_archive.go @@ -6,7 +6,6 @@ import ( "io" "os" "path/filepath" - "sort" "strings" ) @@ -34,7 +33,6 @@ func (ta TarArchive) Decompress(destination string) error { // Struct and slice to collect symlinks and create them after all files have // been created type header struct { - name string linkname string path string } @@ -119,49 +117,61 @@ func (ta TarArchive) Decompress(destination string) error { // Collect all of the headers for symlinks so that they can be verified // after all other files are written symlinkHeaders = append(symlinkHeaders, header{ - name: hdr.Name, linkname: hdr.Linkname, path: path, }) } } - // Sort the symlinks so that symlinks of symlinks have their base link - // created before they are created. - // - // For example: - // b-sym -> a-sym/x - // a-sym -> z - // c-sym -> d-sym - // d-sym -> z - // - // Will sort to: - // a-sym -> z - // b-sym -> a-sym/x - // d-sym -> z - // c-sym -> d-sym - sort.Slice(symlinkHeaders, func(i, j int) bool { - if filepath.Clean(symlinkHeaders[i].name) == linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname) { - return true - } + // Create a map of all of the symlink names and where they are pointing to to + // act as a quasi-graph + symlinkMap := map[string]string{} + for _, h := range symlinkHeaders { + symlinkMap[filepath.Clean(h.path)] = h.linkname + } - if filepath.Clean(symlinkHeaders[j].name) == linknameFullPath(symlinkHeaders[i].name, symlinkHeaders[i].linkname) { - return false - } + // Loop over map until it is empty this is potentially O(infinity) if there + // is a cyclical link but I like to live on the edge + for len(symlinkMap) > 0 { + // Name the outer loop as an escape hatch + Builder: + for path, linkname := range symlinkMap { + // Check to see if the linkname lies on the path of another symlink in + // the table or if it is another symlink in the table + // + // Example: + // path = dir/file + // a-symlink -> dir + // b-symlink -> a-symlink + // c-symlink -> a-symlink/file + // + // If there is a match either of the symlink or it is on the path then + // skip the creation of this symlink for now + sln := strings.Split(linkname, "/") + for i := 0; i < len(sln); i++ { + if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:i+1]...))]; ok { + continue Builder + } + } - return filepath.Clean(symlinkHeaders[i].name) < linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname) - }) + // If the linkname is not an existing link in the symlink table then we + // can attempt the make the link - for _, h := range symlinkHeaders { - // Check to see if the file that will be linked to is valid for symlinking - _, err := filepath.EvalSymlinks(linknameFullPath(h.path, h.linkname)) - if err != nil { - return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err) - } + // Check to see if the file that will be linked to is valid for symlinking + _, err := filepath.EvalSymlinks(linknameFullPath(path, linkname)) + if err != nil { + return fmt.Errorf("failed to evaluate symlink %s: %w", path, err) + } - err = os.Symlink(h.linkname, h.path) - if err != nil { - return fmt.Errorf("failed to extract symlink: %s", err) + // Create the symlink + err = os.Symlink(linkname, path) + if err != nil { + return fmt.Errorf("failed to extract symlink: %s", err) + } + + // Remove the created symlink from the symlink table so that its + // dependent symlinks can be created in the next iteration + delete(symlinkMap, path) } } diff --git a/vacation/zip_archive.go b/vacation/zip_archive.go index 6c11151a..c2ca4ce7 100644 --- a/vacation/zip_archive.go +++ b/vacation/zip_archive.go @@ -6,7 +6,6 @@ import ( "io" "os" "path/filepath" - "sort" "strings" ) @@ -27,7 +26,6 @@ func (z ZipArchive) Decompress(destination string) error { // Struct and slice to collect symlinks and create them after all files have // been created type header struct { - name string linkname string path string } @@ -97,7 +95,6 @@ func (z ZipArchive) Decompress(destination string) error { // Collect all of the headers for symlinks so that they can be verified // after all other files are written symlinkHeaders = append(symlinkHeaders, header{ - name: f.Name, linkname: string(linkname), path: path, }) @@ -133,42 +130,55 @@ func (z ZipArchive) Decompress(destination string) error { } } - // Sort the symlinks so that symlinks of symlinks have their base link - // created before they are created. - // - // For example: - // b-sym -> a-sym/x - // a-sym -> z - // c-sym -> d-sym - // d-sym -> z - // - // Will sort to: - // a-sym -> z - // b-sym -> a-sym/x - // d-sym -> z - // c-sym -> d-sym - sort.Slice(symlinkHeaders, func(i, j int) bool { - if filepath.Clean(symlinkHeaders[i].name) == linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname) { - return true - } + // Create a map of all of the symlink names and where they are pointing to to + // act as a quasi-graph + symlinkMap := map[string]string{} + for _, h := range symlinkHeaders { + symlinkMap[filepath.Clean(h.path)] = h.linkname + } - if filepath.Clean(symlinkHeaders[j].name) == linknameFullPath(symlinkHeaders[i].name, symlinkHeaders[i].linkname) { - return false - } + // Loop over map until it is empty this is potentially O(infinity) if there + // is a cyclical link but I like to live on the edge + for len(symlinkMap) > 0 { + // Name the outer loop as an escape hatch + Builder: + for path, linkname := range symlinkMap { + // Check to see if the linkname lies on the path of another symlink in + // the table or if it is another symlink in the table + // + // Example: + // path = dir/file + // a-symlink -> dir + // b-symlink -> a-symlink + // c-symlink -> a-symlink/file + // + // If there is a match either of the symlink or it is on the path then + // skip the creation of this symlink for now + sln := strings.Split(linkname, "/") + for i := 0; i < len(sln); i++ { + if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:i+1]...))]; ok { + continue Builder + } + } - return filepath.Clean(symlinkHeaders[i].name) < linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname) - }) + // If the linkname is not an existing link in the symlink table then we + // can attempt the make the link - for _, h := range symlinkHeaders { - // Check to see if the file that will be linked to is valid for symlinking - _, err := filepath.EvalSymlinks(linknameFullPath(h.path, h.linkname)) - if err != nil { - return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err) - } + // Check to see if the file that will be linked to is valid for symlinking + _, err := filepath.EvalSymlinks(linknameFullPath(path, linkname)) + if err != nil { + return fmt.Errorf("failed to evaluate symlink %s: %w", path, err) + } - err = os.Symlink(h.linkname, h.path) - if err != nil { - return fmt.Errorf("failed to unzip symlink: %w", err) + // Create the symlink + err = os.Symlink(linkname, path) + if err != nil { + return fmt.Errorf("failed to unzip symlink: %s", err) + } + + // Remove the created symlink from the symlink table so that its + // dependent symlinks can be created in the next iteration + delete(symlinkMap, path) } } From accb0739c571a9a80f68b86978f0cbf1ce9f6719 Mon Sep 17 00:00:00 2001 From: Forest Eckhardt Date: Thu, 9 Sep 2021 18:55:05 +0000 Subject: [PATCH 2/3] Adds logic to prevent symlink cycles from causing deadlock --- vacation/tar_archive.go | 31 ++++++++++++++------- vacation/tar_archive_test.go | 28 +++++++++++++++++++ vacation/zip_archive.go | 31 ++++++++++++++------- vacation/zip_archive_test.go | 52 ++++++++++++++++++++++++++++++------ 4 files changed, 116 insertions(+), 26 deletions(-) diff --git a/vacation/tar_archive.go b/vacation/tar_archive.go index 3fb64192..2522f9a0 100644 --- a/vacation/tar_archive.go +++ b/vacation/tar_archive.go @@ -130,11 +130,11 @@ func (ta TarArchive) Decompress(destination string) error { symlinkMap[filepath.Clean(h.path)] = h.linkname } - // Loop over map until it is empty this is potentially O(infinity) if there - // is a cyclical link but I like to live on the edge - for len(symlinkMap) > 0 { - // Name the outer loop as an escape hatch - Builder: + // Iterate over the symlink map for every link that is found this ensures + // that all symlinks that can be created will be created and any that are + // left over are cyclically dependent + maxIterations := len(symlinkMap) + for i := 0; i < maxIterations; i++ { for path, linkname := range symlinkMap { // Check to see if the linkname lies on the path of another symlink in // the table or if it is another symlink in the table @@ -147,11 +147,18 @@ func (ta TarArchive) Decompress(destination string) error { // // If there is a match either of the symlink or it is on the path then // skip the creation of this symlink for now - sln := strings.Split(linkname, "/") - for i := 0; i < len(sln); i++ { - if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:i+1]...))]; ok { - continue Builder + shouldSkipLink := func() bool { + sln := strings.Split(linkname, "/") + for j := 0; j < len(sln); j++ { + if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:j+1]...))]; ok { + return true + } } + return false + } + + if shouldSkipLink() { + continue } // If the linkname is not an existing link in the symlink table then we @@ -175,6 +182,12 @@ func (ta TarArchive) Decompress(destination string) error { } } + // Check to see if there are any symlinks left in the map which would + // indicate a cyclical dependency + if len(symlinkMap) > 0 { + return fmt.Errorf("failed: max iterations reached: this symlink graph contains a cycle") + } + return nil } diff --git a/vacation/tar_archive_test.go b/vacation/tar_archive_test.go index f30149bd..e8c3ab1f 100644 --- a/vacation/tar_archive_test.go +++ b/vacation/tar_archive_test.go @@ -289,5 +289,33 @@ func testTarArchive(t *testing.T, context spec.G, it spec.S) { }) }) }) + + context("when there is a symlink cycle", func() { + var cyclicalSymlinkTar vacation.TarArchive + + it.Before(func() { + var err error + + buffer := bytes.NewBuffer(nil) + tw := tar.NewWriter(buffer) + + Expect(tw.WriteHeader(&tar.Header{Name: "a-symlink", Mode: 0755, Size: int64(0), Typeflag: tar.TypeSymlink, Linkname: "b-symlink"})).To(Succeed()) + _, err = tw.Write([]byte{}) + Expect(err).NotTo(HaveOccurred()) + + Expect(tw.WriteHeader(&tar.Header{Name: "b-symlink", Mode: 0755, Size: int64(0), Typeflag: tar.TypeSymlink, Linkname: "a-symlink"})).To(Succeed()) + _, err = tw.Write([]byte{}) + Expect(err).NotTo(HaveOccurred()) + + Expect(tw.Close()).To(Succeed()) + + cyclicalSymlinkTar = vacation.NewTarArchive(bytes.NewReader(buffer.Bytes())) + }) + + it("returns an error", func() { + err := cyclicalSymlinkTar.Decompress(tempDir) + Expect(err).To(MatchError(ContainSubstring("failed: max iterations reached: this symlink graph contains a cycle"))) + }) + }) }) } diff --git a/vacation/zip_archive.go b/vacation/zip_archive.go index c2ca4ce7..9ead6ac4 100644 --- a/vacation/zip_archive.go +++ b/vacation/zip_archive.go @@ -137,11 +137,11 @@ func (z ZipArchive) Decompress(destination string) error { symlinkMap[filepath.Clean(h.path)] = h.linkname } - // Loop over map until it is empty this is potentially O(infinity) if there - // is a cyclical link but I like to live on the edge - for len(symlinkMap) > 0 { - // Name the outer loop as an escape hatch - Builder: + // Iterate over the symlink map for every link that is found this ensures + // that all symlinks that can be created will be created and any that are + // left over are cyclically dependent + maxIterations := len(symlinkMap) + for i := 0; i < maxIterations; i++ { for path, linkname := range symlinkMap { // Check to see if the linkname lies on the path of another symlink in // the table or if it is another symlink in the table @@ -154,11 +154,18 @@ func (z ZipArchive) Decompress(destination string) error { // // If there is a match either of the symlink or it is on the path then // skip the creation of this symlink for now - sln := strings.Split(linkname, "/") - for i := 0; i < len(sln); i++ { - if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:i+1]...))]; ok { - continue Builder + shouldSkipLink := func() bool { + sln := strings.Split(linkname, "/") + for j := 0; j < len(sln); j++ { + if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:j+1]...))]; ok { + return true + } } + return false + } + + if shouldSkipLink() { + continue } // If the linkname is not an existing link in the symlink table then we @@ -182,6 +189,12 @@ func (z ZipArchive) Decompress(destination string) error { } } + // Check to see if there are any symlinks left in the map which would + // indicate a cyclical dependency + if len(symlinkMap) > 0 { + return fmt.Errorf("failed: max iterations reached: this symlink graph contains a cycle") + } + return nil } diff --git a/vacation/zip_archive_test.go b/vacation/zip_archive_test.go index c6244297..9a3ce570 100644 --- a/vacation/zip_archive_test.go +++ b/vacation/zip_archive_test.go @@ -244,6 +244,33 @@ func testZipArchive(t *testing.T, context spec.G, it spec.S) { }) }) + context("when it fails to unzip a file", func() { + var buffer *bytes.Buffer + it.Before(func() { + var err error + buffer = bytes.NewBuffer(nil) + zw := zip.NewWriter(buffer) + + _, err = zw.Create("some-file") + Expect(err).NotTo(HaveOccurred()) + + Expect(zw.Close()).To(Succeed()) + + Expect(os.Chmod(tempDir, 0000)).To(Succeed()) + }) + + it.After(func() { + Expect(os.Chmod(tempDir, os.ModePerm)).To(Succeed()) + }) + + it("returns an error", func() { + readyArchive := vacation.NewZipArchive(buffer) + + err := readyArchive.Decompress(tempDir) + Expect(err).To(MatchError(ContainSubstring("failed to unzip file"))) + }) + }) + context("when it tries to symlink to a file that does not exist", func() { var buffer *bytes.Buffer it.Before(func() { @@ -305,30 +332,39 @@ func testZipArchive(t *testing.T, context spec.G, it spec.S) { }) }) - context("when it fails to unzip a file", func() { + context("when there is a symlink cycle", func() { var buffer *bytes.Buffer it.Before(func() { var err error buffer = bytes.NewBuffer(nil) zw := zip.NewWriter(buffer) - _, err = zw.Create("some-file") + header := &zip.FileHeader{Name: "a-symlink"} + header.SetMode(0755 | os.ModeSymlink) + + aSymlink, err := zw.CreateHeader(header) Expect(err).NotTo(HaveOccurred()) - Expect(zw.Close()).To(Succeed()) + _, err = aSymlink.Write([]byte(filepath.Join("b-symlink"))) + Expect(err).NotTo(HaveOccurred()) - Expect(os.Chmod(tempDir, 0000)).To(Succeed()) - }) + header = &zip.FileHeader{Name: "b-symlink"} + header.SetMode(0755 | os.ModeSymlink) - it.After(func() { - Expect(os.Chmod(tempDir, os.ModePerm)).To(Succeed()) + bSymlink, err := zw.CreateHeader(header) + Expect(err).NotTo(HaveOccurred()) + + _, err = bSymlink.Write([]byte(filepath.Join("a-symlink"))) + Expect(err).NotTo(HaveOccurred()) + + Expect(zw.Close()).To(Succeed()) }) it("returns an error", func() { readyArchive := vacation.NewZipArchive(buffer) err := readyArchive.Decompress(tempDir) - Expect(err).To(MatchError(ContainSubstring("failed to unzip file"))) + Expect(err).To(MatchError("failed: max iterations reached: this symlink graph contains a cycle")) }) }) }) From e29898331e629ee9fd33374e2ae4a6f48623f9cb Mon Sep 17 00:00:00 2001 From: Ryan Moran Date: Thu, 9 Sep 2021 15:04:25 -0700 Subject: [PATCH 3/3] Extract out symlink sorting logic --- vacation/symlink_sorting.go | 72 +++++++++++++++++++++++++++++++ vacation/tar_archive.go | 86 +++++++------------------------------ vacation/zip_archive.go | 86 +++++++------------------------------ 3 files changed, 104 insertions(+), 140 deletions(-) create mode 100644 vacation/symlink_sorting.go diff --git a/vacation/symlink_sorting.go b/vacation/symlink_sorting.go new file mode 100644 index 00000000..8c1b61e8 --- /dev/null +++ b/vacation/symlink_sorting.go @@ -0,0 +1,72 @@ +package vacation + +import ( + "fmt" + "path/filepath" + "strings" +) + +type symlink struct { + name string + path string +} + +func sortSymlinks(symlinks []symlink) ([]symlink, error) { + // Create a map of all of the symlink names and where they are pointing to to + // act as a quasi-graph + index := map[string]string{} + for _, s := range symlinks { + index[filepath.Clean(s.path)] = s.name + } + + // Check to see if the link name lies on the path of another symlink in + // the table or if it is another symlink in the table + // + // Example: + // path = dir/file + // a-symlink -> dir + // b-symlink -> a-symlink + // c-symlink -> a-symlink/file + shouldSkipLink := func(linkname, linkpath string) bool { + sln := strings.Split(linkname, "/") + for j := 0; j < len(sln); j++ { + if _, ok := index[linknameFullPath(linkpath, filepath.Join(sln[:j+1]...))]; ok { + return true + } + } + return false + } + + // Iterate over the symlink map for every link that is found this ensures + // that all symlinks that can be created will be created and any that are + // left over are cyclically dependent + var links []symlink + maxIterations := len(index) + for i := 0; i < maxIterations; i++ { + for path, name := range index { + // If there is a match either of the symlink or it is on the path then + // skip the creation of this symlink for now + if shouldSkipLink(name, path) { + continue + } + + links = append(links, symlink{ + name: name, + path: path, + }) + + // Remove the created symlink from the symlink table so that its + // dependent symlinks can be created in the next iteration + delete(index, path) + break + } + } + + // Check to see if there are any symlinks left in the map which would + // indicate a cyclical dependency + if len(index) > 0 { + return nil, fmt.Errorf("failed: max iterations reached: this symlink graph contains a cycle") + } + + return links, nil +} diff --git a/vacation/tar_archive.go b/vacation/tar_archive.go index 2522f9a0..4f3d1956 100644 --- a/vacation/tar_archive.go +++ b/vacation/tar_archive.go @@ -30,14 +30,7 @@ func (ta TarArchive) Decompress(destination string) error { // metadata. directories := map[string]interface{}{} - // Struct and slice to collect symlinks and create them after all files have - // been created - type header struct { - linkname string - path string - } - - var symlinkHeaders []header + var symlinks []symlink tarReader := tar.NewReader(ta.reader) for { @@ -116,76 +109,29 @@ func (ta TarArchive) Decompress(destination string) error { case tar.TypeSymlink: // Collect all of the headers for symlinks so that they can be verified // after all other files are written - symlinkHeaders = append(symlinkHeaders, header{ - linkname: hdr.Linkname, - path: path, + symlinks = append(symlinks, symlink{ + name: hdr.Linkname, + path: path, }) } } - // Create a map of all of the symlink names and where they are pointing to to - // act as a quasi-graph - symlinkMap := map[string]string{} - for _, h := range symlinkHeaders { - symlinkMap[filepath.Clean(h.path)] = h.linkname + symlinks, err := sortSymlinks(symlinks) + if err != nil { + return err } - // Iterate over the symlink map for every link that is found this ensures - // that all symlinks that can be created will be created and any that are - // left over are cyclically dependent - maxIterations := len(symlinkMap) - for i := 0; i < maxIterations; i++ { - for path, linkname := range symlinkMap { - // Check to see if the linkname lies on the path of another symlink in - // the table or if it is another symlink in the table - // - // Example: - // path = dir/file - // a-symlink -> dir - // b-symlink -> a-symlink - // c-symlink -> a-symlink/file - // - // If there is a match either of the symlink or it is on the path then - // skip the creation of this symlink for now - shouldSkipLink := func() bool { - sln := strings.Split(linkname, "/") - for j := 0; j < len(sln); j++ { - if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:j+1]...))]; ok { - return true - } - } - return false - } - - if shouldSkipLink() { - continue - } - - // If the linkname is not an existing link in the symlink table then we - // can attempt the make the link - - // Check to see if the file that will be linked to is valid for symlinking - _, err := filepath.EvalSymlinks(linknameFullPath(path, linkname)) - if err != nil { - return fmt.Errorf("failed to evaluate symlink %s: %w", path, err) - } - - // Create the symlink - err = os.Symlink(linkname, path) - if err != nil { - return fmt.Errorf("failed to extract symlink: %s", err) - } - - // Remove the created symlink from the symlink table so that its - // dependent symlinks can be created in the next iteration - delete(symlinkMap, path) + for _, link := range symlinks { + // Check to see if the file that will be linked to is valid for symlinking + _, err := filepath.EvalSymlinks(linknameFullPath(link.path, link.name)) + if err != nil { + return fmt.Errorf("failed to evaluate symlink %s: %w", link.path, err) } - } - // Check to see if there are any symlinks left in the map which would - // indicate a cyclical dependency - if len(symlinkMap) > 0 { - return fmt.Errorf("failed: max iterations reached: this symlink graph contains a cycle") + err = os.Symlink(link.name, link.path) + if err != nil { + return fmt.Errorf("failed to extract symlink: %s", err) + } } return nil diff --git a/vacation/zip_archive.go b/vacation/zip_archive.go index 9ead6ac4..906523ef 100644 --- a/vacation/zip_archive.go +++ b/vacation/zip_archive.go @@ -23,14 +23,6 @@ func NewZipArchive(inputReader io.Reader) ZipArchive { // Decompress reads from ZipArchive and writes files into the destination // specified. func (z ZipArchive) Decompress(destination string) error { - // Struct and slice to collect symlinks and create them after all files have - // been created - type header struct { - linkname string - path string - } - - var symlinkHeaders []header // Use an os.File to buffer the zip contents. This is needed because // zip.NewReader requires an io.ReaderAt so that it can jump around within @@ -51,6 +43,7 @@ func (z ZipArchive) Decompress(destination string) error { return fmt.Errorf("failed to create zip reader: %w", err) } + var symlinks []symlink for _, f := range zr.File { // Clean the name in the header to prevent './filename' being stripped to // 'filename' also to skip if the destination it the destination directory @@ -94,9 +87,9 @@ func (z ZipArchive) Decompress(destination string) error { // Collect all of the headers for symlinks so that they can be verified // after all other files are written - symlinkHeaders = append(symlinkHeaders, header{ - linkname: string(linkname), - path: path, + symlinks = append(symlinks, symlink{ + name: string(linkname), + path: path, }) default: @@ -130,69 +123,22 @@ func (z ZipArchive) Decompress(destination string) error { } } - // Create a map of all of the symlink names and where they are pointing to to - // act as a quasi-graph - symlinkMap := map[string]string{} - for _, h := range symlinkHeaders { - symlinkMap[filepath.Clean(h.path)] = h.linkname + symlinks, err = sortSymlinks(symlinks) + if err != nil { + return err } - // Iterate over the symlink map for every link that is found this ensures - // that all symlinks that can be created will be created and any that are - // left over are cyclically dependent - maxIterations := len(symlinkMap) - for i := 0; i < maxIterations; i++ { - for path, linkname := range symlinkMap { - // Check to see if the linkname lies on the path of another symlink in - // the table or if it is another symlink in the table - // - // Example: - // path = dir/file - // a-symlink -> dir - // b-symlink -> a-symlink - // c-symlink -> a-symlink/file - // - // If there is a match either of the symlink or it is on the path then - // skip the creation of this symlink for now - shouldSkipLink := func() bool { - sln := strings.Split(linkname, "/") - for j := 0; j < len(sln); j++ { - if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:j+1]...))]; ok { - return true - } - } - return false - } - - if shouldSkipLink() { - continue - } - - // If the linkname is not an existing link in the symlink table then we - // can attempt the make the link - - // Check to see if the file that will be linked to is valid for symlinking - _, err := filepath.EvalSymlinks(linknameFullPath(path, linkname)) - if err != nil { - return fmt.Errorf("failed to evaluate symlink %s: %w", path, err) - } - - // Create the symlink - err = os.Symlink(linkname, path) - if err != nil { - return fmt.Errorf("failed to unzip symlink: %s", err) - } - - // Remove the created symlink from the symlink table so that its - // dependent symlinks can be created in the next iteration - delete(symlinkMap, path) + for _, link := range symlinks { + // Check to see if the file that will be linked to is valid for symlinking + _, err := filepath.EvalSymlinks(linknameFullPath(link.path, link.name)) + if err != nil { + return fmt.Errorf("failed to evaluate symlink %s: %w", link.path, err) } - } - // Check to see if there are any symlinks left in the map which would - // indicate a cyclical dependency - if len(symlinkMap) > 0 { - return fmt.Errorf("failed: max iterations reached: this symlink graph contains a cycle") + err = os.Symlink(link.name, link.path) + if err != nil { + return fmt.Errorf("failed to unzip symlink: %s", err) + } } return nil