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