github.com/lalkh/containerd@v1.4.3/archive/tar.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package archive
    18  
    19  import (
    20  	"archive/tar"
    21  	"context"
    22  	"io"
    23  	"os"
    24  	"path/filepath"
    25  	"runtime"
    26  	"strings"
    27  	"sync"
    28  	"syscall"
    29  	"time"
    30  
    31  	"github.com/containerd/containerd/log"
    32  	"github.com/containerd/continuity/fs"
    33  	"github.com/pkg/errors"
    34  )
    35  
    36  var bufPool = &sync.Pool{
    37  	New: func() interface{} {
    38  		buffer := make([]byte, 32*1024)
    39  		return &buffer
    40  	},
    41  }
    42  
    43  var errInvalidArchive = errors.New("invalid archive")
    44  
    45  // Diff returns a tar stream of the computed filesystem
    46  // difference between the provided directories.
    47  //
    48  // Produces a tar using OCI style file markers for deletions. Deleted
    49  // files will be prepended with the prefix ".wh.". This style is
    50  // based off AUFS whiteouts.
    51  // See https://github.com/opencontainers/image-spec/blob/master/layer.md
    52  func Diff(ctx context.Context, a, b string) io.ReadCloser {
    53  	r, w := io.Pipe()
    54  
    55  	go func() {
    56  		err := WriteDiff(ctx, w, a, b)
    57  		if err = w.CloseWithError(err); err != nil {
    58  			log.G(ctx).WithError(err).Debugf("closing tar pipe failed")
    59  		}
    60  	}()
    61  
    62  	return r
    63  }
    64  
    65  // WriteDiff writes a tar stream of the computed difference between the
    66  // provided directories.
    67  //
    68  // Produces a tar using OCI style file markers for deletions. Deleted
    69  // files will be prepended with the prefix ".wh.". This style is
    70  // based off AUFS whiteouts.
    71  // See https://github.com/opencontainers/image-spec/blob/master/layer.md
    72  func WriteDiff(ctx context.Context, w io.Writer, a, b string) error {
    73  	cw := newChangeWriter(w, b)
    74  	err := fs.Changes(ctx, a, b, cw.HandleChange)
    75  	if err != nil {
    76  		return errors.Wrap(err, "failed to create diff tar stream")
    77  	}
    78  	return cw.Close()
    79  }
    80  
    81  const (
    82  	// whiteoutPrefix prefix means file is a whiteout. If this is followed by a
    83  	// filename this means that file has been removed from the base layer.
    84  	// See https://github.com/opencontainers/image-spec/blob/master/layer.md#whiteouts
    85  	whiteoutPrefix = ".wh."
    86  
    87  	// whiteoutMetaPrefix prefix means whiteout has a special meaning and is not
    88  	// for removing an actual file. Normally these files are excluded from exported
    89  	// archives.
    90  	whiteoutMetaPrefix = whiteoutPrefix + whiteoutPrefix
    91  
    92  	// whiteoutOpaqueDir file means directory has been made opaque - meaning
    93  	// readdir calls to this directory do not follow to lower layers.
    94  	whiteoutOpaqueDir = whiteoutMetaPrefix + ".opq"
    95  
    96  	paxSchilyXattr = "SCHILY.xattr."
    97  )
    98  
    99  // Apply applies a tar stream of an OCI style diff tar.
   100  // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
   101  func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int64, error) {
   102  	root = filepath.Clean(root)
   103  
   104  	var options ApplyOptions
   105  	for _, opt := range opts {
   106  		if err := opt(&options); err != nil {
   107  			return 0, errors.Wrap(err, "failed to apply option")
   108  		}
   109  	}
   110  	if options.Filter == nil {
   111  		options.Filter = all
   112  	}
   113  	if options.applyFunc == nil {
   114  		options.applyFunc = applyNaive
   115  	}
   116  
   117  	return options.applyFunc(ctx, root, tar.NewReader(r), options)
   118  }
   119  
   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.
   122  // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
   123  func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
   124  	var (
   125  		dirs []*tar.Header
   126  
   127  		// Used for handling opaque directory markers which
   128  		// may occur out of order
   129  		unpackedPaths = make(map[string]struct{})
   130  
   131  		convertWhiteout = options.ConvertWhiteout
   132  	)
   133  
   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  
   174  	// Iterate through the files in the archive.
   175  	for {
   176  		select {
   177  		case <-ctx.Done():
   178  			return 0, ctx.Err()
   179  		default:
   180  		}
   181  
   182  		hdr, err := tr.Next()
   183  		if err == io.EOF {
   184  			// end of tar archive
   185  			break
   186  		}
   187  		if err != nil {
   188  			return 0, err
   189  		}
   190  
   191  		size += hdr.Size
   192  
   193  		// Normalize name, for safety and for a simple is-root check
   194  		hdr.Name = filepath.Clean(hdr.Name)
   195  
   196  		accept, err := options.Filter(hdr)
   197  		if err != nil {
   198  			return 0, err
   199  		}
   200  		if !accept {
   201  			continue
   202  		}
   203  
   204  		if skipFile(hdr) {
   205  			log.G(ctx).Warnf("file %q ignored: archive may not be supported on system", hdr.Name)
   206  			continue
   207  		}
   208  
   209  		// Split name and resolve symlinks for root directory.
   210  		ppath, base := filepath.Split(hdr.Name)
   211  		ppath, err = fs.RootPath(root, ppath)
   212  		if err != nil {
   213  			return 0, errors.Wrap(err, "failed to get root path")
   214  		}
   215  
   216  		// Join to root before joining to parent path to ensure relative links are
   217  		// already resolved based on the root before adding to parent.
   218  		path := filepath.Join(ppath, filepath.Join("/", base))
   219  		if path == root {
   220  			log.G(ctx).Debugf("file %q ignored: resolved to root", hdr.Name)
   221  			continue
   222  		}
   223  
   224  		// If file is not directly under root, ensure parent directory
   225  		// exists or is created.
   226  		if ppath != root {
   227  			parentPath := ppath
   228  			if base == "" {
   229  				parentPath = filepath.Dir(path)
   230  			}
   231  			if err := mkparent(ctx, parentPath, root, options.Parents); err != nil {
   232  				return 0, err
   233  			}
   234  		}
   235  
   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
   240  		}
   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 {
   246  			continue
   247  		}
   248  		// If path exits we almost always just want to remove and replace it.
   249  		// The only exception is when it is a directory *and* the file from
   250  		// the layer is also a directory. Then we want to merge them (i.e.
   251  		// just apply the metadata from the layer).
   252  		if fi, err := os.Lstat(path); err == nil {
   253  			if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
   254  				if err := os.RemoveAll(path); err != nil {
   255  					return 0, err
   256  				}
   257  			}
   258  		}
   259  
   260  		srcData := io.Reader(tr)
   261  		srcHdr := hdr
   262  
   263  		if err := createTarFile(ctx, path, root, srcHdr, srcData); err != nil {
   264  			return 0, err
   265  		}
   266  
   267  		// Directory mtimes must be handled at the end to avoid further
   268  		// file creation in them to modify the directory mtime
   269  		if hdr.Typeflag == tar.TypeDir {
   270  			dirs = append(dirs, hdr)
   271  		}
   272  		unpackedPaths[path] = struct{}{}
   273  	}
   274  
   275  	for _, hdr := range dirs {
   276  		path, err := fs.RootPath(root, hdr.Name)
   277  		if err != nil {
   278  			return 0, err
   279  		}
   280  		if err := chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime)); err != nil {
   281  			return 0, err
   282  		}
   283  	}
   284  
   285  	return size, nil
   286  }
   287  
   288  func createTarFile(ctx context.Context, path, extractDir string, hdr *tar.Header, reader io.Reader) error {
   289  	// hdr.Mode is in linux format, which we can use for syscalls,
   290  	// but for os.Foo() calls we need the mode converted to os.FileMode,
   291  	// so use hdrInfo.Mode() (they differ for e.g. setuid bits)
   292  	hdrInfo := hdr.FileInfo()
   293  
   294  	switch hdr.Typeflag {
   295  	case tar.TypeDir:
   296  		// Create directory unless it exists as a directory already.
   297  		// In that case we just want to merge the two
   298  		if fi, err := os.Lstat(path); !(err == nil && fi.IsDir()) {
   299  			if err := mkdir(path, hdrInfo.Mode()); err != nil {
   300  				return err
   301  			}
   302  		}
   303  
   304  	case tar.TypeReg, tar.TypeRegA:
   305  		file, err := openFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, hdrInfo.Mode())
   306  		if err != nil {
   307  			return err
   308  		}
   309  
   310  		_, err = copyBuffered(ctx, file, reader)
   311  		if err1 := file.Close(); err == nil {
   312  			err = err1
   313  		}
   314  		if err != nil {
   315  			return err
   316  		}
   317  
   318  	case tar.TypeBlock, tar.TypeChar:
   319  		// Handle this is an OS-specific way
   320  		if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
   321  			return err
   322  		}
   323  
   324  	case tar.TypeFifo:
   325  		// Handle this is an OS-specific way
   326  		if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
   327  			return err
   328  		}
   329  
   330  	case tar.TypeLink:
   331  		targetPath, err := hardlinkRootPath(extractDir, hdr.Linkname)
   332  		if err != nil {
   333  			return err
   334  		}
   335  
   336  		if err := os.Link(targetPath, path); err != nil {
   337  			return err
   338  		}
   339  
   340  	case tar.TypeSymlink:
   341  		if err := os.Symlink(hdr.Linkname, path); err != nil {
   342  			return err
   343  		}
   344  
   345  	case tar.TypeXGlobalHeader:
   346  		log.G(ctx).Debug("PAX Global Extended Headers found and ignored")
   347  		return nil
   348  
   349  	default:
   350  		return errors.Errorf("unhandled tar header type %d\n", hdr.Typeflag)
   351  	}
   352  
   353  	// Lchown is not supported on Windows.
   354  	if runtime.GOOS != "windows" {
   355  		if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil {
   356  			return err
   357  		}
   358  	}
   359  
   360  	for key, value := range hdr.PAXRecords {
   361  		if strings.HasPrefix(key, paxSchilyXattr) {
   362  			key = key[len(paxSchilyXattr):]
   363  			if err := setxattr(path, key, value); err != nil {
   364  				if errors.Is(err, syscall.ENOTSUP) {
   365  					log.G(ctx).WithError(err).Warnf("ignored xattr %s in archive", key)
   366  					continue
   367  				}
   368  				return err
   369  			}
   370  		}
   371  	}
   372  
   373  	// There is no LChmod, so ignore mode for symlink. Also, this
   374  	// must happen after chown, as that can modify the file mode
   375  	if err := handleLChmod(hdr, path, hdrInfo); err != nil {
   376  		return err
   377  	}
   378  
   379  	return chtimes(path, boundTime(latestTime(hdr.AccessTime, hdr.ModTime)), boundTime(hdr.ModTime))
   380  }
   381  
   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  
   442  type changeWriter struct {
   443  	tw        *tar.Writer
   444  	source    string
   445  	whiteoutT time.Time
   446  	inodeSrc  map[uint64]string
   447  	inodeRefs map[uint64][]string
   448  	addedDirs map[string]struct{}
   449  }
   450  
   451  func newChangeWriter(w io.Writer, source string) *changeWriter {
   452  	return &changeWriter{
   453  		tw:        tar.NewWriter(w),
   454  		source:    source,
   455  		whiteoutT: time.Now(),
   456  		inodeSrc:  map[uint64]string{},
   457  		inodeRefs: map[uint64][]string{},
   458  		addedDirs: map[string]struct{}{},
   459  	}
   460  }
   461  
   462  func (cw *changeWriter) HandleChange(k fs.ChangeKind, p string, f os.FileInfo, err error) error {
   463  	if err != nil {
   464  		return err
   465  	}
   466  	if k == fs.ChangeKindDelete {
   467  		whiteOutDir := filepath.Dir(p)
   468  		whiteOutBase := filepath.Base(p)
   469  		whiteOut := filepath.Join(whiteOutDir, whiteoutPrefix+whiteOutBase)
   470  		hdr := &tar.Header{
   471  			Typeflag:   tar.TypeReg,
   472  			Name:       whiteOut[1:],
   473  			Size:       0,
   474  			ModTime:    cw.whiteoutT,
   475  			AccessTime: cw.whiteoutT,
   476  			ChangeTime: cw.whiteoutT,
   477  		}
   478  		if err := cw.includeParents(hdr); err != nil {
   479  			return err
   480  		}
   481  		if err := cw.tw.WriteHeader(hdr); err != nil {
   482  			return errors.Wrap(err, "failed to write whiteout header")
   483  		}
   484  	} else {
   485  		var (
   486  			link   string
   487  			err    error
   488  			source = filepath.Join(cw.source, p)
   489  		)
   490  
   491  		switch {
   492  		case f.Mode()&os.ModeSocket != 0:
   493  			return nil // ignore sockets
   494  		case f.Mode()&os.ModeSymlink != 0:
   495  			if link, err = os.Readlink(source); err != nil {
   496  				return err
   497  			}
   498  		}
   499  
   500  		hdr, err := tar.FileInfoHeader(f, link)
   501  		if err != nil {
   502  			return err
   503  		}
   504  
   505  		hdr.Mode = int64(chmodTarEntry(os.FileMode(hdr.Mode)))
   506  
   507  		// truncate timestamp for compatibility. without PAX stdlib rounds timestamps instead
   508  		hdr.Format = tar.FormatPAX
   509  		hdr.ModTime = hdr.ModTime.Truncate(time.Second)
   510  		hdr.AccessTime = time.Time{}
   511  		hdr.ChangeTime = time.Time{}
   512  
   513  		name := p
   514  		if strings.HasPrefix(name, string(filepath.Separator)) {
   515  			name, err = filepath.Rel(string(filepath.Separator), name)
   516  			if err != nil {
   517  				return errors.Wrap(err, "failed to make path relative")
   518  			}
   519  		}
   520  		name, err = tarName(name)
   521  		if err != nil {
   522  			return errors.Wrap(err, "cannot canonicalize path")
   523  		}
   524  		// suffix with '/' for directories
   525  		if f.IsDir() && !strings.HasSuffix(name, "/") {
   526  			name += "/"
   527  		}
   528  		hdr.Name = name
   529  
   530  		if err := setHeaderForSpecialDevice(hdr, name, f); err != nil {
   531  			return errors.Wrap(err, "failed to set device headers")
   532  		}
   533  
   534  		// additionalLinks stores file names which must be linked to
   535  		// this file when this file is added
   536  		var additionalLinks []string
   537  		inode, isHardlink := fs.GetLinkInfo(f)
   538  		if isHardlink {
   539  			// If the inode has a source, always link to it
   540  			if source, ok := cw.inodeSrc[inode]; ok {
   541  				hdr.Typeflag = tar.TypeLink
   542  				hdr.Linkname = source
   543  				hdr.Size = 0
   544  			} else {
   545  				if k == fs.ChangeKindUnmodified {
   546  					cw.inodeRefs[inode] = append(cw.inodeRefs[inode], name)
   547  					return nil
   548  				}
   549  				cw.inodeSrc[inode] = name
   550  				additionalLinks = cw.inodeRefs[inode]
   551  				delete(cw.inodeRefs, inode)
   552  			}
   553  		} else if k == fs.ChangeKindUnmodified {
   554  			// Nothing to write to diff
   555  			return nil
   556  		}
   557  
   558  		if capability, err := getxattr(source, "security.capability"); err != nil {
   559  			return errors.Wrap(err, "failed to get capabilities xattr")
   560  		} else if capability != nil {
   561  			if hdr.PAXRecords == nil {
   562  				hdr.PAXRecords = map[string]string{}
   563  			}
   564  			hdr.PAXRecords[paxSchilyXattr+"security.capability"] = string(capability)
   565  		}
   566  
   567  		if err := cw.includeParents(hdr); err != nil {
   568  			return err
   569  		}
   570  		if err := cw.tw.WriteHeader(hdr); err != nil {
   571  			return errors.Wrap(err, "failed to write file header")
   572  		}
   573  
   574  		if hdr.Typeflag == tar.TypeReg && hdr.Size > 0 {
   575  			file, err := open(source)
   576  			if err != nil {
   577  				return errors.Wrapf(err, "failed to open path: %v", source)
   578  			}
   579  			defer file.Close()
   580  
   581  			n, err := copyBuffered(context.TODO(), cw.tw, file)
   582  			if err != nil {
   583  				return errors.Wrap(err, "failed to copy")
   584  			}
   585  			if n != hdr.Size {
   586  				return errors.New("short write copying file")
   587  			}
   588  		}
   589  
   590  		if additionalLinks != nil {
   591  			source = hdr.Name
   592  			for _, extra := range additionalLinks {
   593  				hdr.Name = extra
   594  				hdr.Typeflag = tar.TypeLink
   595  				hdr.Linkname = source
   596  				hdr.Size = 0
   597  
   598  				if err := cw.includeParents(hdr); err != nil {
   599  					return err
   600  				}
   601  				if err := cw.tw.WriteHeader(hdr); err != nil {
   602  					return errors.Wrap(err, "failed to write file header")
   603  				}
   604  			}
   605  		}
   606  	}
   607  	return nil
   608  }
   609  
   610  func (cw *changeWriter) Close() error {
   611  	if err := cw.tw.Close(); err != nil {
   612  		return errors.Wrap(err, "failed to close tar writer")
   613  	}
   614  	return nil
   615  }
   616  
   617  func (cw *changeWriter) includeParents(hdr *tar.Header) error {
   618  	if cw.addedDirs == nil {
   619  		return nil
   620  	}
   621  	name := strings.TrimRight(hdr.Name, "/")
   622  	fname := filepath.Join(cw.source, name)
   623  	parent := filepath.Dir(name)
   624  	pname := filepath.Join(cw.source, parent)
   625  
   626  	// Do not include root directory as parent
   627  	if fname != cw.source && pname != cw.source {
   628  		_, ok := cw.addedDirs[parent]
   629  		if !ok {
   630  			cw.addedDirs[parent] = struct{}{}
   631  			fi, err := os.Stat(pname)
   632  			if err != nil {
   633  				return err
   634  			}
   635  			if err := cw.HandleChange(fs.ChangeKindModify, parent, fi, nil); err != nil {
   636  				return err
   637  			}
   638  		}
   639  	}
   640  	if hdr.Typeflag == tar.TypeDir {
   641  		cw.addedDirs[name] = struct{}{}
   642  	}
   643  	return nil
   644  }
   645  
   646  func copyBuffered(ctx context.Context, dst io.Writer, src io.Reader) (written int64, err error) {
   647  	buf := bufPool.Get().(*[]byte)
   648  	defer bufPool.Put(buf)
   649  
   650  	for {
   651  		select {
   652  		case <-ctx.Done():
   653  			err = ctx.Err()
   654  			return
   655  		default:
   656  		}
   657  
   658  		nr, er := src.Read(*buf)
   659  		if nr > 0 {
   660  			nw, ew := dst.Write((*buf)[0:nr])
   661  			if nw > 0 {
   662  				written += int64(nw)
   663  			}
   664  			if ew != nil {
   665  				err = ew
   666  				break
   667  			}
   668  			if nr != nw {
   669  				err = io.ErrShortWrite
   670  				break
   671  			}
   672  		}
   673  		if er != nil {
   674  			if er != io.EOF {
   675  				err = er
   676  			}
   677  			break
   678  		}
   679  	}
   680  	return written, err
   681  
   682  }
   683  
   684  // hardlinkRootPath returns target linkname, evaluating and bounding any
   685  // symlink to the parent directory.
   686  //
   687  // NOTE: Allow hardlink to the softlink, not the real one. For example,
   688  //
   689  //	touch /tmp/zzz
   690  //	ln -s /tmp/zzz /tmp/xxx
   691  //	ln /tmp/xxx /tmp/yyy
   692  //
   693  // /tmp/yyy should be softlink which be same of /tmp/xxx, not /tmp/zzz.
   694  func hardlinkRootPath(root, linkname string) (string, error) {
   695  	ppath, base := filepath.Split(linkname)
   696  	ppath, err := fs.RootPath(root, ppath)
   697  	if err != nil {
   698  		return "", err
   699  	}
   700  
   701  	targetPath := filepath.Join(ppath, base)
   702  	if !strings.HasPrefix(targetPath, root) {
   703  		targetPath = root
   704  	}
   705  	return targetPath, nil
   706  }
   707  
   708  func validateWhiteout(path string) error {
   709  	base := filepath.Base(path)
   710  	dir := filepath.Dir(path)
   711  
   712  	if base == whiteoutOpaqueDir {
   713  		return nil
   714  	}
   715  
   716  	if strings.HasPrefix(base, whiteoutPrefix) {
   717  		originalBase := base[len(whiteoutPrefix):]
   718  		originalPath := filepath.Join(dir, originalBase)
   719  
   720  		// Ensure originalPath is under dir
   721  		if dir[len(dir)-1] != filepath.Separator {
   722  			dir += string(filepath.Separator)
   723  		}
   724  		if !strings.HasPrefix(originalPath, dir) {
   725  			return errors.Wrapf(errInvalidArchive, "invalid whiteout name: %v", base)
   726  		}
   727  	}
   728  	return nil
   729  }