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
19package sys
20
21import (
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
33const (
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.
40func 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()
46func 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.
53func 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.
119func 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.
148func 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.
170func 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.
178func 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.
185func 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
196func 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
204func makeInheritSa() *windows.SecurityAttributes {
205	var sa windows.SecurityAttributes
206	sa.Length = uint32(unsafe.Sizeof(sa))
207	sa.InheritHandle = 1
208	return &sa
209}
210
211func 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.
262func ForceRemoveAll(path string) error {
263	info := hcsshim.DriverInfo{
264		HomeDir: filepath.Dir(path),
265	}
266
267	return hcsshim.DestroyLayer(info, filepath.Base(path))
268}
269