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  }