Skip to content

Commit 563d46d

Browse files
ForestEckhardtryanmoran
authored andcommitted
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
1 parent 3b001de commit 563d46d

File tree

2 files changed

+90
-70
lines changed

2 files changed

+90
-70
lines changed

vacation/tar_archive.go

+45-35
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9-
"sort"
109
"strings"
1110
)
1211

@@ -34,7 +33,6 @@ func (ta TarArchive) Decompress(destination string) error {
3433
// Struct and slice to collect symlinks and create them after all files have
3534
// been created
3635
type header struct {
37-
name string
3836
linkname string
3937
path string
4038
}
@@ -119,49 +117,61 @@ func (ta TarArchive) Decompress(destination string) error {
119117
// Collect all of the headers for symlinks so that they can be verified
120118
// after all other files are written
121119
symlinkHeaders = append(symlinkHeaders, header{
122-
name: hdr.Name,
123120
linkname: hdr.Linkname,
124121
path: path,
125122
})
126123
}
127124
}
128125

129-
// Sort the symlinks so that symlinks of symlinks have their base link
130-
// created before they are created.
131-
//
132-
// For example:
133-
// b-sym -> a-sym/x
134-
// a-sym -> z
135-
// c-sym -> d-sym
136-
// d-sym -> z
137-
//
138-
// Will sort to:
139-
// a-sym -> z
140-
// b-sym -> a-sym/x
141-
// d-sym -> z
142-
// c-sym -> d-sym
143-
sort.Slice(symlinkHeaders, func(i, j int) bool {
144-
if filepath.Clean(symlinkHeaders[i].name) == linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname) {
145-
return true
146-
}
126+
// Create a map of all of the symlink names and where they are pointing to to
127+
// act as a quasi-graph
128+
symlinkMap := map[string]string{}
129+
for _, h := range symlinkHeaders {
130+
symlinkMap[filepath.Clean(h.path)] = h.linkname
131+
}
147132

148-
if filepath.Clean(symlinkHeaders[j].name) == linknameFullPath(symlinkHeaders[i].name, symlinkHeaders[i].linkname) {
149-
return false
150-
}
133+
// Loop over map until it is empty this is potentially O(infinity) if there
134+
// is a cyclical link but I like to live on the edge
135+
for len(symlinkMap) > 0 {
136+
// Name the outer loop as an escape hatch
137+
Builder:
138+
for path, linkname := range symlinkMap {
139+
// Check to see if the linkname lies on the path of another symlink in
140+
// the table or if it is another symlink in the table
141+
//
142+
// Example:
143+
// path = dir/file
144+
// a-symlink -> dir
145+
// b-symlink -> a-symlink
146+
// c-symlink -> a-symlink/file
147+
//
148+
// If there is a match either of the symlink or it is on the path then
149+
// skip the creation of this symlink for now
150+
sln := strings.Split(linkname, "/")
151+
for i := 0; i < len(sln); i++ {
152+
if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:i+1]...))]; ok {
153+
continue Builder
154+
}
155+
}
151156

152-
return filepath.Clean(symlinkHeaders[i].name) < linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname)
153-
})
157+
// If the linkname is not an existing link in the symlink table then we
158+
// can attempt the make the link
154159

155-
for _, h := range symlinkHeaders {
156-
// Check to see if the file that will be linked to is valid for symlinking
157-
_, err := filepath.EvalSymlinks(linknameFullPath(h.path, h.linkname))
158-
if err != nil {
159-
return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err)
160-
}
160+
// Check to see if the file that will be linked to is valid for symlinking
161+
_, err := filepath.EvalSymlinks(linknameFullPath(path, linkname))
162+
if err != nil {
163+
return fmt.Errorf("failed to evaluate symlink %s: %w", path, err)
164+
}
161165

162-
err = os.Symlink(h.linkname, h.path)
163-
if err != nil {
164-
return fmt.Errorf("failed to extract symlink: %s", err)
166+
// Create the symlink
167+
err = os.Symlink(linkname, path)
168+
if err != nil {
169+
return fmt.Errorf("failed to extract symlink: %s", err)
170+
}
171+
172+
// Remove the created symlink from the symlink table so that its
173+
// dependent symlinks can be created in the next iteration
174+
delete(symlinkMap, path)
165175
}
166176
}
167177

vacation/zip_archive.go

+45-35
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9-
"sort"
109
"strings"
1110
)
1211

@@ -27,7 +26,6 @@ func (z ZipArchive) Decompress(destination string) error {
2726
// Struct and slice to collect symlinks and create them after all files have
2827
// been created
2928
type header struct {
30-
name string
3129
linkname string
3230
path string
3331
}
@@ -97,7 +95,6 @@ func (z ZipArchive) Decompress(destination string) error {
9795
// Collect all of the headers for symlinks so that they can be verified
9896
// after all other files are written
9997
symlinkHeaders = append(symlinkHeaders, header{
100-
name: f.Name,
10198
linkname: string(linkname),
10299
path: path,
103100
})
@@ -133,42 +130,55 @@ func (z ZipArchive) Decompress(destination string) error {
133130
}
134131
}
135132

136-
// Sort the symlinks so that symlinks of symlinks have their base link
137-
// created before they are created.
138-
//
139-
// For example:
140-
// b-sym -> a-sym/x
141-
// a-sym -> z
142-
// c-sym -> d-sym
143-
// d-sym -> z
144-
//
145-
// Will sort to:
146-
// a-sym -> z
147-
// b-sym -> a-sym/x
148-
// d-sym -> z
149-
// c-sym -> d-sym
150-
sort.Slice(symlinkHeaders, func(i, j int) bool {
151-
if filepath.Clean(symlinkHeaders[i].name) == linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname) {
152-
return true
153-
}
133+
// Create a map of all of the symlink names and where they are pointing to to
134+
// act as a quasi-graph
135+
symlinkMap := map[string]string{}
136+
for _, h := range symlinkHeaders {
137+
symlinkMap[filepath.Clean(h.path)] = h.linkname
138+
}
154139

155-
if filepath.Clean(symlinkHeaders[j].name) == linknameFullPath(symlinkHeaders[i].name, symlinkHeaders[i].linkname) {
156-
return false
157-
}
140+
// Loop over map until it is empty this is potentially O(infinity) if there
141+
// is a cyclical link but I like to live on the edge
142+
for len(symlinkMap) > 0 {
143+
// Name the outer loop as an escape hatch
144+
Builder:
145+
for path, linkname := range symlinkMap {
146+
// Check to see if the linkname lies on the path of another symlink in
147+
// the table or if it is another symlink in the table
148+
//
149+
// Example:
150+
// path = dir/file
151+
// a-symlink -> dir
152+
// b-symlink -> a-symlink
153+
// c-symlink -> a-symlink/file
154+
//
155+
// If there is a match either of the symlink or it is on the path then
156+
// skip the creation of this symlink for now
157+
sln := strings.Split(linkname, "/")
158+
for i := 0; i < len(sln); i++ {
159+
if _, ok := symlinkMap[linknameFullPath(path, filepath.Join(sln[:i+1]...))]; ok {
160+
continue Builder
161+
}
162+
}
158163

159-
return filepath.Clean(symlinkHeaders[i].name) < linknameFullPath(symlinkHeaders[j].name, symlinkHeaders[j].linkname)
160-
})
164+
// If the linkname is not an existing link in the symlink table then we
165+
// can attempt the make the link
161166

162-
for _, h := range symlinkHeaders {
163-
// Check to see if the file that will be linked to is valid for symlinking
164-
_, err := filepath.EvalSymlinks(linknameFullPath(h.path, h.linkname))
165-
if err != nil {
166-
return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err)
167-
}
167+
// Check to see if the file that will be linked to is valid for symlinking
168+
_, err := filepath.EvalSymlinks(linknameFullPath(path, linkname))
169+
if err != nil {
170+
return fmt.Errorf("failed to evaluate symlink %s: %w", path, err)
171+
}
168172

169-
err = os.Symlink(h.linkname, h.path)
170-
if err != nil {
171-
return fmt.Errorf("failed to unzip symlink: %w", err)
173+
// Create the symlink
174+
err = os.Symlink(linkname, path)
175+
if err != nil {
176+
return fmt.Errorf("failed to unzip symlink: %s", err)
177+
}
178+
179+
// Remove the created symlink from the symlink table so that its
180+
// dependent symlinks can be created in the next iteration
181+
delete(symlinkMap, path)
172182
}
173183
}
174184

0 commit comments

Comments
 (0)