github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/dokan/cgo.go (about)

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