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 }