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