@@ -110,11 +110,15 @@ func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int
110110 if options .Filter == nil {
111111 options .Filter = all
112112 }
113+ if options .applyFunc == nil {
114+ options .applyFunc = applyNaive
115+ }
113116
114- return apply (ctx , root , tar .NewReader (r ), options )
117+ return options . applyFunc (ctx , root , tar .NewReader (r ), options )
115118}
116119
117- // 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.
118122// See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
119123func applyNaive (ctx context.Context , root string , tr * tar.Reader , options ApplyOptions ) (size int64 , err error ) {
120124 var (
@@ -123,8 +127,50 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
123127 // Used for handling opaque directory markers which
124128 // may occur out of order
125129 unpackedPaths = make (map [string ]struct {})
130+
131+ convertWhiteout = options .ConvertWhiteout
126132 )
127133
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+
128174 // Iterate through the files in the archive.
129175 for {
130176 select {
@@ -182,55 +228,13 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
182228 if base == "" {
183229 parentPath = filepath .Dir (path )
184230 }
185- if _ , err := os .Lstat (parentPath ); err != nil && os .IsNotExist (err ) {
186- err = mkdirAll (parentPath , 0755 )
187- if err != nil {
188- return 0 , err
189- }
231+ if err := mkparent (ctx , parentPath , root , options .Parents ); err != nil {
232+ return 0 , err
190233 }
191234 }
192235
193236 // Naive whiteout convert function which handles whiteout files by
194237 // removing the target files.
195- convertWhiteout := func (hdr * tar.Header , path string ) (bool , error ) {
196- base := filepath .Base (path )
197- dir := filepath .Dir (path )
198- if base == whiteoutOpaqueDir {
199- _ , err := os .Lstat (dir )
200- if err != nil {
201- return false , err
202- }
203- err = filepath .Walk (dir , func (path string , info os.FileInfo , err error ) error {
204- if err != nil {
205- if os .IsNotExist (err ) {
206- err = nil // parent was deleted
207- }
208- return err
209- }
210- if path == dir {
211- return nil
212- }
213- if _ , exists := unpackedPaths [path ]; ! exists {
214- err := os .RemoveAll (path )
215- return err
216- }
217- return nil
218- })
219- return false , err
220- }
221-
222- if strings .HasPrefix (base , whiteoutPrefix ) {
223- originalBase := base [len (whiteoutPrefix ):]
224- originalPath := filepath .Join (dir , originalBase )
225-
226- return false , os .RemoveAll (originalPath )
227- }
228-
229- return true , nil
230- }
231- if options .ConvertWhiteout != nil {
232- convertWhiteout = options .ConvertWhiteout
233- }
234238 if err := validateWhiteout (path ); err != nil {
235239 return 0 , err
236240 }
@@ -375,6 +379,66 @@ func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header
375379 return chtimes (path , boundTime (latestTime (hdr .AccessTime , hdr .ModTime )), boundTime (hdr .ModTime ))
376380}
377381
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+
378442type changeWriter struct {
379443 tw * tar.Writer
380444 source string
@@ -545,6 +609,9 @@ func (cw *changeWriter) Close() error {
545609}
546610
547611func (cw * changeWriter ) includeParents (hdr * tar.Header ) error {
612+ if cw .addedDirs == nil {
613+ return nil
614+ }
548615 name := strings .TrimRight (hdr .Name , "/" )
549616 fname := filepath .Join (cw .source , name )
550617 parent := filepath .Dir (name )
0 commit comments