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

     1  // +build windows
     2  
     3  /*
     4     Copyright The containerd Authors.
     5  
     6     Licensed under the Apache License, Version 2.0 (the "License");
     7     you may not use this file except in compliance with the License.
     8     You may obtain a copy of the License at
     9  
    10         http://www.apache.org/licenses/LICENSE-2.0
    11  
    12     Unless required by applicable law or agreed to in writing, software
    13     distributed under the License is distributed on an "AS IS" BASIS,
    14     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15     See the License for the specific language governing permissions and
    16     limitations under the License.
    17  */
    18  
    19  package archive
    20  
    21  import (
    22  	"archive/tar"
    23  	"bufio"
    24  	"context"
    25  	"encoding/base64"
    26  	"fmt"
    27  	"io"
    28  	"os"
    29  	"path"
    30  	"path/filepath"
    31  	"strconv"
    32  	"strings"
    33  	"syscall"
    34  
    35  	"github.com/Microsoft/go-winio"
    36  	"github.com/Microsoft/hcsshim"
    37  	"github.com/containerd/containerd/sys"
    38  	"github.com/pkg/errors"
    39  )
    40  
    41  const (
    42  	// MSWINDOWS pax vendor extensions
    43  	hdrMSWindowsPrefix = "MSWINDOWS."
    44  
    45  	hdrFileAttributes        = hdrMSWindowsPrefix + "fileattr"
    46  	hdrSecurityDescriptor    = hdrMSWindowsPrefix + "sd"
    47  	hdrRawSecurityDescriptor = hdrMSWindowsPrefix + "rawsd"
    48  	hdrMountPoint            = hdrMSWindowsPrefix + "mountpoint"
    49  	hdrEaPrefix              = hdrMSWindowsPrefix + "xattr."
    50  
    51  	// LIBARCHIVE pax vendor extensions
    52  	hdrLibArchivePrefix = "LIBARCHIVE."
    53  
    54  	hdrCreateTime = hdrLibArchivePrefix + "creationtime"
    55  )
    56  
    57  var (
    58  	// mutatedFiles is a list of files that are mutated by the import process
    59  	// and must be backed up and restored.
    60  	mutatedFiles = map[string]string{
    61  		"UtilityVM/Files/EFI/Microsoft/Boot/BCD":      "bcd.bak",
    62  		"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG":  "bcd.log.bak",
    63  		"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG1": "bcd.log1.bak",
    64  		"UtilityVM/Files/EFI/Microsoft/Boot/BCD.LOG2": "bcd.log2.bak",
    65  	}
    66  )
    67  
    68  // tarName returns platform-specific filepath
    69  // to canonical posix-style path for tar archival. p is relative
    70  // path.
    71  func tarName(p string) (string, error) {
    72  	// windows: convert windows style relative path with backslashes
    73  	// into forward slashes. Since windows does not allow '/' or '\'
    74  	// in file names, it is mostly safe to replace however we must
    75  	// check just in case
    76  	if strings.Contains(p, "/") {
    77  		return "", fmt.Errorf("windows path contains forward slash: %s", p)
    78  	}
    79  
    80  	return strings.Replace(p, string(os.PathSeparator), "/", -1), nil
    81  }
    82  
    83  // chmodTarEntry is used to adjust the file permissions used in tar header based
    84  // on the platform the archival is done.
    85  func chmodTarEntry(perm os.FileMode) os.FileMode {
    86  	perm &= 0755
    87  	// Add the x bit: make everything +x from windows
    88  	perm |= 0111
    89  
    90  	return perm
    91  }
    92  
    93  func setHeaderForSpecialDevice(*tar.Header, string, os.FileInfo) error {
    94  	// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
    95  	return nil
    96  }
    97  
    98  func open(p string) (*os.File, error) {
    99  	// We use sys.OpenSequential to ensure we use sequential file
   100  	// access on Windows to avoid depleting the standby list.
   101  	return sys.OpenSequential(p)
   102  }
   103  
   104  func openFile(name string, flag int, perm os.FileMode) (*os.File, error) {
   105  	// Source is regular file. We use sys.OpenFileSequential to use sequential
   106  	// file access to avoid depleting the standby list on Windows.
   107  	return sys.OpenFileSequential(name, flag, perm)
   108  }
   109  
   110  func mkdir(path string, perm os.FileMode) error {
   111  	return os.Mkdir(path, perm)
   112  }
   113  
   114  func skipFile(hdr *tar.Header) bool {
   115  	// Windows does not support filenames with colons in them. Ignore
   116  	// these files. This is not a problem though (although it might
   117  	// appear that it is). Let's suppose a client is running docker pull.
   118  	// The daemon it points to is Windows. Would it make sense for the
   119  	// client to be doing a docker pull Ubuntu for example (which has files
   120  	// with colons in the name under /usr/share/man/man3)? No, absolutely
   121  	// not as it would really only make sense that they were pulling a
   122  	// Windows image. However, for development, it is necessary to be able
   123  	// to pull Linux images which are in the repository.
   124  	//
   125  	// TODO Windows. Once the registry is aware of what images are Windows-
   126  	// specific or Linux-specific, this warning should be changed to an error
   127  	// to cater for the situation where someone does manage to upload a Linux
   128  	// image but have it tagged as Windows inadvertently.
   129  	return strings.Contains(hdr.Name, ":")
   130  }
   131  
   132  // handleTarTypeBlockCharFifo is an OS-specific helper function used by
   133  // createTarFile to handle the following types of header: Block; Char; Fifo
   134  func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
   135  	return nil
   136  }
   137  
   138  func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
   139  	return nil
   140  }
   141  
   142  func getxattr(path, attr string) ([]byte, error) {
   143  	return nil, nil
   144  }
   145  
   146  func setxattr(path, key, value string) error {
   147  	// Return not support error, do not wrap underlying not supported
   148  	// since xattrs should not exist in windows diff archives
   149  	return errors.New("xattrs not supported on Windows")
   150  }
   151  
   152  // applyWindowsLayer applies a tar stream of an OCI style diff tar of a Windows
   153  // layer using the hcsshim layer writer and backup streams.
   154  // See https://github.com/opencontainers/image-spec/blob/master/layer.md#applying-changesets
   155  func applyWindowsLayer(ctx context.Context, root string, tr *tar.Reader, options ApplyOptions) (size int64, err error) {
   156  	home, id := filepath.Split(root)
   157  	info := hcsshim.DriverInfo{
   158  		HomeDir: home,
   159  	}
   160  
   161  	w, err := hcsshim.NewLayerWriter(info, id, options.Parents)
   162  	if err != nil {
   163  		return 0, err
   164  	}
   165  	defer func() {
   166  		if err2 := w.Close(); err2 != nil {
   167  			// This error should not be discarded as a failure here
   168  			// could result in an invalid layer on disk
   169  			if err == nil {
   170  				err = err2
   171  			}
   172  		}
   173  	}()
   174  
   175  	buf := bufio.NewWriter(nil)
   176  	hdr, nextErr := tr.Next()
   177  	// Iterate through the files in the archive.
   178  	for {
   179  		select {
   180  		case <-ctx.Done():
   181  			return 0, ctx.Err()
   182  		default:
   183  		}
   184  
   185  		if nextErr == io.EOF {
   186  			// end of tar archive
   187  			break
   188  		}
   189  		if nextErr != nil {
   190  			return 0, nextErr
   191  		}
   192  
   193  		// Note: path is used instead of filepath to prevent OS specific handling
   194  		// of the tar path
   195  		base := path.Base(hdr.Name)
   196  		if strings.HasPrefix(base, whiteoutPrefix) {
   197  			dir := path.Dir(hdr.Name)
   198  			originalBase := base[len(whiteoutPrefix):]
   199  			originalPath := path.Join(dir, originalBase)
   200  			if err := w.Remove(filepath.FromSlash(originalPath)); err != nil {
   201  				return 0, err
   202  			}
   203  			hdr, nextErr = tr.Next()
   204  		} else if hdr.Typeflag == tar.TypeLink {
   205  			err := w.AddLink(filepath.FromSlash(hdr.Name), filepath.FromSlash(hdr.Linkname))
   206  			if err != nil {
   207  				return 0, err
   208  			}
   209  			hdr, nextErr = tr.Next()
   210  		} else {
   211  			name, fileSize, fileInfo, err := fileInfoFromHeader(hdr)
   212  			if err != nil {
   213  				return 0, err
   214  			}
   215  			if err := w.Add(filepath.FromSlash(name), fileInfo); err != nil {
   216  				return 0, err
   217  			}
   218  			size += fileSize
   219  			hdr, nextErr = tarToBackupStreamWithMutatedFiles(buf, w, tr, hdr, root)
   220  		}
   221  	}
   222  
   223  	return
   224  }
   225  
   226  // fileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
   227  // WriteTarFileFromBackupStream.
   228  func fileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
   229  	name = hdr.Name
   230  	if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
   231  		size = hdr.Size
   232  	}
   233  	fileInfo = &winio.FileBasicInfo{
   234  		LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
   235  		LastWriteTime:  syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
   236  		ChangeTime:     syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
   237  
   238  		// Default CreationTime to ModTime, updated below if MSWINDOWS.createtime exists
   239  		CreationTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
   240  	}
   241  	if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
   242  		attr, err := strconv.ParseUint(attrStr, 10, 32)
   243  		if err != nil {
   244  			return "", 0, nil, err
   245  		}
   246  		fileInfo.FileAttributes = uint32(attr)
   247  	} else {
   248  		if hdr.Typeflag == tar.TypeDir {
   249  			fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
   250  		}
   251  	}
   252  	if createStr, ok := hdr.PAXRecords[hdrCreateTime]; ok {
   253  		createTime, err := parsePAXTime(createStr)
   254  		if err != nil {
   255  			return "", 0, nil, err
   256  		}
   257  		fileInfo.CreationTime = syscall.NsecToFiletime(createTime.UnixNano())
   258  	}
   259  	return
   260  }
   261  
   262  // tarToBackupStreamWithMutatedFiles reads data from a tar stream and
   263  // writes it to a backup stream, and also saves any files that will be mutated
   264  // by the import layer process to a backup location.
   265  func tarToBackupStreamWithMutatedFiles(buf *bufio.Writer, w io.Writer, t *tar.Reader, hdr *tar.Header, root string) (nextHdr *tar.Header, err error) {
   266  	var (
   267  		bcdBackup       *os.File
   268  		bcdBackupWriter *winio.BackupFileWriter
   269  	)
   270  	if backupPath, ok := mutatedFiles[hdr.Name]; ok {
   271  		bcdBackup, err = os.Create(filepath.Join(root, backupPath))
   272  		if err != nil {
   273  			return nil, err
   274  		}
   275  		defer func() {
   276  			cerr := bcdBackup.Close()
   277  			if err == nil {
   278  				err = cerr
   279  			}
   280  		}()
   281  
   282  		bcdBackupWriter = winio.NewBackupFileWriter(bcdBackup, false)
   283  		defer func() {
   284  			cerr := bcdBackupWriter.Close()
   285  			if err == nil {
   286  				err = cerr
   287  			}
   288  		}()
   289  
   290  		buf.Reset(io.MultiWriter(w, bcdBackupWriter))
   291  	} else {
   292  		buf.Reset(w)
   293  	}
   294  
   295  	defer func() {
   296  		ferr := buf.Flush()
   297  		if err == nil {
   298  			err = ferr
   299  		}
   300  	}()
   301  
   302  	return writeBackupStreamFromTarFile(buf, t, hdr)
   303  }
   304  
   305  // writeBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
   306  // tar file entries in order to collect all the alternate data streams for the file, it returns the next
   307  // tar file that was not processed, or io.EOF is there are no more.
   308  func writeBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
   309  	bw := winio.NewBackupStreamWriter(w)
   310  	var sd []byte
   311  	var err error
   312  	// Maintaining old SDDL-based behavior for backward compatibility.  All new tar headers written
   313  	// by this library will have raw binary for the security descriptor.
   314  	if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
   315  		sd, err = winio.SddlToSecurityDescriptor(sddl)
   316  		if err != nil {
   317  			return nil, err
   318  		}
   319  	}
   320  	if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
   321  		sd, err = base64.StdEncoding.DecodeString(sdraw)
   322  		if err != nil {
   323  			return nil, err
   324  		}
   325  	}
   326  	if len(sd) != 0 {
   327  		bhdr := winio.BackupHeader{
   328  			Id:   winio.BackupSecurity,
   329  			Size: int64(len(sd)),
   330  		}
   331  		err := bw.WriteHeader(&bhdr)
   332  		if err != nil {
   333  			return nil, err
   334  		}
   335  		_, err = bw.Write(sd)
   336  		if err != nil {
   337  			return nil, err
   338  		}
   339  	}
   340  	var eas []winio.ExtendedAttribute
   341  	for k, v := range hdr.PAXRecords {
   342  		if !strings.HasPrefix(k, hdrEaPrefix) {
   343  			continue
   344  		}
   345  		data, err := base64.StdEncoding.DecodeString(v)
   346  		if err != nil {
   347  			return nil, err
   348  		}
   349  		eas = append(eas, winio.ExtendedAttribute{
   350  			Name:  k[len(hdrEaPrefix):],
   351  			Value: data,
   352  		})
   353  	}
   354  	if len(eas) != 0 {
   355  		eadata, err := winio.EncodeExtendedAttributes(eas)
   356  		if err != nil {
   357  			return nil, err
   358  		}
   359  		bhdr := winio.BackupHeader{
   360  			Id:   winio.BackupEaData,
   361  			Size: int64(len(eadata)),
   362  		}
   363  		err = bw.WriteHeader(&bhdr)
   364  		if err != nil {
   365  			return nil, err
   366  		}
   367  		_, err = bw.Write(eadata)
   368  		if err != nil {
   369  			return nil, err
   370  		}
   371  	}
   372  	if hdr.Typeflag == tar.TypeSymlink {
   373  		_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
   374  		rp := winio.ReparsePoint{
   375  			Target:       filepath.FromSlash(hdr.Linkname),
   376  			IsMountPoint: isMountPoint,
   377  		}
   378  		reparse := winio.EncodeReparsePoint(&rp)
   379  		bhdr := winio.BackupHeader{
   380  			Id:   winio.BackupReparseData,
   381  			Size: int64(len(reparse)),
   382  		}
   383  		err := bw.WriteHeader(&bhdr)
   384  		if err != nil {
   385  			return nil, err
   386  		}
   387  		_, err = bw.Write(reparse)
   388  		if err != nil {
   389  			return nil, err
   390  		}
   391  	}
   392  
   393  	buf := bufPool.Get().(*[]byte)
   394  	defer bufPool.Put(buf)
   395  
   396  	if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
   397  		bhdr := winio.BackupHeader{
   398  			Id:   winio.BackupData,
   399  			Size: hdr.Size,
   400  		}
   401  		err := bw.WriteHeader(&bhdr)
   402  		if err != nil {
   403  			return nil, err
   404  		}
   405  		_, err = io.CopyBuffer(bw, t, *buf)
   406  		if err != nil {
   407  			return nil, err
   408  		}
   409  	}
   410  	// Copy all the alternate data streams and return the next non-ADS header.
   411  	for {
   412  		ahdr, err := t.Next()
   413  		if err != nil {
   414  			return nil, err
   415  		}
   416  		if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
   417  			return ahdr, nil
   418  		}
   419  		bhdr := winio.BackupHeader{
   420  			Id:   winio.BackupAlternateData,
   421  			Size: ahdr.Size,
   422  			Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
   423  		}
   424  		err = bw.WriteHeader(&bhdr)
   425  		if err != nil {
   426  			return nil, err
   427  		}
   428  		_, err = io.CopyBuffer(bw, t, *buf)
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  	}
   433  }
   434  
   435  func copyDirInfo(fi os.FileInfo, path string) error {
   436  	if err := os.Chmod(path, fi.Mode()); err != nil {
   437  		return errors.Wrapf(err, "failed to chmod %s", path)
   438  	}
   439  	return nil
   440  }
   441  
   442  func copyUpXAttrs(dst, src string) error {
   443  	return nil
   444  }