@@ -2,13 +2,13 @@ package parser
2
2
3
3
import (
4
4
"archive/tar"
5
- "bytes"
6
5
"compress/gzip"
7
6
"errors"
8
7
"fmt"
9
8
"io"
10
9
"io/fs"
11
10
"os"
11
+ "path"
12
12
"path/filepath"
13
13
14
14
"github.com/liamg/memoryfs"
@@ -18,18 +18,18 @@ import (
18
18
19
19
var errSkipFS = errors .New ("skip parse FS" )
20
20
21
- func (p * Parser ) addTarToFS (path string ) (fs.FS , error ) {
21
+ func (p * Parser ) addTarToFS (archivePath string ) (fs.FS , error ) {
22
22
tarFS := memoryfs .CloneFS (p .workingFS )
23
23
24
- file , err := tarFS .Open (path )
24
+ file , err := tarFS .Open (archivePath )
25
25
if err != nil {
26
26
return nil , fmt .Errorf ("failed to open tar: %w" , err )
27
27
}
28
28
defer file .Close ()
29
29
30
30
var tr * tar.Reader
31
31
32
- if detection .IsZip (path ) {
32
+ if detection .IsZip (archivePath ) {
33
33
zipped , err := gzip .NewReader (file )
34
34
if err != nil {
35
35
return nil , fmt .Errorf ("failed to create gzip reader: %w" , err )
@@ -41,6 +41,7 @@ func (p *Parser) addTarToFS(path string) (fs.FS, error) {
41
41
}
42
42
43
43
checkExistedChart := true
44
+ symlinks := make (map [string ]string )
44
45
45
46
for {
46
47
header , err := tr .Next ()
@@ -51,61 +52,124 @@ func (p *Parser) addTarToFS(path string) (fs.FS, error) {
51
52
return nil , fmt .Errorf ("failed to get next entry: %w" , err )
52
53
}
53
54
55
+ name := filepath .ToSlash (header .Name )
56
+
54
57
if checkExistedChart {
55
58
// Do not add archive files to FS if the chart already exists
56
59
// This can happen when the source chart is located next to an archived chart (with the `helm package` command)
57
60
// The first level folder in the archive is equal to the Chart name
58
- if _ , err := tarFS .Stat (filepath .Dir (path ) + "/" + filepath .Dir (header . Name )); err == nil {
61
+ if _ , err := tarFS .Stat (path .Dir (archivePath ) + "/" + path .Dir (name )); err == nil {
59
62
return nil , errSkipFS
60
63
}
61
64
checkExistedChart = false
62
65
}
63
66
64
67
// get the individual path and extract to the current directory
65
- entryPath := header .Name
68
+ targetPath := path .Join (path .Dir (archivePath ), path .Clean (name ))
69
+
70
+ link := filepath .ToSlash (header .Linkname )
66
71
67
72
switch header .Typeflag {
68
73
case tar .TypeDir :
69
- if err := tarFS .MkdirAll (entryPath , os .FileMode (header .Mode )); err != nil && ! errors .Is (err , fs .ErrExist ) {
74
+ if err := tarFS .MkdirAll (targetPath , os .FileMode (header .Mode )); err != nil && ! errors .Is (err , fs .ErrExist ) {
70
75
return nil , err
71
76
}
72
77
case tar .TypeReg :
73
- writePath := filepath .Dir (path ) + "/" + entryPath
74
- p .debug .Log ("Unpacking tar entry %s" , writePath )
75
-
76
- _ = tarFS .MkdirAll (filepath .Dir (writePath ), fs .ModePerm )
77
-
78
- buf , err := copyChunked (tr , 1024 )
79
- if err != nil {
78
+ p .debug .Log ("Unpacking tar entry %s" , targetPath )
79
+ if err := copyFile (tarFS , tr , targetPath ); err != nil {
80
80
return nil , err
81
81
}
82
-
83
- p . debug . Log ( "writing file contents to %s" , writePath )
84
- if err := tarFS . WriteFile ( writePath , buf . Bytes (), fs . ModePerm ); err != nil {
85
- return nil , fmt . Errorf ( "write file error: %w" , err )
82
+ case tar . TypeSymlink :
83
+ if path . IsAbs ( link ) {
84
+ p . debug . Log ( "Symlink %s is absolute, skipping" , link )
85
+ continue
86
86
}
87
+
88
+ symlinks [targetPath ] = path .Join (path .Dir (targetPath ), link ) // nolint:gosec // virtual file system is used
87
89
default :
88
90
return nil , fmt .Errorf ("header type %q is not supported" , header .Typeflag )
89
91
}
90
92
}
91
93
92
- if err := tarFS .Remove (path ); err != nil {
93
- return nil , fmt .Errorf ("failed to remove tar from FS: %w" , err )
94
+ for target , link := range symlinks {
95
+ if err := copySymlink (tarFS , link , target ); err != nil {
96
+ return nil , fmt .Errorf ("copy symlink error: %w" , err )
97
+ }
98
+ }
99
+
100
+ if err := tarFS .Remove (archivePath ); err != nil {
101
+ return nil , fmt .Errorf ("remove tar from FS error: %w" , err )
94
102
}
95
103
96
104
return tarFS , nil
97
105
}
98
106
99
- func copyChunked (src io.Reader , chunkSize int64 ) (* bytes.Buffer , error ) {
100
- buf := new (bytes.Buffer )
101
- for {
102
- if _ , err := io .CopyN (buf , src , chunkSize ); err != nil {
103
- if errors .Is (err , io .EOF ) {
104
- break
105
- }
106
- return nil , fmt .Errorf ("failed to copy: %w" , err )
107
+ func copySymlink (fsys * memoryfs.FS , src , dst string ) error {
108
+ fi , err := fsys .Stat (src )
109
+ if err != nil {
110
+ return nil
111
+ }
112
+ if fi .IsDir () {
113
+ if err := copyDir (fsys , src , dst ); err != nil {
114
+ return fmt .Errorf ("copy dir error: %w" , err )
115
+ }
116
+ return nil
117
+ }
118
+
119
+ if err := copyFileLazy (fsys , src , dst ); err != nil {
120
+ return fmt .Errorf ("copy file error: %w" , err )
121
+ }
122
+
123
+ return nil
124
+ }
125
+
126
+ func copyFile (fsys * memoryfs.FS , src io.Reader , dst string ) error {
127
+ if err := fsys .MkdirAll (path .Dir (dst ), fs .ModePerm ); err != nil && ! errors .Is (err , fs .ErrExist ) {
128
+ return fmt .Errorf ("mkdir error: %w" , err )
129
+ }
130
+
131
+ b , err := io .ReadAll (src )
132
+ if err != nil {
133
+ return fmt .Errorf ("read error: %w" , err )
134
+ }
135
+
136
+ if err := fsys .WriteFile (dst , b , fs .ModePerm ); err != nil {
137
+ return fmt .Errorf ("write file error: %w" , err )
138
+ }
139
+
140
+ return nil
141
+ }
142
+
143
+ func copyDir (fsys * memoryfs.FS , src , dst string ) error {
144
+ walkFn := func (filePath string , entry fs.DirEntry , err error ) error {
145
+ if err != nil {
146
+ return err
107
147
}
148
+
149
+ if entry .IsDir () {
150
+ return nil
151
+ }
152
+
153
+ dst := path .Join (dst , filePath [len (src ):])
154
+
155
+ if err := copyFileLazy (fsys , filePath , dst ); err != nil {
156
+ return fmt .Errorf ("copy file error: %w" , err )
157
+ }
158
+ return nil
108
159
}
109
160
110
- return buf , nil
161
+ return fs .WalkDir (fsys , src , walkFn )
162
+ }
163
+
164
+ func copyFileLazy (fsys * memoryfs.FS , src , dst string ) error {
165
+ if err := fsys .MkdirAll (path .Dir (dst ), fs .ModePerm ); err != nil && ! errors .Is (err , fs .ErrExist ) {
166
+ return fmt .Errorf ("mkdir error: %w" , err )
167
+ }
168
+ return fsys .WriteLazyFile (dst , func () (io.Reader , error ) {
169
+ f , err := fsys .Open (src )
170
+ if err != nil {
171
+ return nil , err
172
+ }
173
+ return f , nil
174
+ }, fs .ModePerm )
111
175
}
0 commit comments