filesys_windows.go 7.2 KB

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