github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/sys/filesys_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 sys
    20  
    21  import (
    22  	"os"
    23  	"path/filepath"
    24  	"regexp"
    25  	"strings"
    26  	"syscall"
    27  	"unsafe"
    28  
    29  	"github.com/Microsoft/hcsshim"
    30  	"golang.org/x/sys/windows"
    31  )
    32  
    33  const (
    34  	// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System
    35  	SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
    36  )
    37  
    38  // MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
    39  // ACL'd for Builtin Administrators and Local System.
    40  func MkdirAllWithACL(path string, perm os.FileMode) error {
    41  	return mkdirall(path, true)
    42  }
    43  
    44  // MkdirAll implementation that is volume path aware for Windows. It can be used
    45  // as a drop-in replacement for os.MkdirAll()
    46  func MkdirAll(path string, _ os.FileMode) error {
    47  	return mkdirall(path, false)
    48  }
    49  
    50  // mkdirall is a custom version of os.MkdirAll modified for use on Windows
    51  // so that it is both volume path aware, and can create a directory with
    52  // a DACL.
    53  func mkdirall(path string, adminAndLocalSystem bool) error {
    54  	if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
    55  		return nil
    56  	}
    57  
    58  	// The rest of this method is largely copied from os.MkdirAll and should be kept
    59  	// as-is to ensure compatibility.
    60  
    61  	// Fast path: if we can tell whether path is a directory or file, stop with success or error.
    62  	dir, err := os.Stat(path)
    63  	if err == nil {
    64  		if dir.IsDir() {
    65  			return nil
    66  		}
    67  		return &os.PathError{
    68  			Op:   "mkdir",
    69  			Path: path,
    70  			Err:  syscall.ENOTDIR,
    71  		}
    72  	}
    73  
    74  	// Slow path: make sure parent exists and then call Mkdir for path.
    75  	i := len(path)
    76  	for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
    77  		i--
    78  	}
    79  
    80  	j := i
    81  	for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
    82  		j--
    83  	}
    84  
    85  	if j > 1 {
    86  		// Create parent
    87  		err = mkdirall(path[0:j-1], adminAndLocalSystem)
    88  		if err != nil {
    89  			return err
    90  		}
    91  	}
    92  
    93  	// Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result.
    94  	if adminAndLocalSystem {
    95  		err = mkdirWithACL(path)
    96  	} else {
    97  		err = os.Mkdir(path, 0)
    98  	}
    99  
   100  	if err != nil {
   101  		// Handle arguments like "foo/." by
   102  		// double-checking that directory doesn't exist.
   103  		dir, err1 := os.Lstat(path)
   104  		if err1 == nil && dir.IsDir() {
   105  			return nil
   106  		}
   107  		return err
   108  	}
   109  	return nil
   110  }
   111  
   112  // mkdirWithACL creates a new directory. If there is an error, it will be of
   113  // type *PathError. .
   114  //
   115  // This is a modified and combined version of os.Mkdir and windows.Mkdir
   116  // in golang to cater for creating a directory am ACL permitting full
   117  // access, with inheritance, to any subfolder/file for Built-in Administrators
   118  // and Local System.
   119  func mkdirWithACL(name string) error {
   120  	sa := windows.SecurityAttributes{Length: 0}
   121  	sd, err := windows.SecurityDescriptorFromString(SddlAdministratorsLocalSystem)
   122  	if err != nil {
   123  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   124  	}
   125  	sa.Length = uint32(unsafe.Sizeof(sa))
   126  	sa.InheritHandle = 1
   127  	sa.SecurityDescriptor = sd
   128  
   129  	namep, err := windows.UTF16PtrFromString(name)
   130  	if err != nil {
   131  		return &os.PathError{Op: "mkdir", Path: name, Err: err}
   132  	}
   133  
   134  	e := windows.CreateDirectory(namep, &sa)
   135  	if e != nil {
   136  		return &os.PathError{Op: "mkdir", Path: name, Err: e}
   137  	}
   138  	return nil
   139  }
   140  
   141  // IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows,
   142  // golang filepath.IsAbs does not consider a path \windows\system32 as absolute
   143  // as it doesn't start with a drive-letter/colon combination. However, in
   144  // docker we need to verify things such as WORKDIR /windows/system32 in
   145  // a Dockerfile (which gets translated to \windows\system32 when being processed
   146  // by the daemon. This SHOULD be treated as absolute from a docker processing
   147  // perspective.
   148  func IsAbs(path string) bool {
   149  	if !filepath.IsAbs(path) {
   150  		if !strings.HasPrefix(path, string(os.PathSeparator)) {
   151  			return false
   152  		}
   153  	}
   154  	return true
   155  }
   156  
   157  // The origin of the functions below here are the golang OS and windows packages,
   158  // slightly modified to only cope with files, not directories due to the
   159  // specific use case.
   160  //
   161  // The alteration is to allow a file on Windows to be opened with
   162  // FILE_FLAG_SEQUENTIAL_SCAN (particular for docker load), to avoid eating
   163  // the standby list, particularly when accessing large files such as layer.tar.
   164  
   165  // CreateSequential creates the named file with mode 0666 (before umask), truncating
   166  // it if it already exists. If successful, methods on the returned
   167  // File can be used for I/O; the associated file descriptor has mode
   168  // O_RDWR.
   169  // If there is an error, it will be of type *PathError.
   170  func CreateSequential(name string) (*os.File, error) {
   171  	return OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0)
   172  }
   173  
   174  // OpenSequential opens the named file for reading. If successful, methods on
   175  // the returned file can be used for reading; the associated file
   176  // descriptor has mode O_RDONLY.
   177  // If there is an error, it will be of type *PathError.
   178  func OpenSequential(name string) (*os.File, error) {
   179  	return OpenFileSequential(name, os.O_RDONLY, 0)
   180  }
   181  
   182  // OpenFileSequential is the generalized open call; most users will use Open
   183  // or Create instead.
   184  // If there is an error, it will be of type *PathError.
   185  func OpenFileSequential(name string, flag int, _ os.FileMode) (*os.File, error) {
   186  	if name == "" {
   187  		return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
   188  	}
   189  	r, errf := windowsOpenFileSequential(name, flag, 0)
   190  	if errf == nil {
   191  		return r, nil
   192  	}
   193  	return nil, &os.PathError{Op: "open", Path: name, Err: errf}
   194  }
   195  
   196  func windowsOpenFileSequential(name string, flag int, _ os.FileMode) (file *os.File, err error) {
   197  	r, e := windowsOpenSequential(name, flag|windows.O_CLOEXEC, 0)
   198  	if e != nil {
   199  		return nil, e
   200  	}
   201  	return os.NewFile(uintptr(r), name), nil
   202  }
   203  
   204  func makeInheritSa() *windows.SecurityAttributes {
   205  	var sa windows.SecurityAttributes
   206  	sa.Length = uint32(unsafe.Sizeof(sa))
   207  	sa.InheritHandle = 1
   208  	return &sa
   209  }
   210  
   211  func windowsOpenSequential(path string, mode int, _ uint32) (fd windows.Handle, err error) {
   212  	if len(path) == 0 {
   213  		return windows.InvalidHandle, windows.ERROR_FILE_NOT_FOUND
   214  	}
   215  	pathp, err := windows.UTF16PtrFromString(path)
   216  	if err != nil {
   217  		return windows.InvalidHandle, err
   218  	}
   219  	var access uint32
   220  	switch mode & (windows.O_RDONLY | windows.O_WRONLY | windows.O_RDWR) {
   221  	case windows.O_RDONLY:
   222  		access = windows.GENERIC_READ
   223  	case windows.O_WRONLY:
   224  		access = windows.GENERIC_WRITE
   225  	case windows.O_RDWR:
   226  		access = windows.GENERIC_READ | windows.GENERIC_WRITE
   227  	}
   228  	if mode&windows.O_CREAT != 0 {
   229  		access |= windows.GENERIC_WRITE
   230  	}
   231  	if mode&windows.O_APPEND != 0 {
   232  		access &^= windows.GENERIC_WRITE
   233  		access |= windows.FILE_APPEND_DATA
   234  	}
   235  	sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE)
   236  	var sa *windows.SecurityAttributes
   237  	if mode&windows.O_CLOEXEC == 0 {
   238  		sa = makeInheritSa()
   239  	}
   240  	var createmode uint32
   241  	switch {
   242  	case mode&(windows.O_CREAT|windows.O_EXCL) == (windows.O_CREAT | windows.O_EXCL):
   243  		createmode = windows.CREATE_NEW
   244  	case mode&(windows.O_CREAT|windows.O_TRUNC) == (windows.O_CREAT | windows.O_TRUNC):
   245  		createmode = windows.CREATE_ALWAYS
   246  	case mode&windows.O_CREAT == windows.O_CREAT:
   247  		createmode = windows.OPEN_ALWAYS
   248  	case mode&windows.O_TRUNC == windows.O_TRUNC:
   249  		createmode = windows.TRUNCATE_EXISTING
   250  	default:
   251  		createmode = windows.OPEN_EXISTING
   252  	}
   253  	// Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang.
   254  	// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
   255  	const fileFlagSequentialScan = 0x08000000 // FILE_FLAG_SEQUENTIAL_SCAN
   256  	h, e := windows.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0)
   257  	return h, e
   258  }
   259  
   260  // ForceRemoveAll is the same as os.RemoveAll, but uses hcsshim.DestroyLayer in order
   261  // to delete container layers.
   262  func ForceRemoveAll(path string) error {
   263  	info := hcsshim.DriverInfo{
   264  		HomeDir: filepath.Dir(path),
   265  	}
   266  
   267  	return hcsshim.DestroyLayer(info, filepath.Base(path))
   268  }