github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/pkg/system/filesys_windows.go (about) 1 // +build windows 2 3 package system 4 5 import ( 6 "os" 7 "path/filepath" 8 "regexp" 9 "strconv" 10 "strings" 11 "sync" 12 "syscall" 13 "time" 14 "unsafe" 15 16 winio "github.com/Microsoft/go-winio" 17 ) 18 19 // MkdirAllWithACL is a wrapper for MkdirAll that creates a directory 20 // ACL'd for Builtin Administrators and Local System. 21 func MkdirAllWithACL(path string, perm os.FileMode) error { 22 return mkdirall(path, true) 23 } 24 25 // MkdirAll implementation that is volume path aware for Windows. 26 func MkdirAll(path string, _ os.FileMode) error { 27 return mkdirall(path, false) 28 } 29 30 // mkdirall is a custom version of os.MkdirAll modified for use on Windows 31 // so that it is both volume path aware, and can create a directory with 32 // a DACL. 33 func mkdirall(path string, adminAndLocalSystem bool) error { 34 if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) { 35 return nil 36 } 37 38 // The rest of this method is largely copied from os.MkdirAll and should be kept 39 // as-is to ensure compatibility. 40 41 // Fast path: if we can tell whether path is a directory or file, stop with success or error. 42 dir, err := os.Stat(path) 43 if err == nil { 44 if dir.IsDir() { 45 return nil 46 } 47 return &os.PathError{ 48 Op: "mkdir", 49 Path: path, 50 Err: syscall.ENOTDIR, 51 } 52 } 53 54 // Slow path: make sure parent exists and then call Mkdir for path. 55 i := len(path) 56 for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. 57 i-- 58 } 59 60 j := i 61 for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. 62 j-- 63 } 64 65 if j > 1 { 66 // Create parent 67 err = mkdirall(path[0:j-1], false) 68 if err != nil { 69 return err 70 } 71 } 72 73 // Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result. 74 if adminAndLocalSystem { 75 err = mkdirWithACL(path) 76 } else { 77 err = os.Mkdir(path, 0) 78 } 79 80 if err != nil { 81 // Handle arguments like "foo/." by 82 // double-checking that directory doesn't exist. 83 dir, err1 := os.Lstat(path) 84 if err1 == nil && dir.IsDir() { 85 return nil 86 } 87 return err 88 } 89 return nil 90 } 91 92 // mkdirWithACL creates a new directory. If there is an error, it will be of 93 // type *PathError. . 94 // 95 // This is a modified and combined version of os.Mkdir and syscall.Mkdir 96 // in golang to cater for creating a directory am ACL permitting full 97 // access, with inheritance, to any subfolder/file for Built-in Administrators 98 // and Local System. 99 func mkdirWithACL(name string) error { 100 sa := syscall.SecurityAttributes{Length: 0} 101 sddl := "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)" 102 sd, err := winio.SddlToSecurityDescriptor(sddl) 103 if err != nil { 104 return &os.PathError{Op: "mkdir", Path: name, Err: err} 105 } 106 sa.Length = uint32(unsafe.Sizeof(sa)) 107 sa.InheritHandle = 1 108 sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0])) 109 110 namep, err := syscall.UTF16PtrFromString(name) 111 if err != nil { 112 return &os.PathError{Op: "mkdir", Path: name, Err: err} 113 } 114 115 e := syscall.CreateDirectory(namep, &sa) 116 if e != nil { 117 return &os.PathError{Op: "mkdir", Path: name, Err: e} 118 } 119 return nil 120 } 121 122 // IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows, 123 // golang filepath.IsAbs does not consider a path \windows\system32 as absolute 124 // as it doesn't start with a drive-letter/colon combination. However, in 125 // docker we need to verify things such as WORKDIR /windows/system32 in 126 // a Dockerfile (which gets translated to \windows\system32 when being processed 127 // by the daemon. This SHOULD be treated as absolute from a docker processing 128 // perspective. 129 func IsAbs(path string) bool { 130 if !filepath.IsAbs(path) { 131 if !strings.HasPrefix(path, string(os.PathSeparator)) { 132 return false 133 } 134 } 135 return true 136 } 137 138 // The origin of the functions below here are the golang OS and syscall packages, 139 // slightly modified to only cope with files, not directories due to the 140 // specific use case. 141 // 142 // The alteration is to allow a file on Windows to be opened with 143 // FILE_FLAG_SEQUENTIAL_SCAN (particular for docker load), to avoid eating 144 // the standby list, particularly when accessing large files such as layer.tar. 145 146 // CreateSequential creates the named file with mode 0666 (before umask), truncating 147 // it if it already exists. If successful, methods on the returned 148 // File can be used for I/O; the associated file descriptor has mode 149 // O_RDWR. 150 // If there is an error, it will be of type *PathError. 151 func CreateSequential(name string) (*os.File, error) { 152 return OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0) 153 } 154 155 // OpenSequential opens the named file for reading. If successful, methods on 156 // the returned file can be used for reading; the associated file 157 // descriptor has mode O_RDONLY. 158 // If there is an error, it will be of type *PathError. 159 func OpenSequential(name string) (*os.File, error) { 160 return OpenFileSequential(name, os.O_RDONLY, 0) 161 } 162 163 // OpenFileSequential is the generalized open call; most users will use Open 164 // or Create instead. 165 // If there is an error, it will be of type *PathError. 166 func OpenFileSequential(name string, flag int, _ os.FileMode) (*os.File, error) { 167 if name == "" { 168 return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT} 169 } 170 r, errf := syscallOpenFileSequential(name, flag, 0) 171 if errf == nil { 172 return r, nil 173 } 174 return nil, &os.PathError{Op: "open", Path: name, Err: errf} 175 } 176 177 func syscallOpenFileSequential(name string, flag int, _ os.FileMode) (file *os.File, err error) { 178 r, e := syscallOpenSequential(name, flag|syscall.O_CLOEXEC, 0) 179 if e != nil { 180 return nil, e 181 } 182 return os.NewFile(uintptr(r), name), nil 183 } 184 185 func makeInheritSa() *syscall.SecurityAttributes { 186 var sa syscall.SecurityAttributes 187 sa.Length = uint32(unsafe.Sizeof(sa)) 188 sa.InheritHandle = 1 189 return &sa 190 } 191 192 func syscallOpenSequential(path string, mode int, _ uint32) (fd syscall.Handle, err error) { 193 if len(path) == 0 { 194 return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND 195 } 196 pathp, err := syscall.UTF16PtrFromString(path) 197 if err != nil { 198 return syscall.InvalidHandle, err 199 } 200 var access uint32 201 switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) { 202 case syscall.O_RDONLY: 203 access = syscall.GENERIC_READ 204 case syscall.O_WRONLY: 205 access = syscall.GENERIC_WRITE 206 case syscall.O_RDWR: 207 access = syscall.GENERIC_READ | syscall.GENERIC_WRITE 208 } 209 if mode&syscall.O_CREAT != 0 { 210 access |= syscall.GENERIC_WRITE 211 } 212 if mode&syscall.O_APPEND != 0 { 213 access &^= syscall.GENERIC_WRITE 214 access |= syscall.FILE_APPEND_DATA 215 } 216 sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE) 217 var sa *syscall.SecurityAttributes 218 if mode&syscall.O_CLOEXEC == 0 { 219 sa = makeInheritSa() 220 } 221 var createmode uint32 222 switch { 223 case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL): 224 createmode = syscall.CREATE_NEW 225 case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC): 226 createmode = syscall.CREATE_ALWAYS 227 case mode&syscall.O_CREAT == syscall.O_CREAT: 228 createmode = syscall.OPEN_ALWAYS 229 case mode&syscall.O_TRUNC == syscall.O_TRUNC: 230 createmode = syscall.TRUNCATE_EXISTING 231 default: 232 createmode = syscall.OPEN_EXISTING 233 } 234 // Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang. 235 //https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx 236 const fileFlagSequentialScan = 0x08000000 // FILE_FLAG_SEQUENTIAL_SCAN 237 h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0) 238 return h, e 239 } 240 241 // Helpers for TempFileSequential 242 var rand uint32 243 var randmu sync.Mutex 244 245 func reseed() uint32 { 246 return uint32(time.Now().UnixNano() + int64(os.Getpid())) 247 } 248 func nextSuffix() string { 249 randmu.Lock() 250 r := rand 251 if r == 0 { 252 r = reseed() 253 } 254 r = r*1664525 + 1013904223 // constants from Numerical Recipes 255 rand = r 256 randmu.Unlock() 257 return strconv.Itoa(int(1e9 + r%1e9))[1:] 258 } 259 260 // TempFileSequential is a copy of ioutil.TempFile, modified to use sequential 261 // file access. Below is the original comment from golang: 262 // TempFile creates a new temporary file in the directory dir 263 // with a name beginning with prefix, opens the file for reading 264 // and writing, and returns the resulting *os.File. 265 // If dir is the empty string, TempFile uses the default directory 266 // for temporary files (see os.TempDir). 267 // Multiple programs calling TempFile simultaneously 268 // will not choose the same file. The caller can use f.Name() 269 // to find the pathname of the file. It is the caller's responsibility 270 // to remove the file when no longer needed. 271 func TempFileSequential(dir, prefix string) (f *os.File, err error) { 272 if dir == "" { 273 dir = os.TempDir() 274 } 275 276 nconflict := 0 277 for i := 0; i < 10000; i++ { 278 name := filepath.Join(dir, prefix+nextSuffix()) 279 f, err = OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) 280 if os.IsExist(err) { 281 if nconflict++; nconflict > 10 { 282 randmu.Lock() 283 rand = reseed() 284 randmu.Unlock() 285 } 286 continue 287 } 288 break 289 } 290 return 291 }