@@ -19,9 +19,7 @@ package archive
1919import (
2020 "archive/tar"
2121 "context"
22- "fmt"
2322 "io"
24- "io/ioutil"
2523 "os"
2624 "path/filepath"
2725 "runtime"
@@ -91,11 +89,6 @@ const (
9189 // archives.
9290 whiteoutMetaPrefix = whiteoutPrefix + whiteoutPrefix
9391
94- // whiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
95- // layers. Normally these should not go into exported archives and all changed
96- // hardlinks should be copied to the top layer.
97- whiteoutLinkDir = whiteoutMetaPrefix + "plnk"
98-
9992 // whiteoutOpaqueDir file means directory has been made opaque - meaning
10093 // readdir calls to this directory do not follow to lower layers.
10194 whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
@@ -117,11 +110,15 @@ func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int
117110 if options .Filter == nil {
118111 options .Filter = all
119112 }
113+ if options .applyFunc == nil {
114+ options .applyFunc = applyNaive
115+ }
120116
121- return apply (ctx , root , tar .NewReader (r ), options )
117+ return options . applyFunc (ctx , root , tar .NewReader (r ), options )
122118}
123119
124- // applyNaive applies a tar stream of an OCI style diff tar.
120+ // applyNaive applies a tar stream of an OCI style diff tar to a directory
121+ // applying each file as either a whole file or whiteout.
125122// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
126123func applyNaive (ctx context.Context , root string , tr * tar.Reader , options ApplyOptions ) (size int64 , err error ) {
127124 var (
@@ -131,11 +128,49 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
131128 // may occur out of order
132129 unpackedPaths = make (map [string ]struct {})
133130
134- // Used for aufs plink directory
135- aufsTempdir = ""
136- aufsHardlinks = make (map [string ]* tar.Header )
131+ convertWhiteout = options .ConvertWhiteout
137132 )
138133
134+ if convertWhiteout == nil {
135+ // handle whiteouts by removing the target files
136+ convertWhiteout = func (hdr * tar.Header , path string ) (bool , error ) {
137+ base := filepath .Base (path )
138+ dir := filepath .Dir (path )
139+ if base == whiteoutOpaqueDir {
140+ _ , err := os .Lstat (dir )
141+ if err != nil {
142+ return false , err
143+ }
144+ err = filepath .Walk (dir , func (path string , info os.FileInfo , err error ) error {
145+ if err != nil {
146+ if os .IsNotExist (err ) {
147+ err = nil // parent was deleted
148+ }
149+ return err
150+ }
151+ if path == dir {
152+ return nil
153+ }
154+ if _ , exists := unpackedPaths [path ]; ! exists {
155+ err := os .RemoveAll (path )
156+ return err
157+ }
158+ return nil
159+ })
160+ return false , err
161+ }
162+
163+ if strings .HasPrefix (base , whiteoutPrefix ) {
164+ originalBase := base [len (whiteoutPrefix ):]
165+ originalPath := filepath .Join (dir , originalBase )
166+
167+ return false , os .RemoveAll (originalPath )
168+ }
169+
170+ return true , nil
171+ }
172+ }
173+
139174 // Iterate through the files in the archive.
140175 for {
141176 select {
@@ -193,85 +228,21 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
193228 if base == "" {
194229 parentPath = filepath .Dir (path )
195230 }
196- if _ , err := os .Lstat (parentPath ); err != nil && os .IsNotExist (err ) {
197- err = mkdirAll (parentPath , 0755 )
198- if err != nil {
199- return 0 , err
200- }
231+ if err := mkparent (ctx , parentPath , root , options .Parents ); err != nil {
232+ return 0 , err
201233 }
202234 }
203235
204- // Skip AUFS metadata dirs
205- if strings .HasPrefix (hdr .Name , whiteoutMetaPrefix ) {
206- // Regular files inside /.wh..wh.plnk can be used as hardlink targets
207- // We don't want this directory, but we need the files in them so that
208- // such hardlinks can be resolved.
209- if strings .HasPrefix (hdr .Name , whiteoutLinkDir ) && hdr .Typeflag == tar .TypeReg {
210- basename := filepath .Base (hdr .Name )
211- aufsHardlinks [basename ] = hdr
212- if aufsTempdir == "" {
213- if aufsTempdir , err = ioutil .TempDir (os .Getenv ("XDG_RUNTIME_DIR" ), "dockerplnk" ); err != nil {
214- return 0 , err
215- }
216- defer os .RemoveAll (aufsTempdir )
217- }
218- p , err := fs .RootPath (aufsTempdir , basename )
219- if err != nil {
220- return 0 , err
221- }
222- if err := createTarFile (ctx , p , root , hdr , tr ); err != nil {
223- return 0 , err
224- }
225- }
226-
227- if hdr .Name != whiteoutOpaqueDir {
228- continue
229- }
236+ // Naive whiteout convert function which handles whiteout files by
237+ // removing the target files.
238+ if err := validateWhiteout (path ); err != nil {
239+ return 0 , err
230240 }
231-
232- if strings .HasPrefix (base , whiteoutPrefix ) {
233- dir := filepath .Dir (path )
234- if base == whiteoutOpaqueDir {
235- _ , err := os .Lstat (dir )
236- if err != nil {
237- return 0 , err
238- }
239- err = filepath .Walk (dir , func (path string , info os.FileInfo , err error ) error {
240- if err != nil {
241- if os .IsNotExist (err ) {
242- err = nil // parent was deleted
243- }
244- return err
245- }
246- if path == dir {
247- return nil
248- }
249- if _ , exists := unpackedPaths [path ]; ! exists {
250- err := os .RemoveAll (path )
251- return err
252- }
253- return nil
254- })
255- if err != nil {
256- return 0 , err
257- }
258- continue
259- }
260-
261- originalBase := base [len (whiteoutPrefix ):]
262- originalPath := filepath .Join (dir , originalBase )
263-
264- // Ensure originalPath is under dir
265- if dir [len (dir )- 1 ] != filepath .Separator {
266- dir += string (filepath .Separator )
267- }
268- if ! strings .HasPrefix (originalPath , dir ) {
269- return 0 , errors .Wrapf (errInvalidArchive , "invalid whiteout name: %v" , base )
270- }
271-
272- if err := os .RemoveAll (originalPath ); err != nil {
273- return 0 , err
274- }
241+ writeFile , err := convertWhiteout (hdr , path )
242+ if err != nil {
243+ return 0 , errors .Wrapf (err , "failed to convert whiteout file %q" , hdr .Name )
244+ }
245+ if ! writeFile {
275246 continue
276247 }
277248 // If path exits we almost always just want to remove and replace it.
@@ -289,26 +260,6 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
289260 srcData := io .Reader (tr )
290261 srcHdr := hdr
291262
292- // Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
293- // we manually retarget these into the temporary files we extracted them into
294- if hdr .Typeflag == tar .TypeLink && strings .HasPrefix (filepath .Clean (hdr .Linkname ), whiteoutLinkDir ) {
295- linkBasename := filepath .Base (hdr .Linkname )
296- srcHdr = aufsHardlinks [linkBasename ]
297- if srcHdr == nil {
298- return 0 , fmt .Errorf ("invalid aufs hardlink" )
299- }
300- p , err := fs .RootPath (aufsTempdir , linkBasename )
301- if err != nil {
302- return 0 , err
303- }
304- tmpFile , err := os .Open (p )
305- if err != nil {
306- return 0 , err
307- }
308- defer tmpFile .Close ()
309- srcData = tmpFile
310- }
311-
312263 if err := createTarFile (ctx , path , root , srcHdr , srcData ); err != nil {
313264 return 0 , err
314265 }
@@ -428,6 +379,66 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
428379 return chtimes (path , boundTime (latestTime (hdr .AccessTime , hdr .ModTime )), boundTime (hdr .ModTime ))
429380}
430381
382+ func mkparent (ctx context.Context , path , root string , parents []string ) error {
383+ if dir , err := os .Lstat (path ); err == nil {
384+ if dir .IsDir () {
385+ return nil
386+ }
387+ return & os.PathError {
388+ Op : "mkparent" ,
389+ Path : path ,
390+ Err : syscall .ENOTDIR ,
391+ }
392+ } else if ! os .IsNotExist (err ) {
393+ return err
394+ }
395+
396+ i := len (path )
397+ for i > len (root ) && ! os .IsPathSeparator (path [i - 1 ]) {
398+ i --
399+ }
400+
401+ if i > len (root )+ 1 {
402+ if err := mkparent (ctx , path [:i - 1 ], root , parents ); err != nil {
403+ return err
404+ }
405+ }
406+
407+ if err := mkdir (path , 0755 ); err != nil {
408+ // Check that still doesn't exist
409+ dir , err1 := os .Lstat (path )
410+ if err1 == nil && dir .IsDir () {
411+ return nil
412+ }
413+ return err
414+ }
415+
416+ for _ , p := range parents {
417+ ppath , err := fs .RootPath (p , path [len (root ):])
418+ if err != nil {
419+ return err
420+ }
421+
422+ dir , err := os .Lstat (ppath )
423+ if err == nil {
424+ if ! dir .IsDir () {
425+ // Replaced, do not copy attributes
426+ break
427+ }
428+ if err := copyDirInfo (dir , path ); err != nil {
429+ return err
430+ }
431+ return copyUpXAttrs (path , ppath )
432+ } else if ! os .IsNotExist (err ) {
433+ return err
434+ }
435+ }
436+
437+ log .G (ctx ).Debugf ("parent directory %q not found: default permissions(0755) used" , path )
438+
439+ return nil
440+ }
441+
431442type changeWriter struct {
432443 tw * tar.Writer
433444 source string
@@ -598,6 +609,9 @@ func (cw *changeWriter) Close() error {
598609}
599610
600611func (cw * changeWriter ) includeParents (hdr * tar.Header ) error {
612+ if cw .addedDirs == nil {
613+ return nil
614+ }
601615 name := strings .TrimRight (hdr .Name , "/" )
602616 fname := filepath .Join (cw .source , name )
603617 parent := filepath .Dir (name )
@@ -684,3 +698,26 @@ func hardlinkRootPath(root, linkname string) (string, error) {
684698 }
685699 return targetPath , nil
686700}
701+
702+ func validateWhiteout (path string ) error {
703+ base := filepath .Base (path )
704+ dir := filepath .Dir (path )
705+
706+ if base == whiteoutOpaqueDir {
707+ return nil
708+ }
709+
710+ if strings .HasPrefix (base , whiteoutPrefix ) {
711+ originalBase := base [len (whiteoutPrefix ):]
712+ originalPath := filepath .Join (dir , originalBase )
713+
714+ // Ensure originalPath is under dir
715+ if dir [len (dir )- 1 ] != filepath .Separator {
716+ dir += string (filepath .Separator )
717+ }
718+ if ! strings .HasPrefix (originalPath , dir ) {
719+ return errors .Wrapf (errInvalidArchive , "invalid whiteout name: %v" , base )
720+ }
721+ }
722+ return nil
723+ }
0 commit comments