github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/dokan/testfs_test.go (about) 1 // Copyright 2016 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 import ( 11 "bytes" 12 "context" 13 "io" 14 "os" 15 "strings" 16 "sync" 17 "syscall" 18 "testing" 19 "time" 20 "unsafe" 21 22 "github.com/keybase/client/go/kbfs/dokan/winacl" 23 "github.com/keybase/client/go/kbfs/ioutil" 24 "github.com/stretchr/testify/require" 25 "golang.org/x/sys/windows" 26 ) 27 28 func TestEmptyFS(t *testing.T) { 29 s0 := fsTableStore(emptyFS{}, nil) 30 defer fsTableFree(s0) 31 fs := newTestFS() 32 mnt, err := Mount(&Config{FileSystem: fs, Path: `T:\`}) 33 if err != nil { 34 t.Fatal("Mount failed:", err) 35 } 36 defer mnt.Close() 37 time.Sleep(5 * time.Second) 38 testShouldNotExist(t) 39 testHelloTxt(t) 40 testRAMFile(t) 41 testReaddir(t) 42 testPlaceHolderRemoveRename(t) 43 testDiskFreeSpace(t) 44 } 45 46 func testShouldNotExist(t *testing.T) { 47 _, err := os.Open(`T:\should-not-exist`) 48 if !ioutil.IsNotExist(err) { 49 t.Fatal("Opening non-existent file:", err) 50 } 51 } 52 53 func testHelloTxt(t *testing.T) { 54 f, err := os.Open(`T:\hello.txt`) 55 if err != nil { 56 t.Fatal("Opening hello.txt file:", err) 57 } 58 defer f.Close() 59 bs := make([]byte, 256) 60 n, err := f.Read(bs) 61 if err != nil { 62 t.Fatal("Reading hello.txt file:", err) 63 } 64 if string(bs[:n]) != helloStr { 65 t.Fatal("Read returned wrong bytes:", bs[:n]) 66 } 67 statIsLike(t, f, int64(len(helloStr)), nil) 68 } 69 70 func testRAMFile(t *testing.T) { 71 f, err := os.Create(`T:\ram.txt`) 72 if err != nil { 73 t.Fatal("Opening ram.txt file:", err) 74 } 75 defer f.Close() 76 bs := make([]byte, 256) 77 n, err := f.Read(bs) 78 if n != 0 || err != io.EOF { 79 t.Fatal("Reading empty ram.txt file:", n, err) 80 } 81 n, err = f.WriteAt([]byte(helloStr), 4) 82 if n != len(helloStr) || err != nil { 83 t.Fatal("WriteAt ram.txt file:", n, err) 84 } 85 n, err = f.ReadAt(bs, 4) 86 if err != nil && err != io.EOF { 87 t.Fatal("ReadAt ram.txt file:", err) 88 } 89 if string(bs[:n]) != helloStr { 90 t.Fatal("ReadAt ram.txt returned wrong bytes:", bs[:n]) 91 } 92 n, err = f.Read(bs) 93 if err != nil && err != io.EOF { 94 t.Fatal("Reading ram.txt file:", err) 95 } 96 if string(bs[:n]) != string([]byte{0, 0, 0, 0})+helloStr { 97 t.Fatal("Read ram.txt returned wrong bytes:", bs[:n]) 98 } 99 t0 := time.Now() 100 statIsLike(t, f, int64(len(helloStr)+4), &t0) 101 tp := time.Date(2007, 1, 2, 3, 4, 5, 6, time.UTC) 102 ft := syscall.NsecToFiletime(tp.UnixNano()) 103 err = syscall.SetFileTime(syscall.Handle(f.Fd()), nil, nil, &ft) 104 if err != nil { 105 t.Fatal("SetFileTime ram.txt file:", err) 106 } 107 statIsLike(t, f, int64(len(helloStr)+4), &tp) 108 testLock(t, f) 109 testUnlock(t, f) 110 testSync(t, f) 111 testTruncate(t, f) 112 } 113 114 func statIsLike(t *testing.T, f *os.File, sz int64, timptr *time.Time) { 115 st, err := f.Stat() 116 if err != nil { 117 t.Fatal("Statting ", f.Name(), err) 118 } 119 if st.Size() != sz { 120 t.Fatal("Size returned wrong size", f.Name(), st.Size(), "vs", len(helloStr)) 121 } 122 if timptr != nil && !isNearTime(*timptr, st.ModTime()) { 123 t.Fatal("Modification time returned by stat is wrong", f.Name(), st.ModTime(), "vs", *timptr) 124 } 125 } 126 127 func isNearTime(t0, t1 time.Time) bool { 128 if t1.Before(t0) { 129 t0, t1 = t1, t0 130 } 131 // TODO consider a less flaky way to do this. 132 return t1.Sub(t0) < time.Second 133 } 134 135 var ( 136 modkernel32 = windows.NewLazySystemDLL("kernel32.dll") 137 138 procLockFile = modkernel32.NewProc("LockFile") 139 procUnlockFile = modkernel32.NewProc("UnlockFile") 140 procGetDiskFreeSpaceExW = modkernel32.NewProc("GetDiskFreeSpaceExW") 141 ) 142 143 func testLock(t *testing.T, f *os.File) { 144 res, _, err := syscall.Syscall6(procLockFile.Addr(), 5, f.Fd(), 1, 0, 2, 0, 0) 145 if res == 0 { 146 t.Fatal("LockFile failed with:", err) 147 } 148 } 149 150 func testUnlock(t *testing.T, f *os.File) { 151 res, _, err := syscall.Syscall6(procUnlockFile.Addr(), 5, f.Fd(), 1, 0, 2, 0, 0) 152 if res == 0 { 153 t.Fatal("UnlockFile failed with:", err) 154 } 155 } 156 157 func testSync(t *testing.T, f *os.File) { 158 err := f.Sync() 159 if err != nil { 160 t.Fatal("Syncing ", f.Name(), err) 161 } 162 } 163 164 func testTruncate(t *testing.T, f *os.File) { 165 for _, size := range []int64{400, 2, 0, 1, 5, 77, 13} { 166 err := f.Truncate(size) 167 if err != nil { 168 t.Fatal("Truncating ", f.Name(), "to", size, err) 169 } 170 statIsLike(t, f, size, nil) 171 } 172 } 173 174 func testReaddir(t *testing.T) { 175 f, err := os.Open(`T:\`) 176 if err != nil { 177 t.Fatal("Opening root directory:", err) 178 } 179 defer f.Close() 180 debug("Starting readdir") 181 fs, err := f.Readdir(-1) 182 if err != nil { 183 t.Fatal("Readdir root directory:", err) 184 } 185 if len(fs) != 1 { 186 t.Fatal("Readdir root directory element number mismatch: ", len(fs)) 187 } 188 st := fs[0] 189 if st.Name() != `hello.txt` { 190 t.Fatal("Readdir invalid name:", st.Name()) 191 } 192 if st.Size() != int64(len(helloStr)) { 193 t.Fatal("Size returned wrong size:", st.Size(), "vs", len(helloStr)) 194 } 195 196 } 197 198 func testPlaceHolderRemoveRename(t *testing.T) { 199 err := ioutil.Remove(`T:\hello.txt`) 200 require.Error(t, err) 201 err = ioutil.Remove(`T:\`) 202 require.Error(t, err) 203 err = ioutil.Rename(`T:\hello.txt`, `T:\does-not-exist2`) 204 require.NoError(t, err) 205 } 206 207 func testDiskFreeSpace(t *testing.T) { 208 var free, total, totalFree uint64 209 ppath := syscall.StringToUTF16Ptr(`T:\`) // nolint 210 res, _, err := syscall.Syscall6(procGetDiskFreeSpaceExW.Addr(), 211 4, 212 uintptr(unsafe.Pointer(ppath)), 213 uintptr(unsafe.Pointer(&free)), 214 uintptr(unsafe.Pointer(&total)), 215 uintptr(unsafe.Pointer(&totalFree)), 0, 0) 216 if res == 0 { 217 t.Fatal("GetDiskFreeSpaceEx failed with:", err) 218 } 219 if free != testFreeAvail { 220 t.Fatalf("GetDiskFreeSpace: %X vs %X", free, uint64(testFreeAvail)) 221 } 222 if total != testTotalBytes { 223 t.Fatalf("GetDiskFreeSpace: %X vs %X", total, uint64(testTotalBytes)) 224 } 225 if totalFree != testTotalFree { 226 t.Fatalf("GetDiskFreeSpace: %X vs %X", totalFree, uint64(testTotalFree)) 227 } 228 } 229 230 var _ FileSystem = emptyFS{} 231 232 type emptyFS struct{} 233 234 func (t emptyFile) GetFileSecurity(ctx context.Context, fi *FileInfo, si winacl.SecurityInformation, sd *winacl.SecurityDescriptor) error { 235 debug("emptyFS.GetFileSecurity") 236 return nil 237 } 238 func (t emptyFile) SetFileSecurity(ctx context.Context, fi *FileInfo, si winacl.SecurityInformation, sd *winacl.SecurityDescriptor) error { 239 debug("emptyFS.SetFileSecurity") 240 return nil 241 } 242 func (t emptyFile) Cleanup(ctx context.Context, fi *FileInfo) { 243 debug("emptyFS.Cleanup") 244 } 245 246 func (t emptyFile) CloseFile(ctx context.Context, fi *FileInfo) { 247 debug("emptyFS.CloseFile") 248 } 249 250 func (t emptyFS) WithContext(ctx context.Context) (context.Context, context.CancelFunc) { 251 return ctx, nil 252 } 253 254 func (t emptyFS) GetVolumeInformation(ctx context.Context) (VolumeInformation, error) { 255 debug("emptyFS.GetVolumeInformation") 256 return VolumeInformation{}, nil 257 } 258 259 func (t emptyFS) GetDiskFreeSpace(ctx context.Context) (FreeSpace, error) { 260 debug("emptyFS.GetDiskFreeSpace") 261 return FreeSpace{}, nil 262 } 263 264 func (t emptyFS) ErrorPrint(err error) { 265 debug(err) 266 } 267 268 func (t emptyFS) Printf(string, ...interface{}) { 269 } 270 271 func (t emptyFS) CreateFile(ctx context.Context, fi *FileInfo, cd *CreateData) (File, CreateStatus, error) { 272 debug("emptyFS.CreateFile") 273 return emptyFile{}, ExistingDir, nil 274 } 275 func (t emptyFile) CanDeleteFile(ctx context.Context, fi *FileInfo) error { 276 return ErrAccessDenied 277 } 278 func (t emptyFile) CanDeleteDirectory(ctx context.Context, fi *FileInfo) error { 279 return ErrAccessDenied 280 } 281 func (t emptyFile) SetEndOfFile(ctx context.Context, fi *FileInfo, length int64) error { 282 debug("emptyFile.SetEndOfFile") 283 return nil 284 } 285 func (t emptyFile) SetAllocationSize(ctx context.Context, fi *FileInfo, length int64) error { 286 debug("emptyFile.SetAllocationSize") 287 return nil 288 } 289 func (t emptyFS) MoveFile(ctx context.Context, src File, sourceFI *FileInfo, targetPath string, replaceExisting bool) error { 290 debug("emptyFS.MoveFile") 291 return nil 292 } 293 func (t emptyFile) ReadFile(ctx context.Context, fi *FileInfo, bs []byte, offset int64) (int, error) { 294 return len(bs), nil 295 } 296 func (t emptyFile) WriteFile(ctx context.Context, fi *FileInfo, bs []byte, offset int64) (int, error) { 297 return len(bs), nil 298 } 299 func (t emptyFile) FlushFileBuffers(ctx context.Context, fi *FileInfo) error { 300 debug("emptyFS.FlushFileBuffers") 301 return nil 302 } 303 304 type emptyFile struct{} 305 306 func (t emptyFile) GetFileInformation(ctx context.Context, fi *FileInfo) (*Stat, error) { 307 debug("emptyFile.GetFileInformation") 308 var st Stat 309 st.FileAttributes = FileAttributeNormal 310 return &st, nil 311 } 312 func (t emptyFile) FindFiles(context.Context, *FileInfo, string, func(*NamedStat) error) error { 313 debug("emptyFile.FindFiles") 314 return nil 315 } 316 func (t emptyFile) SetFileTime(context.Context, *FileInfo, time.Time, time.Time, time.Time) error { 317 debug("emptyFile.SetFileTime") 318 return nil 319 } 320 func (t emptyFile) SetFileAttributes(ctx context.Context, fi *FileInfo, fileAttributes FileAttribute) error { 321 debug("emptyFile.SetFileAttributes") 322 return nil 323 } 324 325 func (t emptyFile) LockFile(ctx context.Context, fi *FileInfo, offset int64, length int64) error { 326 debug("emptyFile.LockFile") 327 return nil 328 } 329 func (t emptyFile) UnlockFile(ctx context.Context, fi *FileInfo, offset int64, length int64) error { 330 debug("emptyFile.UnlockFile") 331 return nil 332 } 333 334 type testFS struct { 335 emptyFS 336 ramFile *ramFile 337 } 338 339 func newTestFS() *testFS { 340 var t testFS 341 t.ramFile = newRAMFile() 342 return &t 343 } 344 345 func (t *testFS) CreateFile(ctx context.Context, fi *FileInfo, cd *CreateData) (File, CreateStatus, error) { 346 path := fi.Path() 347 debug("testFS.CreateFile", path) 348 switch path { 349 case `\hello.txt`: 350 return testFile{}, ExistingFile, nil 351 case `\ram.txt`: 352 return t.ramFile, ExistingFile, nil 353 // SL_OPEN_TARGET_DIRECTORY may get empty paths... 354 case `\`, ``: 355 if cd.CreateOptions&FileNonDirectoryFile != 0 { 356 return nil, 0, ErrFileIsADirectory 357 } 358 return testDir{}, ExistingDir, nil 359 } 360 return nil, 0, ErrObjectNameNotFound 361 } 362 func (t *testFS) GetDiskFreeSpace(ctx context.Context) (FreeSpace, error) { 363 debug("testFS.GetDiskFreeSpace") 364 return FreeSpace{ 365 FreeBytesAvailable: testFreeAvail, 366 TotalNumberOfBytes: testTotalBytes, 367 TotalNumberOfFreeBytes: testTotalFree, 368 }, nil 369 } 370 371 const ( 372 // Windows mangles the last bytes of GetDiskFreeSpaceEx 373 // because of GetDiskFreeSpace and sectors... 374 testFreeAvail = 0xA234567887654000 375 testTotalBytes = 0xB234567887654000 376 testTotalFree = 0xC234567887654000 377 ) 378 379 type testDir struct { 380 emptyFile 381 } 382 383 const helloStr = "hello world\r\n" 384 385 func (t testDir) FindFiles(ctx context.Context, fi *FileInfo, p string, cb func(*NamedStat) error) error { 386 debug("testDir.FindFiles") 387 st := NamedStat{} 388 st.Name = "hello.txt" 389 st.FileSize = int64(len(helloStr)) 390 return cb(&st) 391 } 392 func (t testDir) GetFileInformation(ctx context.Context, fi *FileInfo) (*Stat, error) { 393 debug("testDir.GetFileInformation") 394 return &Stat{ 395 FileAttributes: FileAttributeDirectory, 396 }, nil 397 } 398 399 type testFile struct { 400 emptyFile 401 } 402 403 func (t testFile) GetFileInformation(ctx context.Context, fi *FileInfo) (*Stat, error) { 404 debug("testFile.GetFileInformation") 405 return &Stat{ 406 FileSize: int64(len(helloStr)), 407 }, nil 408 } 409 func (t testFile) ReadFile(ctx context.Context, fi *FileInfo, bs []byte, offset int64) (int, error) { 410 debug("testFile.ReadFile") 411 rd := strings.NewReader(helloStr) 412 return rd.ReadAt(bs, offset) 413 } 414 415 type ramFile struct { 416 emptyFile 417 lock sync.Mutex 418 creationTime time.Time 419 lastReadTime time.Time 420 lastWriteTime time.Time 421 contents []byte 422 } 423 424 func newRAMFile() *ramFile { 425 var r ramFile 426 r.creationTime = time.Now() 427 r.lastReadTime = r.creationTime 428 r.lastWriteTime = r.creationTime 429 return &r 430 } 431 432 func (r *ramFile) GetFileInformation(ctx context.Context, fi *FileInfo) (*Stat, error) { 433 debug("ramFile.GetFileInformation") 434 r.lock.Lock() 435 defer r.lock.Unlock() 436 return &Stat{ 437 FileSize: int64(len(r.contents)), 438 LastAccess: r.lastReadTime, 439 LastWrite: r.lastWriteTime, 440 Creation: r.creationTime, 441 }, nil 442 } 443 444 func (r *ramFile) ReadFile(ctx context.Context, fi *FileInfo, bs []byte, offset int64) (int, error) { 445 debug("ramFile.ReadFile") 446 r.lock.Lock() 447 defer r.lock.Unlock() 448 r.lastReadTime = time.Now() 449 rd := bytes.NewReader(r.contents) 450 return rd.ReadAt(bs, offset) 451 } 452 453 func (r *ramFile) WriteFile(ctx context.Context, fi *FileInfo, bs []byte, offset int64) (int, error) { 454 debug("ramFile.WriteFile") 455 r.lock.Lock() 456 defer r.lock.Unlock() 457 r.lastWriteTime = time.Now() 458 maxl := len(r.contents) 459 if int(offset)+len(bs) > maxl { 460 maxl = int(offset) + len(bs) 461 r.contents = append(r.contents, make([]byte, maxl-len(r.contents))...) 462 } 463 n := copy(r.contents[int(offset):], bs) 464 return n, nil 465 } 466 func (r *ramFile) SetFileTime(ctx context.Context, fi *FileInfo, creationTime time.Time, lastReadTime time.Time, lastWriteTime time.Time) error { 467 debug("ramFile.SetFileTime") 468 r.lock.Lock() 469 defer r.lock.Unlock() 470 if !lastWriteTime.IsZero() { 471 r.lastWriteTime = lastWriteTime 472 } 473 return nil 474 } 475 func (r *ramFile) SetEndOfFile(ctx context.Context, fi *FileInfo, length int64) error { 476 debug("ramFile.SetEndOfFile") 477 r.lock.Lock() 478 defer r.lock.Unlock() 479 r.lastWriteTime = time.Now() 480 switch { 481 case int(length) < len(r.contents): 482 r.contents = r.contents[:int(length)] 483 case int(length) > len(r.contents): 484 r.contents = append(r.contents, make([]byte, int(length)-len(r.contents))...) 485 } 486 return nil 487 } 488 func (r *ramFile) SetAllocationSize(ctx context.Context, fi *FileInfo, length int64) error { 489 debug("ramFile.SetAllocationSize") 490 r.lock.Lock() 491 defer r.lock.Unlock() 492 r.lastWriteTime = time.Now() 493 if int(length) < len(r.contents) { 494 r.contents = r.contents[:int(length)] 495 } 496 return nil 497 }