1// Copyright 2015-2018 Keybase Inc. All rights reserved.
2// Use of this source code is governed by a BSD
3// license that can be found in the LICENSE file.
4
5// +build windows
6
7package dokan
8
9/*
10#include "bridge.h"
11*/
12import "C"
13
14import (
15	"context"
16	"errors"
17	"fmt"
18	"io"
19	"reflect"
20	"runtime"
21	"syscall"
22	"time"
23	"unicode/utf16"
24	"unsafe"
25
26	"github.com/keybase/client/go/kbfs/dokan/winacl"
27	"golang.org/x/sys/windows"
28)
29
30// SID wraps syscall.SID for users.
31type SID syscall.SID
32
33const (
34	kbfsLibdokanDebug                   = MountFlag(C.kbfsLibdokanDebug)
35	kbfsLibdokanStderr                  = MountFlag(C.kbfsLibdokanStderr)
36	kbfsLibdokanRemovable               = MountFlag(C.kbfsLibdokanRemovable)
37	kbfsLibdokanMountManager            = MountFlag(C.kbfsLibdokanMountManager)
38	kbfsLibdokanCurrentSession          = MountFlag(C.kbfsLibdokanCurrentSession)
39	kbfsLibdokanUseFindFilesWithPattern = MountFlag(C.kbfsLibdokanUseFindFilesWithPattern)
40)
41
42const ntstatusOk = C.NTSTATUS(0)
43
44func checkFileDirectoryFile(err error, isDir bool, createOptions uint32) {
45	if createOptions&FileDirectoryFile != 0 && createOptions&FileNonDirectoryFile != 0 {
46		debugf("checkFileDirectoryFile both FileDirectoryFile FileNonDirectoryFile set")
47	}
48	switch {
49	case err == nil:
50		if (!isDir && createOptions&FileDirectoryFile != 0) ||
51			(isDir && createOptions&FileNonDirectoryFile != 0) {
52			debugf("checkFileDirectoryFile INCONSISTENCY %v %08X", isDir, createOptions)
53		}
54	case err == ErrNotADirectory:
55		if createOptions&FileDirectoryFile == 0 {
56			debugf("checkFileDirectoryFile ErrNotADirectory but no createOptions&FileDirectoryFile")
57		}
58	case err == ErrFileIsADirectory:
59		if createOptions&FileNonDirectoryFile == 0 {
60			debugf("checkFileDirectoryFile ErrFileIsADirectory but no createOptions&FileNonDirectoryFile")
61		}
62	}
63}
64
65//export kbfsLibdokanCreateFile
66func kbfsLibdokanCreateFile(
67	fname C.LPCWSTR,
68	psec C.PDOKAN_IO_SECURITY_CONTEXT,
69	DesiredAccess C.ACCESS_MASK, //nolint
70	FileAttributes C.ULONG, //nolint
71	ShareAccess C.ULONG, //nolint
72	cCreateDisposition C.ULONG,
73	CreateOptions C.ULONG, //nolint
74	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
75	var cd = CreateData{
76		DesiredAccess:     uint32(DesiredAccess),
77		FileAttributes:    FileAttribute(FileAttributes),
78		ShareAccess:       uint32(ShareAccess),
79		CreateDisposition: CreateDisposition(cCreateDisposition),
80		CreateOptions:     uint32(CreateOptions),
81	}
82	debugf("CreateFile '%v' %#v pid: %v\n",
83		d16{fname}, cd, pfi.ProcessId)
84	fs := getfs(pfi)
85	ctx, cancel := fs.WithContext(globalContext())
86	if cancel != nil {
87		defer cancel()
88	}
89	fi, status, err := fs.CreateFile(ctx, makeFI(fname, pfi), &cd)
90	if isDebug {
91		checkFileDirectoryFile(err, status.IsDir(), uint32(CreateOptions))
92		debugf("CreateFile result: %v new-entry: %v raw %v", status.IsDir(), status.IsNew(), status)
93		if err == nil && status&isValid == 0 {
94			debugf("CreateFile invalid status for successful operation!")
95		}
96	}
97	if status.IsDir() {
98		pfi.IsDirectory = 1
99	}
100	if err == nil && !status.IsNew() && cd.CreateDisposition.isSignalExisting() {
101		debugf("CreateFile adding ErrObjectNameCollision")
102		err = ErrObjectNameCollision
103	}
104	return fiStore(pfi, fi, err)
105}
106
107func (cd CreateDisposition) isSignalExisting() bool {
108	return cd == FileOpenIf || cd == FileSupersede || cd == FileOverwriteIf
109}
110
111func globalContext() context.Context {
112	return context.Background()
113}
114func getContext(pfi C.PDOKAN_FILE_INFO) (context.Context, context.CancelFunc) {
115	return getfs(pfi).WithContext(globalContext())
116}
117
118//export kbfsLibdokanCleanup
119func kbfsLibdokanCleanup(fname C.LPCWSTR, pfi C.PDOKAN_FILE_INFO) {
120	debugf("Cleanup '%v' %v\n", d16{fname}, *pfi)
121	ctx, cancel := getContext(pfi)
122	if cancel != nil {
123		defer cancel()
124	}
125	getfi(pfi).Cleanup(ctx, makeFI(fname, pfi))
126}
127
128//export kbfsLibdokanCloseFile
129func kbfsLibdokanCloseFile(fname C.LPCWSTR, pfi C.PDOKAN_FILE_INFO) {
130	debugf("CloseFile '%v' %v\n", d16{fname}, *pfi)
131	ctx, cancel := getContext(pfi)
132	if cancel != nil {
133		defer cancel()
134	}
135	getfi(pfi).CloseFile(ctx, makeFI(fname, pfi))
136	fiTableFreeFile(uint32(pfi.DokanOptions.GlobalContext), uint32(pfi.Context))
137	pfi.Context = 0
138}
139
140//export kbfsLibdokanReadFile
141func kbfsLibdokanReadFile(
142	fname C.LPCWSTR,
143	Buffer C.LPVOID, //nolint
144	NumberOfBytesToRead C.DWORD, //nolint
145	NumberOfBytesRead C.LPDWORD, //nolint
146	Offset C.LONGLONG, //nolint
147	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
148	debugf("ReadFile '%v' %d bytes @ %d %v", d16{fname}, NumberOfBytesToRead, Offset, *pfi)
149	ctx, cancel := getContext(pfi)
150	if cancel != nil {
151		defer cancel()
152	}
153	n, err := getfi(pfi).ReadFile(
154		ctx,
155		makeFI(fname, pfi),
156		bufToSlice(unsafe.Pointer(Buffer), uint32(NumberOfBytesToRead)),
157		int64(Offset))
158	*NumberOfBytesRead = C.DWORD(n)
159	// EOF is success with Windows...
160	if err == io.EOF {
161		err = nil
162	}
163	debug("->", n, err)
164	return errToNT(err)
165}
166
167//export kbfsLibdokanWriteFile
168func kbfsLibdokanWriteFile(
169	fname C.LPCWSTR,
170	Buffer C.LPCVOID, //nolint
171	NumberOfBytesToWrite C.DWORD, //nolint
172	NumberOfBytesWritten C.LPDWORD, //nolint
173	Offset C.LONGLONG, //nolint
174	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
175	debugf("WriteFile '%v' %d bytes @ %d %v", d16{fname}, NumberOfBytesToWrite, Offset, *pfi)
176	ctx, cancel := getContext(pfi)
177	if cancel != nil {
178		defer cancel()
179	}
180	n, err := getfi(pfi).WriteFile(
181		ctx,
182		makeFI(fname, pfi),
183		bufToSlice(unsafe.Pointer(Buffer), uint32(NumberOfBytesToWrite)),
184		int64(Offset))
185	*NumberOfBytesWritten = C.DWORD(n)
186	debug("->", n, err)
187	return errToNT(err)
188}
189
190//export kbfsLibdokanFlushFileBuffers
191func kbfsLibdokanFlushFileBuffers(
192	fname C.LPCWSTR,
193	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
194	debugf("FlushFileBuffers '%v' %v", d16{fname}, *pfi)
195	ctx, cancel := getContext(pfi)
196	if cancel != nil {
197		defer cancel()
198	}
199	err := getfi(pfi).FlushFileBuffers(ctx, makeFI(fname, pfi))
200	return errToNT(err)
201}
202
203func u32zeroToOne(u uint32) uint32 {
204	if u == 0 {
205		return 1
206	}
207	return u
208}
209
210//export kbfsLibdokanGetFileInformation
211func kbfsLibdokanGetFileInformation(
212	fname C.LPCWSTR,
213	sbuf C.LPBY_HANDLE_FILE_INFORMATION,
214	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
215	debugf("GetFileInformation '%v' %v", d16{fname}, *pfi)
216	ctx, cancel := getContext(pfi)
217	if cancel != nil {
218		defer cancel()
219	}
220	st, err := getfi(pfi).GetFileInformation(ctx, makeFI(fname, pfi))
221	debugf("-> %#v, %v", st, err)
222	if st != nil {
223		sbuf.dwFileAttributes = C.DWORD(st.FileAttributes)
224		sbuf.ftCreationTime = packTime(st.Creation)
225		sbuf.ftLastAccessTime = packTime(st.LastAccess)
226		sbuf.ftLastWriteTime = packTime(st.LastWrite)
227		sbuf.dwVolumeSerialNumber = C.DWORD(st.VolumeSerialNumber)
228		sbuf.nFileSizeHigh = C.DWORD(st.FileSize >> 32)
229		sbuf.nFileSizeLow = C.DWORD(st.FileSize)
230		sbuf.nNumberOfLinks = C.DWORD(u32zeroToOne(st.NumberOfLinks))
231		sbuf.nFileIndexHigh = C.DWORD(st.FileIndex >> 32)
232		sbuf.nFileIndexLow = C.DWORD(st.FileIndex)
233	}
234	return errToNT(err)
235}
236
237var errFindNoSpace = errors.New("Find out of space")
238
239//export kbfsLibdokanFindFiles
240func kbfsLibdokanFindFiles(
241	PathName C.LPCWSTR, //nolint
242	FindData C.PFillFindData, //nolint call this function with PWIN32_FIND_DATAW
243	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
244	debugf("FindFiles '%v' %v", d16{PathName}, *pfi)
245	return kbfsLibdokanFindFilesImpl(PathName, "", FindData, pfi)
246}
247
248//export kbfsLibdokanFindFilesWithPattern
249func kbfsLibdokanFindFilesWithPattern(
250	PathName C.LPCWSTR, //nolint
251	SearchPattern C.LPCWSTR, //nolint
252	FindData C.PFillFindData, //nolint call this function with PWIN32_FIND_DATAW
253	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
254	pattern := lpcwstrToString(SearchPattern)
255	debugf("FindFilesWithPattern '%v' %v %q", d16{PathName}, *pfi, pattern)
256	return kbfsLibdokanFindFilesImpl(PathName, pattern, FindData, pfi)
257}
258
259func kbfsLibdokanFindFilesImpl(
260	PathName C.LPCWSTR, //nolint
261	pattern string,
262	FindData C.PFillFindData, //nolint
263	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
264	debugf("FindFiles '%v' %v", d16{PathName}, *pfi)
265	ctx, cancel := getContext(pfi)
266	if cancel != nil {
267		defer cancel()
268	}
269	var fdata C.WIN32_FIND_DATAW
270	fun := func(ns *NamedStat) error {
271		fdata.dwFileAttributes = C.DWORD(ns.FileAttributes)
272		fdata.ftCreationTime = packTime(ns.Creation)
273		fdata.ftLastAccessTime = packTime(ns.LastAccess)
274		fdata.ftLastWriteTime = packTime(ns.LastWrite)
275		fdata.nFileSizeHigh = C.DWORD(ns.FileSize >> 32)
276		fdata.nFileSizeLow = C.DWORD(ns.FileSize)
277		fdata.dwReserved0 = C.DWORD(ns.ReparsePointTag)
278		stringToUtf16BufferPtr(ns.Name,
279			unsafe.Pointer(&fdata.cFileName),
280			C.DWORD(C.MAX_PATH))
281		if ns.ShortName != "" {
282			stringToUtf16BufferPtr(ns.ShortName,
283				unsafe.Pointer(&fdata.cFileName),
284				C.DWORD(14))
285		}
286
287		v := C.kbfsLibdokanFill_find(FindData, &fdata, pfi)
288		if v != 0 {
289			return errFindNoSpace
290		}
291		return nil
292	}
293	err := getfi(pfi).FindFiles(ctx, makeFI(PathName, pfi), pattern, fun)
294	return errToNT(err)
295}
296
297//export kbfsLibdokanSetFileAttributes
298func kbfsLibdokanSetFileAttributes(
299	fname C.LPCWSTR,
300	fileAttributes C.DWORD,
301	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
302	debugf("SetFileAttributes '%v' %X %v", d16{fname}, fileAttributes, pfi)
303	ctx, cancel := getContext(pfi)
304	if cancel != nil {
305		defer cancel()
306	}
307	err := getfi(pfi).SetFileAttributes(ctx, makeFI(fname, pfi), FileAttribute(fileAttributes))
308	return errToNT(err)
309}
310
311//export kbfsLibdokanSetFileTime
312func kbfsLibdokanSetFileTime(
313	fname C.LPCWSTR,
314	creation *C.FILETIME,
315	lastAccess *C.FILETIME,
316	lastWrite *C.FILETIME,
317	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
318	debugf("SetFileTime '%v' %v", d16{fname}, *pfi)
319	ctx, cancel := getContext(pfi)
320	if cancel != nil {
321		defer cancel()
322	}
323	var t0, t1, t2 time.Time
324	if creation != nil {
325		t0 = unpackTime(*creation)
326	}
327	if lastAccess != nil {
328		t1 = unpackTime(*lastAccess)
329	}
330	if lastWrite != nil {
331		t2 = unpackTime(*lastWrite)
332	}
333	err := getfi(pfi).SetFileTime(ctx, makeFI(fname, pfi), t0, t1, t2)
334	return errToNT(err)
335}
336
337//export kbfsLibdokanDeleteFile
338func kbfsLibdokanDeleteFile(
339	fname C.LPCWSTR,
340	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
341	debugf("DeleteFile '%v' %v", d16{fname}, *pfi)
342	ctx, cancel := getContext(pfi)
343	if cancel != nil {
344		defer cancel()
345	}
346	err := getfi(pfi).CanDeleteFile(ctx, makeFI(fname, pfi))
347	return errToNT(err)
348}
349
350//export kbfsLibdokanDeleteDirectory
351func kbfsLibdokanDeleteDirectory(
352	fname C.LPCWSTR,
353	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
354	debugf("DeleteDirectory '%v' %v", d16{fname}, *pfi)
355	ctx, cancel := getContext(pfi)
356	if cancel != nil {
357		defer cancel()
358	}
359	err := getfi(pfi).CanDeleteDirectory(ctx, makeFI(fname, pfi))
360	return errToNT(err)
361}
362
363//export kbfsLibdokanMoveFile
364func kbfsLibdokanMoveFile(
365	oldFName C.LPCWSTR,
366	newFName C.LPCWSTR,
367	replaceExisiting C.BOOL,
368	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
369	newPath := lpcwstrToString(newFName)
370	debugf("MoveFile '%v' %v target %v", d16{oldFName}, *pfi, newPath)
371	ctx, cancel := getContext(pfi)
372	if cancel != nil {
373		defer cancel()
374	}
375	// On error nil, not a dummy file like in getfi.
376	file := fiTableGetFile(uint32(pfi.Context))
377	err := getfs(pfi).MoveFile(ctx, file, makeFI(oldFName, pfi), newPath, bool(replaceExisiting != 0))
378	return errToNT(err)
379}
380
381//export kbfsLibdokanSetEndOfFile
382func kbfsLibdokanSetEndOfFile(
383	fname C.LPCWSTR,
384	length C.LONGLONG,
385	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
386	debugf("SetEndOfFile '%v' %d %v", d16{fname}, length, *pfi)
387	ctx, cancel := getContext(pfi)
388	if cancel != nil {
389		defer cancel()
390	}
391	err := getfi(pfi).SetEndOfFile(ctx, makeFI(fname, pfi), int64(length))
392	return errToNT(err)
393}
394
395//export kbfsLibdokanSetAllocationSize
396func kbfsLibdokanSetAllocationSize(
397	fname C.LPCWSTR,
398	length C.LONGLONG,
399	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
400	debugf("SetAllocationSize '%v' %d %v", d16{fname}, length, *pfi)
401	ctx, cancel := getContext(pfi)
402	if cancel != nil {
403		defer cancel()
404	}
405	err := getfi(pfi).SetAllocationSize(ctx, makeFI(fname, pfi), int64(length))
406	return errToNT(err)
407}
408
409//export kbfsLibdokanLockFile
410func kbfsLibdokanLockFile(
411	fname C.LPCWSTR,
412	offset C.LONGLONG,
413	length C.LONGLONG,
414	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
415	debugf("LockFile '%v' %v", d16{fname}, *pfi)
416	ctx, cancel := getContext(pfi)
417	if cancel != nil {
418		defer cancel()
419	}
420
421	err := getfi(pfi).LockFile(ctx, makeFI(fname, pfi), int64(offset), int64(length))
422	return errToNT(err)
423}
424
425//export kbfsLibdokanUnlockFile
426func kbfsLibdokanUnlockFile(
427	fname C.LPCWSTR,
428	offset C.LONGLONG,
429	length C.LONGLONG,
430	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
431	debugf("UnlockFile '%v' %v", d16{fname}, *pfi)
432	ctx, cancel := getContext(pfi)
433	if cancel != nil {
434		defer cancel()
435	}
436	err := getfi(pfi).UnlockFile(ctx, makeFI(fname, pfi), int64(offset), int64(length))
437	return errToNT(err)
438}
439
440//export kbfsLibdokanGetDiskFreeSpace
441func kbfsLibdokanGetDiskFreeSpace(
442	FreeBytesAvailable *C.ULONGLONG, //nolint
443	TotalNumberOfBytes *C.ULONGLONG, //nolint
444	TotalNumberOfFreeBytes *C.ULONGLONG, //nolint
445	FileInfo C.PDOKAN_FILE_INFO) C.NTSTATUS { //nolint
446	debug("GetDiskFreeSpace", *FileInfo)
447	fs := getfs(FileInfo)
448	ctx, cancel := fs.WithContext(globalContext())
449	if cancel != nil {
450		defer cancel()
451	}
452	space, err := fs.GetDiskFreeSpace(ctx)
453	debug("->", space, err)
454	if err != nil {
455		return errToNT(err)
456	}
457	if FreeBytesAvailable != nil {
458		*FreeBytesAvailable = C.ULONGLONG(space.FreeBytesAvailable)
459	}
460	if TotalNumberOfBytes != nil {
461		*TotalNumberOfBytes = C.ULONGLONG(space.TotalNumberOfBytes)
462	}
463	if TotalNumberOfFreeBytes != nil {
464		*TotalNumberOfFreeBytes = C.ULONGLONG(space.TotalNumberOfFreeBytes)
465	}
466	return ntstatusOk
467}
468
469//export kbfsLibdokanGetVolumeInformation
470func kbfsLibdokanGetVolumeInformation(
471	VolumeNameBuffer C.LPWSTR, //nolint
472	VolumeNameSize C.DWORD, //nolint in num of chars
473	VolumeSerialNumber C.LPDWORD, //nolint
474	MaximumComponentLength C.LPDWORD, //nolint in num of chars
475	FileSystemFlags C.LPDWORD, //nolint
476	FileSystemNameBuffer C.LPWSTR, //nolint
477	FileSystemNameSize C.DWORD, //nolint in num of chars
478	FileInfo C.PDOKAN_FILE_INFO) C.NTSTATUS { //nolint
479	debug("GetVolumeInformation", VolumeNameSize, MaximumComponentLength, FileSystemNameSize, *FileInfo)
480	fs := getfs(FileInfo)
481	ctx, cancel := fs.WithContext(globalContext())
482	if cancel != nil {
483		defer cancel()
484	}
485	vi, err := fs.GetVolumeInformation(ctx)
486	debug("->", vi, err)
487	if err != nil {
488		return errToNT(err)
489	}
490	if VolumeNameBuffer != nil {
491		stringToUtf16Buffer(vi.VolumeName, VolumeNameBuffer, VolumeNameSize)
492	}
493	if VolumeSerialNumber != nil {
494		*VolumeSerialNumber = C.DWORD(vi.VolumeSerialNumber)
495	}
496	if MaximumComponentLength != nil {
497		*MaximumComponentLength = C.DWORD(vi.MaximumComponentLength)
498	}
499	if FileSystemFlags != nil {
500		*FileSystemFlags = C.DWORD(vi.FileSystemFlags)
501	}
502	if FileSystemNameBuffer != nil {
503		stringToUtf16Buffer(vi.FileSystemName, FileSystemNameBuffer, FileSystemNameSize)
504	}
505
506	return ntstatusOk
507}
508
509//export kbfsLibdokanMounted
510func kbfsLibdokanMounted(pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
511	debug("Mounted")
512	// Signal that the filesystem is mounted and can be used.
513	fsTableGetErrChan(uint32(pfi.DokanOptions.GlobalContext)) <- nil
514	// Dokan wants a NTSTATUS here, but is discarding it.
515	return ntstatusOk
516}
517
518//export kbfsLibdokanGetFileSecurity
519func kbfsLibdokanGetFileSecurity(
520	fname C.LPCWSTR,
521	//A pointer to SECURITY_INFORMATION value being requested
522	input C.PSECURITY_INFORMATION,
523	// A pointer to SECURITY_DESCRIPTOR buffer to be filled
524	output C.PSECURITY_DESCRIPTOR,
525	outlen C.ULONG, // length of Security descriptor buffer
526	LengthNeeded *C.ULONG, //nolint
527	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
528	var si winacl.SecurityInformation
529	if input != nil {
530		si = winacl.SecurityInformation(*input)
531	}
532	debug("GetFileSecurity", si)
533	ctx, cancel := getContext(pfi)
534	if cancel != nil {
535		defer cancel()
536	}
537	buf := bufToSlice(unsafe.Pointer(output), uint32(outlen))
538	sd := winacl.NewSecurityDescriptorWithBuffer(
539		buf)
540	err := getfi(pfi).GetFileSecurity(ctx, makeFI(fname, pfi), si, sd)
541	if err != nil {
542		return errToNT(err)
543	}
544	if LengthNeeded != nil {
545		*LengthNeeded = C.ULONG(sd.Size())
546	}
547	if sd.HasOverflowed() {
548		debug("Too small buffer", outlen, "would have needed", sd.Size())
549		return errToNT(StatusBufferOverflow)
550	}
551	debugf("%X", buf)
552	debug("-> ok,", sd.Size(), "bytes")
553	return ntstatusOk
554}
555
556//export kbfsLibdokanSetFileSecurity
557func kbfsLibdokanSetFileSecurity(
558	fname C.LPCWSTR,
559	SecurityInformation C.PSECURITY_INFORMATION, //nolint
560	SecurityDescriptor C.PSECURITY_DESCRIPTOR, //nolint
561	SecurityDescriptorLength C.ULONG, //nolint
562	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
563	debug("SetFileSecurity TODO")
564	return ntstatusOk
565}
566
567/* FIXME add support for multiple streams per file?
568//export kbfsLibdokanFindStreams
569func kbfsLibdokanFindStreams (
570	fname C.LPCWSTR,
571	// call this function with PWIN32_FIND_STREAM_DATA
572	FindStreamData uintptr,
573	pfi C.PDOKAN_FILE_INFO) C.NTSTATUS {
574
575
576}
577*/
578
579// FileInfo contains information about a file including the path.
580type FileInfo struct {
581	ptr     C.PDOKAN_FILE_INFO
582	rawPath C.LPCWSTR
583}
584
585func makeFI(fname C.LPCWSTR, pfi C.PDOKAN_FILE_INFO) *FileInfo {
586	return &FileInfo{pfi, fname}
587}
588
589func packTime(t time.Time) C.FILETIME {
590	if t.IsZero() {
591		return C.FILETIME{}
592	}
593	ft := syscall.NsecToFiletime(t.UnixNano())
594	return C.FILETIME{dwLowDateTime: C.DWORD(ft.LowDateTime), dwHighDateTime: C.DWORD(ft.HighDateTime)}
595}
596func unpackTime(c C.FILETIME) time.Time {
597	// Zero means ignore. Sometimes -1 is passed for ignore too.
598	if c == (C.FILETIME{}) || c == (C.FILETIME{0xFFFFffff, 0xFFFFffff}) {
599		return time.Time{}
600	}
601	ft := syscall.Filetime{LowDateTime: uint32(c.dwLowDateTime), HighDateTime: uint32(c.dwHighDateTime)}
602	// This is valid, see docs and code for package time.
603	return time.Unix(0, ft.Nanoseconds())
604}
605
606func getfs(fi C.PDOKAN_FILE_INFO) FileSystem {
607	return fsTableGet(uint32(fi.DokanOptions.GlobalContext))
608}
609
610func getfi(fi C.PDOKAN_FILE_INFO) File {
611	file := fiTableGetFile(uint32(fi.Context))
612	if file == nil {
613		file = &errorFile{getfs(fi)}
614	}
615	return file
616}
617
618func fiStore(pfi C.PDOKAN_FILE_INFO, fi File, err error) C.NTSTATUS {
619	debug("->", fi, err)
620	if fi != nil {
621		pfi.Context = C.ULONG64(fiTableStoreFile(uint32(pfi.DokanOptions.GlobalContext), fi))
622	}
623	return errToNT(err)
624}
625
626func errToNT(err error) C.NTSTATUS {
627	// NTSTATUS constants are defined as unsigned but the type is signed
628	// and the values overflow on purpose. This is horrible.
629	var code uint32
630	if err != nil {
631		debug("ERROR:", err)
632		n, ok := err.(NtStatus)
633		if ok {
634			code = uint32(n)
635		} else {
636			code = uint32(ErrAccessDenied)
637		}
638	}
639	return C.NTSTATUS(code)
640}
641
642type dokanCtx struct {
643	ptr  *C.struct_kbfsLibdokanCtx
644	slot uint32
645}
646
647func allocCtx(slot uint32) *dokanCtx {
648	return &dokanCtx{C.kbfsLibdokanAllocCtx(C.ULONG64(slot)), slot}
649}
650
651func (ctx *dokanCtx) Run(path string, flags MountFlag) error {
652	ctx.ptr.dokan_options.Options = C.ULONG(flags)
653	if isDebug {
654		ctx.ptr.dokan_options.Options |= C.kbfsLibdokanDebug | C.kbfsLibdokanStderr
655	}
656	C.kbfsLibdokanSet_path(ctx.ptr, stringToUtf16Ptr(path))
657	ec := C.kbfsLibdokanRun(ctx.ptr)
658	if ec != 0 {
659		return fmt.Errorf("Dokan failed: code=%d %q", ec, dokanErrString(int32(ec)))
660	}
661	return nil
662}
663
664//nolint
665func dokanErrString(code int32) string {
666	switch code {
667	case C.kbfsLibDokan_ERROR:
668		return "General error"
669	case C.kbfsLibDokan_DRIVE_LETTER_ERROR:
670		return "Drive letter error"
671	case C.kbfsLibDokan_DRIVER_INSTALL_ERROR:
672		return "Driver install error"
673	case C.kbfsLibDokan_START_ERROR:
674		return "Start error"
675	case C.kbfsLibDokan_MOUNT_ERROR:
676		return "Mount error"
677	case C.kbfsLibDokan_MOUNT_POINT_ERROR:
678		return "Mount point error"
679	case C.kbfsLibDokan_VERSION_ERROR:
680		return "Version error"
681	case C.kbfsLibDokan_DLL_LOAD_ERROR:
682		return "Error loading Dokan DLL"
683	default:
684		return "UNKNOWN"
685	}
686}
687
688func (ctx *dokanCtx) Free() {
689	debug("dokanCtx.Free")
690	C.kbfsLibdokanFree(ctx.ptr)
691	fsTableFree(ctx.slot)
692}
693
694// getRequestorToken returns the syscall.Token associated with
695// the requestor of this file system operation. Remember to
696// call Close on the Token.
697func (fi *FileInfo) getRequestorToken() (syscall.Token, error) {
698	hdl := syscall.Handle(C.kbfsLibdokan_OpenRequestorToken(fi.ptr))
699	var err error
700	if hdl == syscall.InvalidHandle {
701		// Tokens are value types, so returning nil is impossible,
702		// returning an InvalidHandle is the best way.
703		err = errors.New("Invalid handle from DokanOpenRequestorHandle")
704	}
705	return syscall.Token(hdl), err
706}
707
708// isRequestorUserSidEqualTo returns true if the sid passed as
709// the argument is equal to the sid of the user associated with
710// the filesystem request.
711func (fi *FileInfo) isRequestorUserSidEqualTo(sid *winacl.SID) bool {
712	tok, err := fi.getRequestorToken()
713	if err != nil {
714		debug("IsRequestorUserSidEqualTo:", err)
715		return false
716	}
717	defer tok.Close()
718	tokUser, err := tok.GetTokenUser()
719	if err != nil {
720		debug("IsRequestorUserSidEqualTo: GetTokenUser:", err)
721		return false
722	}
723	res, _, _ := syscall.Syscall(procEqualSid.Addr(), 2,
724		uintptr(unsafe.Pointer(sid)),
725		uintptr(unsafe.Pointer(tokUser.User.Sid)),
726		0)
727	if isDebug {
728		u1, _ := (*syscall.SID)(sid).String()
729		u2, _ := tokUser.User.Sid.String()
730		debugf("IsRequestorUserSidEqualTo: EqualSID(%q,%q) => %v (expecting non-zero)\n", u1, u2, res)
731	}
732	runtime.KeepAlive(sid)
733	runtime.KeepAlive(tokUser.User.Sid)
734	return res != 0
735}
736
737var (
738	modadvapi32  = windows.NewLazySystemDLL("advapi32.dll")
739	procEqualSid = modadvapi32.NewProc("EqualSid")
740)
741
742// unmount a drive mounted by dokan.
743func unmount(path string) error {
744	debug("Unmount: Calling Dokan.Unmount")
745	res := C.kbfsLibdokan_RemoveMountPoint((*C.WCHAR)(stringToUtf16Ptr(path)))
746	if res == C.FALSE {
747		debug("Unmount: Failed!")
748		return errors.New("kbfsLibdokan_RemoveMountPoint failed")
749	}
750	debug("Unmount: Success!")
751	return nil
752}
753
754// lpcwstrToString converts a nul-terminated Windows wide string to a Go string,
755func lpcwstrToString(ptr C.LPCWSTR) string {
756	if ptr == nil {
757		return ""
758	}
759	var len = 0
760	for tmp := ptr; *tmp != 0; tmp = (C.LPCWSTR)(unsafe.Pointer((uintptr(unsafe.Pointer(tmp)) + 2))) {
761		len++
762	}
763	raw := ptrUcs2Slice(unsafe.Pointer(ptr), len)
764	return string(utf16.Decode(raw))
765}
766
767// stringToUtf16Buffer pokes a string into a Windows wide string buffer.
768// On overflow does not poke anything and returns false.
769func stringToUtf16Buffer(s string, ptr C.LPWSTR, blenUcs2 C.DWORD) bool {
770	return stringToUtf16BufferPtr(s, unsafe.Pointer(ptr), blenUcs2)
771}
772func stringToUtf16BufferPtr(s string, ptr unsafe.Pointer, blenUcs2 C.DWORD) bool {
773	if ptr == nil || blenUcs2 == 0 {
774		return false
775	}
776	src := utf16.Encode([]rune(s))
777	tgt := ptrUcs2Slice(ptr, int(blenUcs2))
778	if len(src)+1 >= len(tgt) {
779		tgt[0] = 0
780		return false
781	}
782	copy(tgt, src)
783	tgt[len(src)] = 0
784	return true
785}
786
787// stringToUtf16Ptr return a pointer to the string as utf16 with zero
788// termination.
789func stringToUtf16Ptr(s string) unsafe.Pointer {
790	tmp := utf16.Encode([]rune(s + "\000"))
791	return unsafe.Pointer(&tmp[0])
792}
793
794// ptrUcs2Slice takes a C Windows wide string and length in UCS2
795// and returns it aliased as a uint16 slice.
796func ptrUcs2Slice(ptr unsafe.Pointer, lenUcs2 int) []uint16 {
797	return *(*[]uint16)(unsafe.Pointer(&reflect.SliceHeader{
798		Data: uintptr(ptr),
799		Len:  lenUcs2,
800		Cap:  lenUcs2}))
801}
802
803// d16 wraps C wide string pointers to a struct with nice printing
804// with zero cost when not debugging and pretty prints when debugging.
805type d16 struct{ ptr C.LPCWSTR }
806
807func (s d16) String() string {
808	return lpcwstrToString(s.ptr)
809}
810