1
2
3 package safefile
4
5 import (
6 "errors"
7 "io"
8 "os"
9 "path/filepath"
10 "strings"
11 "syscall"
12 "unicode/utf16"
13 "unsafe"
14
15 "github.com/Microsoft/hcsshim/internal/longpath"
16 "github.com/Microsoft/hcsshim/internal/winapi"
17
18 winio "github.com/Microsoft/go-winio"
19 )
20
21 func OpenRoot(path string) (*os.File, error) {
22 longpath, err := longpath.LongAbs(path)
23 if err != nil {
24 return nil, err
25 }
26 return winio.OpenForBackup(longpath, syscall.GENERIC_READ, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, syscall.OPEN_EXISTING)
27 }
28
29 func cleanGoStringRelativePath(path string) (string, error) {
30 path = filepath.Clean(path)
31 if strings.Contains(path, ":") {
32
33
34 return "", errors.New("path contains invalid character `:`")
35 }
36 fspath := filepath.FromSlash(path)
37 if len(fspath) > 0 && fspath[0] == '\\' {
38 return "", errors.New("expected relative path")
39 }
40 return fspath, nil
41 }
42
43 func ntRelativePath(path string) ([]uint16, error) {
44 fspath, err := cleanGoStringRelativePath(path)
45 if err != nil {
46 return nil, err
47 }
48
49 path16 := utf16.Encode(([]rune)(fspath))
50 if len(path16) > 32767 {
51 return nil, syscall.ENAMETOOLONG
52 }
53
54 return path16, nil
55 }
56
57
58
59 func openRelativeInternal(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) {
60 var (
61 h uintptr
62 iosb winapi.IOStatusBlock
63 oa winapi.ObjectAttributes
64 )
65
66 cleanRelativePath, err := cleanGoStringRelativePath(path)
67 if err != nil {
68 return nil, err
69 }
70
71 if root == nil || root.Fd() == 0 {
72 return nil, errors.New("missing root directory")
73 }
74
75 pathUnicode, err := winapi.NewUnicodeString(cleanRelativePath)
76 if err != nil {
77 return nil, err
78 }
79
80 oa.Length = unsafe.Sizeof(oa)
81 oa.ObjectName = pathUnicode
82 oa.RootDirectory = uintptr(root.Fd())
83 oa.Attributes = winapi.OBJ_DONT_REPARSE
84 status := winapi.NtCreateFile(
85 &h,
86 accessMask|syscall.SYNCHRONIZE,
87 &oa,
88 &iosb,
89 nil,
90 0,
91 shareFlags,
92 createDisposition,
93 winapi.FILE_OPEN_FOR_BACKUP_INTENT|winapi.FILE_SYNCHRONOUS_IO_NONALERT|flags,
94 nil,
95 0,
96 )
97 if status != 0 {
98 return nil, winapi.RtlNtStatusToDosError(status)
99 }
100
101 fullPath, err := longpath.LongAbs(filepath.Join(root.Name(), path))
102 if err != nil {
103 syscall.Close(syscall.Handle(h))
104 return nil, err
105 }
106
107 return os.NewFile(h, fullPath), nil
108 }
109
110
111
112 func OpenRelative(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) {
113 f, err := openRelativeInternal(path, root, accessMask, shareFlags, createDisposition, flags)
114 if err != nil {
115 err = &os.PathError{Op: "open", Path: filepath.Join(root.Name(), path), Err: err}
116 }
117 return f, err
118 }
119
120
121
122
123 func LinkRelative(oldname string, oldroot *os.File, newname string, newroot *os.File) error {
124
125 oldf, err := openRelativeInternal(
126 oldname,
127 oldroot,
128 syscall.FILE_WRITE_ATTRIBUTES,
129 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
130 winapi.FILE_OPEN,
131 0,
132 )
133 if err != nil {
134 return &os.LinkError{Op: "link", Old: filepath.Join(oldroot.Name(), oldname), New: filepath.Join(newroot.Name(), newname), Err: err}
135 }
136 defer oldf.Close()
137
138
139 var parent *os.File
140 parentPath := filepath.Dir(newname)
141 if parentPath != "." {
142 parent, err = openRelativeInternal(
143 parentPath,
144 newroot,
145 syscall.GENERIC_READ,
146 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
147 winapi.FILE_OPEN,
148 winapi.FILE_DIRECTORY_FILE)
149 if err != nil {
150 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: err}
151 }
152 defer parent.Close()
153
154 fi, err := winio.GetFileBasicInfo(parent)
155 if err != nil {
156 return err
157 }
158 if (fi.FileAttributes & syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 {
159 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: winapi.RtlNtStatusToDosError(winapi.STATUS_REPARSE_POINT_ENCOUNTERED)}
160 }
161 } else {
162 parent = newroot
163 }
164
165
166
167
168 newbase := filepath.Base(newname)
169 newbase16, err := ntRelativePath(newbase)
170 if err != nil {
171 return err
172 }
173
174 size := int(unsafe.Offsetof(winapi.FileLinkInformation{}.FileName)) + len(newbase16)*2
175 linkinfoBuffer := winapi.LocalAlloc(0, size)
176 defer winapi.LocalFree(linkinfoBuffer)
177
178 linkinfo := (*winapi.FileLinkInformation)(unsafe.Pointer(linkinfoBuffer))
179 linkinfo.RootDirectory = parent.Fd()
180 linkinfo.FileNameLength = uint32(len(newbase16) * 2)
181 copy(winapi.Uint16BufferToSlice(&linkinfo.FileName[0], len(newbase16)), newbase16)
182
183 var iosb winapi.IOStatusBlock
184 status := winapi.NtSetInformationFile(
185 oldf.Fd(),
186 &iosb,
187 linkinfoBuffer,
188 uint32(size),
189 winapi.FileLinkInformationClass,
190 )
191 if status != 0 {
192 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(parent.Name(), newbase), Err: winapi.RtlNtStatusToDosError(status)}
193 }
194
195 return nil
196 }
197
198
199 func deleteOnClose(f *os.File) error {
200 disposition := winapi.FileDispositionInformationEx{Flags: winapi.FILE_DISPOSITION_DELETE}
201 var iosb winapi.IOStatusBlock
202 status := winapi.NtSetInformationFile(
203 f.Fd(),
204 &iosb,
205 uintptr(unsafe.Pointer(&disposition)),
206 uint32(unsafe.Sizeof(disposition)),
207 winapi.FileDispositionInformationExClass,
208 )
209 if status != 0 {
210 return winapi.RtlNtStatusToDosError(status)
211 }
212 return nil
213 }
214
215
216 func clearReadOnly(f *os.File) error {
217 bi, err := winio.GetFileBasicInfo(f)
218 if err != nil {
219 return err
220 }
221 if bi.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY == 0 {
222 return nil
223 }
224 sbi := winio.FileBasicInfo{
225 FileAttributes: bi.FileAttributes &^ syscall.FILE_ATTRIBUTE_READONLY,
226 }
227 if sbi.FileAttributes == 0 {
228 sbi.FileAttributes = syscall.FILE_ATTRIBUTE_NORMAL
229 }
230 return winio.SetFileBasicInfo(f, &sbi)
231 }
232
233
234
235 func RemoveRelative(path string, root *os.File) error {
236 f, err := openRelativeInternal(
237 path,
238 root,
239 winapi.FILE_READ_ATTRIBUTES|winapi.FILE_WRITE_ATTRIBUTES|winapi.DELETE,
240 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
241 winapi.FILE_OPEN,
242 winapi.FILE_OPEN_REPARSE_POINT)
243 if err == nil {
244 defer f.Close()
245 err = deleteOnClose(f)
246 if err == syscall.ERROR_ACCESS_DENIED {
247
248 _ = clearReadOnly(f)
249 err = deleteOnClose(f)
250 }
251 }
252 if err != nil {
253 return &os.PathError{Op: "remove", Path: filepath.Join(root.Name(), path), Err: err}
254 }
255 return nil
256 }
257
258
259
260 func RemoveAllRelative(path string, root *os.File) error {
261 fi, err := LstatRelative(path, root)
262 if err != nil {
263 if os.IsNotExist(err) {
264 return nil
265 }
266 return err
267 }
268 fileAttributes := fi.Sys().(*syscall.Win32FileAttributeData).FileAttributes
269 if fileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 || fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
270
271 err := RemoveRelative(path, root)
272 if err == nil || os.IsNotExist(err) {
273 return nil
274 }
275 return err
276 }
277
278
279
280
281
282 fd, err := os.Open(filepath.Join(root.Name(), path))
283 if err != nil {
284 if os.IsNotExist(err) {
285
286
287 return nil
288 }
289 return err
290 }
291
292
293 for {
294 names, err1 := fd.Readdirnames(100)
295 for _, name := range names {
296 err1 := RemoveAllRelative(path+string(os.PathSeparator)+name, root)
297 if err == nil {
298 err = err1
299 }
300 }
301 if err1 == io.EOF {
302 break
303 }
304
305 if err == nil {
306 err = err1
307 }
308 if len(names) == 0 {
309 break
310 }
311 }
312 fd.Close()
313
314
315 err1 := RemoveRelative(path, root)
316 if err1 == nil || os.IsNotExist(err1) {
317 return nil
318 }
319 if err == nil {
320 err = err1
321 }
322 return err
323 }
324
325
326
327 func MkdirRelative(path string, root *os.File) error {
328 f, err := openRelativeInternal(
329 path,
330 root,
331 0,
332 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
333 winapi.FILE_CREATE,
334 winapi.FILE_DIRECTORY_FILE)
335 if err == nil {
336 f.Close()
337 } else {
338 err = &os.PathError{Op: "mkdir", Path: filepath.Join(root.Name(), path), Err: err}
339 }
340 return err
341 }
342
343
344
345 func MkdirAllRelative(path string, root *os.File) error {
346 pathParts := strings.Split(filepath.Clean(path), (string)(filepath.Separator))
347 for index := range pathParts {
348 partialPath := filepath.Join(pathParts[0 : index+1]...)
349 stat, err := LstatRelative(partialPath, root)
350
351 if err != nil {
352 if os.IsNotExist(err) {
353 if err := MkdirRelative(partialPath, root); err != nil {
354 return err
355 }
356 continue
357 }
358 return err
359 }
360
361 if !stat.IsDir() {
362 fullPath := filepath.Join(root.Name(), partialPath)
363 return &os.PathError{Op: "mkdir", Path: fullPath, Err: syscall.ENOTDIR}
364 }
365 }
366
367 return nil
368 }
369
370
371
372 func LstatRelative(path string, root *os.File) (os.FileInfo, error) {
373 f, err := openRelativeInternal(
374 path,
375 root,
376 winapi.FILE_READ_ATTRIBUTES,
377 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
378 winapi.FILE_OPEN,
379 winapi.FILE_OPEN_REPARSE_POINT)
380 if err != nil {
381 return nil, &os.PathError{Op: "stat", Path: filepath.Join(root.Name(), path), Err: err}
382 }
383 defer f.Close()
384 return f.Stat()
385 }
386
387
388
389 func EnsureNotReparsePointRelative(path string, root *os.File) error {
390
391 f, err := OpenRelative(
392 path,
393 root,
394 0,
395 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
396 winapi.FILE_OPEN,
397 0)
398 if err != nil {
399 return err
400 }
401 f.Close()
402 return nil
403 }
404
View as plain text