@@ -2,13 +2,13 @@ package parser
22
33import (
44 "archive/tar"
5- "bytes"
65 "compress/gzip"
76 "errors"
87 "fmt"
98 "io"
109 "io/fs"
1110 "os"
11+ "path"
1212 "path/filepath"
1313
1414 "github.com/liamg/memoryfs"
@@ -18,18 +18,18 @@ import (
1818
1919var errSkipFS = errors .New ("skip parse FS" )
2020
21- func (p * Parser ) addTarToFS (path string ) (fs.FS , error ) {
21+ func (p * Parser ) addTarToFS (archivePath string ) (fs.FS , error ) {
2222 tarFS := memoryfs .CloneFS (p .workingFS )
2323
24- file , err := tarFS .Open (path )
24+ file , err := tarFS .Open (archivePath )
2525 if err != nil {
2626 return nil , fmt .Errorf ("failed to open tar: %w" , err )
2727 }
2828 defer file .Close ()
2929
3030 var tr * tar.Reader
3131
32- if detection .IsZip (path ) {
32+ if detection .IsZip (archivePath ) {
3333 zipped , err := gzip .NewReader (file )
3434 if err != nil {
3535 return nil , fmt .Errorf ("failed to create gzip reader: %w" , err )
@@ -41,6 +41,7 @@ func (p *Parser) addTarToFS(path string) (fs.FS, error) {
4141 }
4242
4343 checkExistedChart := true
44+ symlinks := make (map [string ]string )
4445
4546 for {
4647 header , err := tr .Next ()
@@ -51,61 +52,124 @@ func (p *Parser) addTarToFS(path string) (fs.FS, error) {
5152 return nil , fmt .Errorf ("failed to get next entry: %w" , err )
5253 }
5354
55+ name := filepath .ToSlash (header .Name )
56+
5457 if checkExistedChart {
5558 // Do not add archive files to FS if the chart already exists
5659 // This can happen when the source chart is located next to an archived chart (with the `helm package` command)
5760 // 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 {
5962 return nil , errSkipFS
6063 }
6164 checkExistedChart = false
6265 }
6366
6467 // 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 )
6671
6772 switch header .Typeflag {
6873 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 ) {
7075 return nil , err
7176 }
7277 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 {
8080 return nil , err
8181 }
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
8686 }
87+
88+ symlinks [targetPath ] = path .Join (path .Dir (targetPath ), link ) // nolint:gosec // virtual file system is used
8789 default :
8890 return nil , fmt .Errorf ("header type %q is not supported" , header .Typeflag )
8991 }
9092 }
9193
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 )
94102 }
95103
96104 return tarFS , nil
97105}
98106
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
107147 }
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
108159 }
109160
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 )
111175}
0 commit comments