@@ -218,100 +218,131 @@ loop:
218218 return errors .Wrapf (err , "error advancing tar stream" )
219219 }
220220
221- hdr .Name = filepath .Clean (hdr .Name )
222- if ! strings .HasSuffix (hdr .Name , string (os .PathSeparator )) {
223- // Not the root directory, ensure that the parent directory exists
224- parent := filepath .Dir (hdr .Name )
225- parentPath := filepath .Join (dest , parent )
226- if _ , err2 := os .Lstat (parentPath ); err2 != nil && os .IsNotExist (err2 ) {
227- if err3 := os .MkdirAll (parentPath , 0755 ); err3 != nil {
228- return err3
229- }
230- }
231- }
232- path := filepath .Join (dest , hdr .Name )
233- if entries [path ] {
234- return fmt .Errorf ("duplicate entry for %s" , path )
235- }
236- entries [path ] = true
237- rel , err := filepath .Rel (dest , path )
221+ var whiteout bool
222+ whiteout , err = unpackLayerEntry (dest , hdr , tr , & entries )
238223 if err != nil {
239224 return err
240225 }
241- info := hdr .FileInfo ()
242- if strings .HasPrefix (rel , ".." + string (os .PathSeparator )) {
243- return fmt .Errorf ("%q is outside of %q" , hdr .Name , dest )
226+ if whiteout {
227+ continue loop
244228 }
245229
246- if strings .HasPrefix (info .Name (), ".wh." ) {
247- path = strings .Replace (path , ".wh." , "" , 1 )
230+ // Directory mtimes must be handled at the end to avoid further
231+ // file creation in them to modify the directory mtime
232+ if hdr .Typeflag == tar .TypeDir {
233+ dirs = append (dirs , hdr )
234+ }
235+ }
236+ for _ , hdr := range dirs {
237+ path := filepath .Join (dest , hdr .Name )
238+
239+ finfo := hdr .FileInfo ()
240+ // I believe the old version was using time.Now().UTC() to overcome an
241+ // invalid error from chtimes.....but here we lose hdr.AccessTime like this...
242+ if err := os .Chtimes (path , time .Now ().UTC (), finfo .ModTime ()); err != nil {
243+ return errors .Wrap (err , "error changing time" )
244+ }
245+ }
246+ return nil
247+ }
248248
249- if err := os .RemoveAll (path ); err != nil {
250- return errors .Wrap (err , "unable to delete whiteout path" )
249+ // unpackLayerEntry unpacks a single entry from a layer.
250+ func unpackLayerEntry (dest string , header * tar.Header , reader io.Reader , entries * map [string ]bool ) (whiteout bool , err error ) {
251+ header .Name = filepath .Clean (header .Name )
252+ if ! strings .HasSuffix (header .Name , string (os .PathSeparator )) {
253+ // Not the root directory, ensure that the parent directory exists
254+ parent := filepath .Dir (header .Name )
255+ parentPath := filepath .Join (dest , parent )
256+ if _ , err2 := os .Lstat (parentPath ); err2 != nil && os .IsNotExist (err2 ) {
257+ if err3 := os .MkdirAll (parentPath , 0755 ); err3 != nil {
258+ return false , err3
251259 }
260+ }
261+ }
262+ path := filepath .Join (dest , header .Name )
263+ if (* entries )[path ] {
264+ return false , fmt .Errorf ("duplicate entry for %s" , path )
265+ }
266+ (* entries )[path ] = true
267+ rel , err := filepath .Rel (dest , path )
268+ if err != nil {
269+ return false , err
270+ }
271+ info := header .FileInfo ()
272+ if strings .HasPrefix (rel , ".." + string (os .PathSeparator )) {
273+ return false , fmt .Errorf ("%q is outside of %q" , header .Name , dest )
274+ }
252275
253- continue loop
276+ if strings .HasPrefix (info .Name (), ".wh." ) {
277+ path = strings .Replace (path , ".wh." , "" , 1 )
278+
279+ if err = os .RemoveAll (path ); err != nil {
280+ return true , errors .Wrap (err , "unable to delete whiteout path" )
254281 }
255282
256- switch hdr .Typeflag {
257- case tar .TypeDir :
258- if fi , err := os .Lstat (path ); ! (err == nil && fi .IsDir ()) {
259- if err2 := os .MkdirAll (path , info .Mode ()); err2 != nil {
260- return errors .Wrap (err2 , "error creating directory" )
261- }
262- }
283+ return true , nil
284+ }
263285
264- case tar .TypeReg , tar .TypeRegA :
265- f , err := os .OpenFile (path , os .O_CREATE | os .O_WRONLY , info .Mode ())
286+ if header .Typeflag != tar .TypeDir {
287+ err = os .RemoveAll (path )
288+ if err != nil && ! os .IsNotExist (err ) {
289+ return false , err
290+ }
291+ }
292+
293+ switch header .Typeflag {
294+ case tar .TypeDir :
295+ fi , err := os .Lstat (path )
296+ if err != nil && ! os .IsNotExist (err ) {
297+ return false , err
298+ }
299+ if os .IsNotExist (err ) || ! fi .IsDir () {
300+ err = os .RemoveAll (path )
301+ if err != nil && ! os .IsNotExist (err ) {
302+ return false , err
303+ }
304+ err = os .MkdirAll (path , info .Mode ())
266305 if err != nil {
267- return errors . Wrap ( err , "unable to open file" )
306+ return false , err
268307 }
308+ }
269309
270- if _ , err := io . Copy ( f , tr ); err != nil {
271- f . Close ( )
272- return errors . Wrap ( err , "unable to copy" )
273- }
274- f . Close ()
310+ case tar . TypeReg , tar . TypeRegA :
311+ f , err := os . OpenFile ( path , os . O_CREATE | os . O_WRONLY , info . Mode () )
312+ if err != nil {
313+ return false , errors . Wrap ( err , "unable to open file" )
314+ }
275315
276- case tar .TypeLink :
277- target := filepath .Join (dest , hdr .Linkname )
316+ if _ , err := io .Copy (f , reader ); err != nil {
317+ f .Close ()
318+ return false , errors .Wrap (err , "unable to copy" )
319+ }
320+ f .Close ()
278321
279- if ! strings .HasPrefix (target , dest ) {
280- return fmt .Errorf ("invalid hardlink %q -> %q" , target , hdr .Linkname )
281- }
322+ case tar .TypeLink :
323+ target := filepath .Join (dest , header .Linkname )
282324
283- if err := os . Link (target , path ); err != nil {
284- return err
285- }
325+ if ! strings . HasPrefix (target , dest ) {
326+ return false , fmt . Errorf ( "invalid hardlink %q -> %q" , target , header . Linkname )
327+ }
286328
287- case tar .TypeSymlink :
288- target := filepath .Join (filepath .Dir (path ), hdr .Linkname )
329+ if err := os .Link (target , path ); err != nil {
330+ return false , err
331+ }
289332
290- if ! strings .HasPrefix (target , dest ) {
291- return fmt .Errorf ("invalid symlink %q -> %q" , path , hdr .Linkname )
292- }
333+ case tar .TypeSymlink :
334+ target := filepath .Join (filepath .Dir (path ), header .Linkname )
293335
294- if err := os .Symlink (hdr .Linkname , path ); err != nil {
295- return err
296- }
297- case tar .TypeXGlobalHeader :
298- return nil
299- }
300- // Directory mtimes must be handled at the end to avoid further
301- // file creation in them to modify the directory mtime
302- if hdr .Typeflag == tar .TypeDir {
303- dirs = append (dirs , hdr )
336+ if ! strings .HasPrefix (target , dest ) {
337+ return false , fmt .Errorf ("invalid symlink %q -> %q" , path , header .Linkname )
304338 }
305- }
306- for _ , hdr := range dirs {
307- path := filepath .Join (dest , hdr .Name )
308339
309- finfo := hdr .FileInfo ()
310- // I believe the old version was using time.Now().UTC() to overcome an
311- // invalid error from chtimes.....but here we lose hdr.AccessTime like this...
312- if err := os .Chtimes (path , time .Now ().UTC (), finfo .ModTime ()); err != nil {
313- return errors .Wrap (err , "error changing time" )
340+ if err := os .Symlink (header .Linkname , path ); err != nil {
341+ return false , err
314342 }
343+ case tar .TypeXGlobalHeader :
344+ return false , nil
315345 }
316- return nil
346+
347+ return false , nil
317348}
0 commit comments