github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/pkg/archive/diff.go (about)

     1  package archive // import "github.com/docker/docker/pkg/archive"
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  
    12  	"github.com/docker/docker/pkg/idtools"
    13  	"github.com/docker/docker/pkg/pools"
    14  	"github.com/docker/docker/pkg/system"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  // UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
    19  // compressed or uncompressed.
    20  // Returns the size in bytes of the contents of the layer.
    21  func UnpackLayer(dest string, layer io.Reader, options *TarOptions) (size int64, err error) {
    22  	tr := tar.NewReader(layer)
    23  	trBuf := pools.BufioReader32KPool.Get(tr)
    24  	defer pools.BufioReader32KPool.Put(trBuf)
    25  
    26  	var dirs []*tar.Header
    27  	unpackedPaths := make(map[string]struct{})
    28  
    29  	if options == nil {
    30  		options = &TarOptions{}
    31  	}
    32  	if options.ExcludePatterns == nil {
    33  		options.ExcludePatterns = []string{}
    34  	}
    35  	idMapping := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
    36  
    37  	aufsTempdir := ""
    38  	aufsHardlinks := make(map[string]*tar.Header)
    39  
    40  	// Iterate through the files in the archive.
    41  	for {
    42  		hdr, err := tr.Next()
    43  		if err == io.EOF {
    44  			// end of tar archive
    45  			break
    46  		}
    47  		if err != nil {
    48  			return 0, err
    49  		}
    50  
    51  		size += hdr.Size
    52  
    53  		// Normalize name, for safety and for a simple is-root check
    54  		hdr.Name = filepath.Clean(hdr.Name)
    55  
    56  		// Windows does not support filenames with colons in them. Ignore
    57  		// these files. This is not a problem though (although it might
    58  		// appear that it is). Let's suppose a client is running docker pull.
    59  		// The daemon it points to is Windows. Would it make sense for the
    60  		// client to be doing a docker pull Ubuntu for example (which has files
    61  		// with colons in the name under /usr/share/man/man3)? No, absolutely
    62  		// not as it would really only make sense that they were pulling a
    63  		// Windows image. However, for development, it is necessary to be able
    64  		// to pull Linux images which are in the repository.
    65  		//
    66  		// TODO Windows. Once the registry is aware of what images are Windows-
    67  		// specific or Linux-specific, this warning should be changed to an error
    68  		// to cater for the situation where someone does manage to upload a Linux
    69  		// image but have it tagged as Windows inadvertently.
    70  		if runtime.GOOS == "windows" {
    71  			if strings.Contains(hdr.Name, ":") {
    72  				logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
    73  				continue
    74  			}
    75  		}
    76  
    77  		// Note as these operations are platform specific, so must the slash be.
    78  		if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
    79  			// Not the root directory, ensure that the parent directory exists.
    80  			// This happened in some tests where an image had a tarfile without any
    81  			// parent directories.
    82  			parent := filepath.Dir(hdr.Name)
    83  			parentPath := filepath.Join(dest, parent)
    84  
    85  			if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
    86  				err = system.MkdirAll(parentPath, 0600)
    87  				if err != nil {
    88  					return 0, err
    89  				}
    90  			}
    91  		}
    92  
    93  		// Skip AUFS metadata dirs
    94  		if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) {
    95  			// Regular files inside /.wh..wh.plnk can be used as hardlink targets
    96  			// We don't want this directory, but we need the files in them so that
    97  			// such hardlinks can be resolved.
    98  			if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
    99  				basename := filepath.Base(hdr.Name)
   100  				aufsHardlinks[basename] = hdr
   101  				if aufsTempdir == "" {
   102  					if aufsTempdir, err = os.MkdirTemp("", "dockerplnk"); err != nil {
   103  						return 0, err
   104  					}
   105  					defer os.RemoveAll(aufsTempdir)
   106  				}
   107  				if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true, nil, options.InUserNS); err != nil {
   108  					return 0, err
   109  				}
   110  			}
   111  
   112  			if hdr.Name != WhiteoutOpaqueDir {
   113  				continue
   114  			}
   115  		}
   116  		path := filepath.Join(dest, hdr.Name)
   117  		rel, err := filepath.Rel(dest, path)
   118  		if err != nil {
   119  			return 0, err
   120  		}
   121  
   122  		// Note as these operations are platform specific, so must the slash be.
   123  		if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
   124  			return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
   125  		}
   126  		base := filepath.Base(path)
   127  
   128  		if strings.HasPrefix(base, WhiteoutPrefix) {
   129  			dir := filepath.Dir(path)
   130  			if base == WhiteoutOpaqueDir {
   131  				_, err := os.Lstat(dir)
   132  				if err != nil {
   133  					return 0, err
   134  				}
   135  				err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
   136  					if err != nil {
   137  						if os.IsNotExist(err) {
   138  							err = nil // parent was deleted
   139  						}
   140  						return err
   141  					}
   142  					if path == dir {
   143  						return nil
   144  					}
   145  					if _, exists := unpackedPaths[path]; !exists {
   146  						err := os.RemoveAll(path)
   147  						return err
   148  					}
   149  					return nil
   150  				})
   151  				if err != nil {
   152  					return 0, err
   153  				}
   154  			} else {
   155  				originalBase := base[len(WhiteoutPrefix):]
   156  				originalPath := filepath.Join(dir, originalBase)
   157  				if err := os.RemoveAll(originalPath); err != nil {
   158  					return 0, err
   159  				}
   160  			}
   161  		} else {
   162  			// If path exits we almost always just want to remove and replace it.
   163  			// The only exception is when it is a directory *and* the file from
   164  			// the layer is also a directory. Then we want to merge them (i.e.
   165  			// just apply the metadata from the layer).
   166  			if fi, err := os.Lstat(path); err == nil {
   167  				if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
   168  					if err := os.RemoveAll(path); err != nil {
   169  						return 0, err
   170  					}
   171  				}
   172  			}
   173  
   174  			trBuf.Reset(tr)
   175  			srcData := io.Reader(trBuf)
   176  			srcHdr := hdr
   177  
   178  			// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
   179  			// we manually retarget these into the temporary files we extracted them into
   180  			if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
   181  				linkBasename := filepath.Base(hdr.Linkname)
   182  				srcHdr = aufsHardlinks[linkBasename]
   183  				if srcHdr == nil {
   184  					return 0, fmt.Errorf("Invalid aufs hardlink")
   185  				}
   186  				tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
   187  				if err != nil {
   188  					return 0, err
   189  				}
   190  				defer tmpFile.Close()
   191  				srcData = tmpFile
   192  			}
   193  
   194  			if err := remapIDs(idMapping, srcHdr); err != nil {
   195  				return 0, err
   196  			}
   197  
   198  			if err := createTarFile(path, dest, srcHdr, srcData, !options.NoLchown, nil, options.InUserNS); err != nil {
   199  				return 0, err
   200  			}
   201  
   202  			// Directory mtimes must be handled at the end to avoid further
   203  			// file creation in them to modify the directory mtime
   204  			if hdr.Typeflag == tar.TypeDir {
   205  				dirs = append(dirs, hdr)
   206  			}
   207  			unpackedPaths[path] = struct{}{}
   208  		}
   209  	}
   210  
   211  	for _, hdr := range dirs {
   212  		path := filepath.Join(dest, hdr.Name)
   213  		if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
   214  			return 0, err
   215  		}
   216  	}
   217  
   218  	return size, nil
   219  }
   220  
   221  // ApplyLayer parses a diff in the standard layer format from `layer`,
   222  // and applies it to the directory `dest`. The stream `layer` can be
   223  // compressed or uncompressed.
   224  // Returns the size in bytes of the contents of the layer.
   225  func ApplyLayer(dest string, layer io.Reader) (int64, error) {
   226  	return applyLayerHandler(dest, layer, &TarOptions{}, true)
   227  }
   228  
   229  // ApplyUncompressedLayer parses a diff in the standard layer format from
   230  // `layer`, and applies it to the directory `dest`. The stream `layer`
   231  // can only be uncompressed.
   232  // Returns the size in bytes of the contents of the layer.
   233  func ApplyUncompressedLayer(dest string, layer io.Reader, options *TarOptions) (int64, error) {
   234  	return applyLayerHandler(dest, layer, options, false)
   235  }
   236  
   237  // do the bulk load of ApplyLayer, but allow for not calling DecompressStream
   238  func applyLayerHandler(dest string, layer io.Reader, options *TarOptions, decompress bool) (int64, error) {
   239  	dest = filepath.Clean(dest)
   240  
   241  	// We need to be able to set any perms
   242  	if runtime.GOOS != "windows" {
   243  		oldmask, err := system.Umask(0)
   244  		if err != nil {
   245  			return 0, err
   246  		}
   247  		defer system.Umask(oldmask)
   248  	}
   249  
   250  	if decompress {
   251  		decompLayer, err := DecompressStream(layer)
   252  		if err != nil {
   253  			return 0, err
   254  		}
   255  		defer decompLayer.Close()
   256  		layer = decompLayer
   257  	}
   258  	return UnpackLayer(dest, layer, options)
   259  }