From bd3380d0beb193fc5ab98134aee94721ccbb7268 Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:57:56 -0700 Subject: [PATCH 1/2] Improve test coverage of libfuse to above 90% --- component/libfuse/libfuse2_handler.go | 131 +-- .../libfuse/libfuse2_handler_test_wrapper.go | 755 ++++++++++++++++++ component/libfuse/libfuse_handler_test.go | 246 ++++++ 3 files changed, 1042 insertions(+), 90 deletions(-) diff --git a/component/libfuse/libfuse2_handler.go b/component/libfuse/libfuse2_handler.go index c15d5a52d..db44a9c41 100644 --- a/component/libfuse/libfuse2_handler.go +++ b/component/libfuse/libfuse2_handler.go @@ -32,7 +32,6 @@ import ( "io/fs" "os" "runtime" - "syscall" "time" "github.com/Seagate/cloudfuse/common" @@ -286,14 +285,7 @@ func (cf *CgofuseFS) Getattr(path string, stat *fuse.Stat_t, fh uint64) int { attr, err := fuseFS.NextComponent().GetAttr(internal.GetAttrOptions{Name: name}) if err != nil { //log.Err("Libfuse::Getattr : Failed to get attributes of %s [%s]", name, err.Error()) - switch err { - case syscall.ENOENT: - return -fuse.ENOENT - case syscall.EACCES: - return -fuse.EACCES - default: - return -fuse.EIO - } + return fuseErrnoFromError(err) } // Populate stat @@ -372,13 +364,7 @@ func (cf *CgofuseFS) Mkdir(path string, mode uint32) int { CreateDir(internal.CreateDirOptions{Name: name, Mode: fs.FileMode(mode)}) if err != nil { log.Err("Libfuse::Mkdir : Failed to create %s [%s]", name, err.Error()) - if os.IsPermission(err) { - return -fuse.EACCES - } else if os.IsExist(err) { - return -fuse.EEXIST - } else { - return -fuse.EIO - } + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents(createDir, name, map[string]any{md: fs.FileMode(mode)}) @@ -546,12 +532,7 @@ func populateDirChildCache( Token: cacheInfo.token, }) if err != nil { - if os.IsNotExist(err) { - return -fuse.ENOENT - } else if os.IsPermission(err) { - return -fuse.EACCES - } - return -fuse.EIO + return fuseErrnoFromError(err) } // compile results and update cache // let the cache grow to MaxDirListCount @@ -608,11 +589,7 @@ func (cf *CgofuseFS) Rmdir(path string) int { err := fuseFS.NextComponent().DeleteDir(internal.DeleteDirOptions{Name: name}) if err != nil { log.Err("Libfuse::Rmdir : Failed to delete %s [%s]", name, err.Error()) - if os.IsNotExist(err) { - return -fuse.ENOENT - } - - return -fuse.EIO + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents(deleteDir, name, nil) @@ -631,13 +608,7 @@ func (cf *CgofuseFS) Create(path string, flags int, mode uint32) (int, uint64) { CreateFile(internal.CreateFileOptions{Name: name, Mode: fs.FileMode(mode)}) if err != nil { log.Err("Libfuse::Create : Failed to create %s [%s]", name, err.Error()) - if os.IsExist(err) { - return -fuse.EEXIST, 0 - } else if os.IsPermission(err) { - return -fuse.EACCES, 0 - } - - return -fuse.EIO, 0 + return fuseErrnoFromError(err), 0 } fh := handlemap.Add(handle) @@ -676,13 +647,7 @@ func (cf *CgofuseFS) Open(path string, flags int) (int, uint64) { if err != nil { log.Err("Libfuse::Open : Failed to open %s [%s]", name, err.Error()) - if os.IsNotExist(err) { - return -fuse.ENOENT, 0 - } else if os.IsPermission(err) { - return -fuse.EACCES, 0 - } - - return -fuse.EIO, 0 + return fuseErrnoFromError(err), 0 } fh := handlemap.Add(handle) @@ -733,23 +698,31 @@ func (cf *CgofuseFS) Read(path string, buff []byte, ofst int64, fh uint64) int { err = nil } if err != nil { - if isAccessDeniedFuseErr(err) { - return -fuse.EACCES - } log.Err( "Libfuse::Read : error reading file %s, handle: %d [%s]", handle.Path, handle.ID, err.Error(), ) - return -fuse.EIO + return fuseErrnoFromError(err) } return bytesRead } -func isAccessDeniedFuseErr(err error) bool { - return os.IsPermission(err) || errors.Is(err, syscall.EACCES) || errors.Is(err, syscall.EPERM) +func fuseErrnoFromError(err error) int { + switch { + case err == nil: + return 0 + case errors.Is(err, fs.ErrNotExist): + return -fuse.ENOENT + case errors.Is(err, fs.ErrPermission): + return -fuse.EACCES + case errors.Is(err, fs.ErrExist): + return -fuse.EEXIST + default: + return -fuse.EIO + } } // Write writes data to a file from the buffer with the given offset. @@ -778,7 +751,7 @@ func (cf *CgofuseFS) Write(path string, buff []byte, ofst int64, fh uint64) int handle.ID, err.Error(), ) - return -fuse.EIO + return fuseErrnoFromError(err) } return bytesWritten @@ -813,14 +786,7 @@ func (cf *CgofuseFS) Flush(path string, fh uint64) int { handle.ID, err.Error(), ) - switch err { - case syscall.ENOENT: - return -fuse.ENOENT - case syscall.EACCES: - return -fuse.EACCES - default: - return -fuse.EIO - } + return fuseErrnoFromError(err) } return 0 @@ -843,10 +809,7 @@ func (cf *CgofuseFS) Truncate(path string, size int64, fh uint64) int { }) if err != nil { log.Err("Libfuse::Truncate : error truncating file %s [%s]", name, err.Error()) - if os.IsNotExist(err) { - return -fuse.ENOENT - } - return -fuse.EIO + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents(truncateFile, name, map[string]any{"size": size}) @@ -873,14 +836,7 @@ func (cf *CgofuseFS) Release(path string, fh uint64) int { handle.ID, err.Error(), ) - switch err { - case syscall.ENOENT: - return -fuse.ENOENT - case syscall.EACCES: - return -fuse.EACCES - default: - return -fuse.EIO - } + return fuseErrnoFromError(err) } handlemap.Delete(handle.ID) @@ -900,12 +856,7 @@ func (cf *CgofuseFS) Unlink(path string) int { err := fuseFS.NextComponent().DeleteFile(internal.DeleteFileOptions{Name: name}) if err != nil { log.Err("Libfuse::Unlink : error deleting file %s [%s]", name, err.Error()) - if os.IsNotExist(err) { - return -fuse.ENOENT - } else if os.IsPermission(err) { - return -fuse.EACCES - } - return -fuse.EIO + return fuseErrnoFromError(err) } @@ -935,13 +886,17 @@ func (cf *CgofuseFS) Rename(oldpath string, newpath string) int { } srcAttr, srcErr := fuseFS.NextComponent().GetAttr(internal.GetAttrOptions{Name: srcPath}) - if os.IsNotExist(srcErr) { + if srcErr != nil { log.Err("Libfuse::Rename : Failed to get attributes of %s [%s]", srcPath, srcErr.Error()) - return -fuse.ENOENT + return fuseErrnoFromError(srcErr) } if srcAttr.IsDir() { dstAttr, dstErr := fuseFS.NextComponent().GetAttr(internal.GetAttrOptions{Name: dstPath}) + if errors.Is(dstErr, fs.ErrPermission) { + log.Err("Libfuse::Rename : access denied for %s [%s]", dstPath, dstErr.Error()) + return -fuse.EACCES + } // ENOTDIR if dstErr == nil && !dstAttr.IsDir() { @@ -970,7 +925,7 @@ func (cf *CgofuseFS) Rename(oldpath string, newpath string) int { dstPath, err.Error(), ) - return -fuse.EIO + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents( @@ -1006,6 +961,10 @@ func (cf *CgofuseFS) Rename(oldpath string, newpath string) int { dstPath, err.Error(), ) + + if errors.Is(err, fs.ErrPermission) || errors.Is(dstErr, fs.ErrPermission) { + return -fuse.EACCES + } return -fuse.EIO } @@ -1037,7 +996,7 @@ func (cf *CgofuseFS) Symlink(target string, newpath string) int { targetPath, err.Error(), ) - return -fuse.EIO + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents(createLink, name, map[string]any{trgt: targetPath}) @@ -1062,10 +1021,7 @@ func (cf *CgofuseFS) Readlink(path string) (int, string) { ReadLink(internal.ReadLinkOptions{Name: name, Size: linkSize}) if err != nil { log.Err("Libfuse::Readlink : error reading link file %s [%s]", name, err.Error()) - if os.IsNotExist(err) { - return -fuse.ENOENT, targetPath - } - return -fuse.EIO, targetPath + return fuseErrnoFromError(err), targetPath } // Don't think we need when with using cgofuse @@ -1100,7 +1056,7 @@ func (cf *CgofuseFS) Fsync(path string, datasync bool, fh uint64) int { err := fuseFS.NextComponent().SyncFile(options) if err != nil { log.Err("Libfuse::Fsync : error syncing file %s [%s]", handle.Path, err.Error()) - return -fuse.EIO + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents(syncFile, handle.Path, nil) @@ -1122,7 +1078,7 @@ func (cf *CgofuseFS) Fsyncdir(path string, datasync bool, fh uint64) int { err := fuseFS.NextComponent().SyncDir(options) if err != nil { log.Err("Libfuse::Fsyncdir : error syncing dir %s [%s]", name, err.Error()) - return -fuse.EIO + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents(syncDir, name, nil) @@ -1144,12 +1100,7 @@ func (cf *CgofuseFS) Chmod(path string, mode uint32) int { }) if err != nil { log.Err("Libfuse::Chmod : error in chmod of %s [%s]", name, err.Error()) - if os.IsNotExist(err) { - return -fuse.ENOENT - } else if os.IsPermission(err) { - return -fuse.EACCES - } - return -fuse.EIO + return fuseErrnoFromError(err) } libfuseStatsCollector.PushEvents(chmod, name, map[string]any{md: fs.FileMode(mode)}) diff --git a/component/libfuse/libfuse2_handler_test_wrapper.go b/component/libfuse/libfuse2_handler_test_wrapper.go index 8904e88a5..e9e3d7962 100644 --- a/component/libfuse/libfuse2_handler_test_wrapper.go +++ b/component/libfuse/libfuse2_handler_test_wrapper.go @@ -29,6 +29,7 @@ import ( "errors" "fmt" "io/fs" + "os" "runtime" "strings" "syscall" @@ -157,6 +158,35 @@ func testStatFsNotPopulated(suite *libfuseTestSuite) { suite.assert.NotZero(buf.Namemax) } +func testStatFsCloudStorageCapacity(suite *libfuseTestSuite) { + defer suite.cleanupTest() + path := "/" + + fuseFS.displayCapacityMb = 1 + attr := &common.Statfs_t{ + Frsize: 1, + Blocks: 10, + Bavail: 0, + Bfree: 0, + Bsize: 1024, + Files: 6, + Ffree: 7, + Namemax: 255, + } + suite.mock.EXPECT().StatFs().Return(attr, true, nil) + + buf := &fuse.Statfs_t{} + ret := cfuseFS.Statfs(path, buf) + + suite.assert.Equal(0, ret) + blocksUnavailable := uint64(attr.Blocks - attr.Bavail) + displayCapacityBlocks := fuseFS.displayCapacityMb * common.MbToBytes / uint64(attr.Bsize) + expectedBlocks := max(displayCapacityBlocks, blocksUnavailable) + suite.assert.Equal(expectedBlocks, buf.Blocks) + suite.assert.Equal(expectedBlocks-blocksUnavailable, buf.Bavail) + suite.assert.Equal(expectedBlocks-uint64(attr.Blocks-attr.Bfree), buf.Bfree) +} + func testStatFsError(suite *libfuseTestSuite) { defer suite.cleanupTest() path := "/" @@ -184,6 +214,30 @@ func testMkDirError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testMkDirErrorPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + mode := fs.FileMode(0775) + options := internal.CreateDirOptions{Name: name, Mode: mode} + suite.mock.EXPECT().CreateDir(options).Return(os.ErrPermission) + + err := cfuseFS.Mkdir(path, 0775) + suite.assert.Equal(-fuse.EACCES, err) +} + +func testMkDirErrorExist(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + mode := fs.FileMode(0775) + options := internal.CreateDirOptions{Name: name, Mode: mode} + suite.mock.EXPECT().CreateDir(options).Return(os.ErrExist) + + err := cfuseFS.Mkdir(path, 0775) + suite.assert.Equal(-fuse.EEXIST, err) +} + // testMkDirErrorAttrExist only runs on Windows to test the case that the directory already exists // and the attributes state it is a directory. func testMkDirErrorAttrExist(suite *libfuseTestSuite) { @@ -201,6 +255,355 @@ func testMkDirErrorAttrExist(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EEXIST, err) } +func testTrimFusePath(suite *libfuseTestSuite) { + defer suite.cleanupTest() + suite.assert.Empty(trimFusePath("")) + suite.assert.Equal("path", trimFusePath("/path")) + suite.assert.Equal("path", trimFusePath("path")) +} + +func testNewCgofuseFS(suite *libfuseTestSuite) { + defer suite.cleanupTest() + cgofuse := NewcgofuseFS() + suite.assert.NotNil(cgofuse) +} + +func testGetAttrRoot(suite *libfuseTestSuite) { + defer suite.cleanupTest() + cfuseFS.uid = 1234 + cfuseFS.gid = 5678 + buf := &fuse.Stat_t{} + err := cfuseFS.Getattr("/", buf, 0) + suite.assert.Equal(0, err) + suite.assert.Equal(uint32(1234), buf.Uid) + suite.assert.Equal(uint32(5678), buf.Gid) + suite.assert.Equal(int64(4096), buf.Size) +} + +func testGetAttrIgnoredFile(suite *libfuseTestSuite) { + defer suite.cleanupTest() + buf := &fuse.Stat_t{} + err := cfuseFS.Getattr("/.Trash", buf, 0) + suite.assert.Equal(-fuse.ENOENT, err) +} + +func testGetAttrErrors(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + option := internal.GetAttrOptions{Name: name} + buf := &fuse.Stat_t{} + + suite.mock.EXPECT().GetAttr(option).Return(nil, syscall.ENOENT) + suite.assert.Equal(-fuse.ENOENT, cfuseFS.Getattr("/"+name, buf, 0)) + + suite.mock.EXPECT().GetAttr(option).Return(nil, syscall.EACCES) + suite.assert.Equal(-fuse.EACCES, cfuseFS.Getattr("/"+name, buf, 0)) + + suite.mock.EXPECT().GetAttr(option).Return(nil, errors.New("boom")) + suite.assert.Equal(-fuse.EIO, cfuseFS.Getattr("/"+name, buf, 0)) +} + +func testOpendirAndReleasedir(suite *libfuseTestSuite) { + defer suite.cleanupTest() + err, fh := cfuseFS.Opendir("/dir") + suite.assert.Equal(0, err) + release := cfuseFS.Releasedir("/dir", fh) + suite.assert.Equal(0, release) +} + +func testReleasedirMissingHandle(suite *libfuseTestSuite) { + defer suite.cleanupTest() + release := cfuseFS.Releasedir("/dir", 999) + suite.assert.Equal(-fuse.EBADF, release) +} + +func testServeCachedEntries(suite *libfuseTestSuite) { + defer suite.cleanupTest() + cacheInfo := &dirChildCache{ + sIndex: 0, + eIndex: 2, + length: 2, + children: []*internal.ObjAttr{ + {Name: "a", Flags: internal.NewFileBitMap()}, + {Name: "b", Flags: internal.NewFileBitMap()}, + }, + lastPage: true, + } + fillCount := 0 + fill := func(name string, stat *fuse.Stat_t, ofst int64) bool { + fillCount++ + return true + } + + nextOffset, done := serveCachedEntries(cacheInfo, 0, fill) + suite.assert.Equal(uint64(2), nextOffset) + suite.assert.True(done) + suite.assert.Equal(2, fillCount) +} + +func testServeCachedEntriesStopEarly(suite *libfuseTestSuite) { + defer suite.cleanupTest() + cacheInfo := &dirChildCache{ + sIndex: 0, + eIndex: 2, + length: 2, + children: []*internal.ObjAttr{ + {Name: "a", Flags: internal.NewFileBitMap()}, + {Name: "b", Flags: internal.NewFileBitMap()}, + }, + lastPage: false, + } + fillCount := 0 + fill := func(name string, stat *fuse.Stat_t, ofst int64) bool { + fillCount++ + return false + } + + nextOffset, done := serveCachedEntries(cacheInfo, 0, fill) + suite.assert.Equal(uint64(1), nextOffset) + suite.assert.True(done) + suite.assert.Equal(1, fillCount) +} + +func testFillStatModes(suite *libfuseTestSuite) { + defer suite.cleanupTest() + lf := suite.libfuse + lf.dirPermission = 0777 + lf.filePermission = 0666 + + dirAttr := &internal.ObjAttr{Flags: internal.NewDirBitMap(), Mode: 0} + fileAttr := &internal.ObjAttr{Flags: internal.NewFileBitMap(), Mode: 0} + linkAttr := &internal.ObjAttr{Flags: internal.NewSymlinkBitMap(), Mode: 0} + + dirStat := &fuse.Stat_t{} + fileStat := &fuse.Stat_t{} + linkStat := &fuse.Stat_t{} + lf.fillStat(dirAttr, dirStat) + lf.fillStat(fileAttr, fileStat) + lf.fillStat(linkAttr, linkStat) + + suite.assert.NotEqual(dirStat.Mode&fuse.S_IFDIR, 0) + suite.assert.NotEqual(fileStat.Mode&fuse.S_IFREG, 0) + suite.assert.NotEqual(linkStat.Mode&fuse.S_IFLNK, 0) +} + +func testFillStatModeDefault(suite *libfuseTestSuite) { + defer suite.cleanupTest() + lf := suite.libfuse + lf.dirPermission = 0755 + lf.filePermission = 0644 + + attr := &internal.ObjAttr{Flags: internal.NewDirBitMap(), Mode: 0} + attr.Flags.Set(internal.PropFlagModeDefault) + + st := &fuse.Stat_t{} + lf.fillStat(attr, st) + + suite.assert.NotEqual(st.Mode&fuse.S_IFDIR, 0) + suite.assert.Equal(uint32(lf.dirPermission), st.Mode&0x1ff) +} + +func testReadMissingHandle(suite *libfuseTestSuite) { + defer suite.cleanupTest() + buf := make([]byte, 8) + err := cfuseFS.Read("/path", buf, 0, 999) + suite.assert.Equal(-fuse.EBADF, err) +} + +func testReadCachedHandle(suite *libfuseTestSuite) { + defer suite.cleanupTest() + file, err := os.CreateTemp("", "cfuse-read-*") + suite.assert.NoError(err) + defer os.Remove(file.Name()) + defer file.Close() + + _, err = file.Write([]byte("hello")) + suite.assert.NoError(err) + + handle := handlemap.NewHandle("file") + handle.Flags.Set(handlemap.HandleFlagCached) + handle.SetFileObject(file) + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := make([]byte, 5) + read := cfuseFS.Read("/file", buf, 0, uint64(fh)) + suite.assert.Equal(5, read) + suite.assert.Equal("hello", string(buf)) +} + +func testReadCachedHandleEOF(suite *libfuseTestSuite) { + defer suite.cleanupTest() + file, err := os.CreateTemp("", "cfuse-read-eof-*") + suite.assert.NoError(err) + defer os.Remove(file.Name()) + defer file.Close() + + _, err = file.Write([]byte("hi")) + suite.assert.NoError(err) + + handle := handlemap.NewHandle("file") + handle.Flags.Set(handlemap.HandleFlagCached) + handle.SetFileObject(file) + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := make([]byte, 5) + read := cfuseFS.Read("/file", buf, 0, uint64(fh)) + suite.assert.Equal(2, read) + suite.assert.Equal("hi", string(buf[:read])) +} + +func testReadFromComponent(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := make([]byte, 4) + suite.mock.EXPECT().ReadInBuffer(gomock.Any()).Return(4, nil) + read := cfuseFS.Read("/file", buf, 0, uint64(fh)) + suite.assert.Equal(4, read) +} + +func testReadAccessDenied(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := make([]byte, 4) + suite.mock.EXPECT().ReadInBuffer(gomock.Any()).Return(0, os.ErrPermission) + read := cfuseFS.Read("/file", buf, 0, uint64(fh)) + suite.assert.Equal(-fuse.EACCES, read) +} + +func testReadError(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := make([]byte, 4) + suite.mock.EXPECT().ReadInBuffer(gomock.Any()).Return(0, errors.New("boom")) + read := cfuseFS.Read("/file", buf, 0, uint64(fh)) + suite.assert.Equal(-fuse.EIO, read) +} + +func testWriteMissingHandle(suite *libfuseTestSuite) { + defer suite.cleanupTest() + buf := []byte("data") + err := cfuseFS.Write("/path", buf, 0, 999) + suite.assert.Equal(-fuse.EBADF, err) +} + +func testWriteSuccess(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := []byte("data") + suite.mock.EXPECT().WriteFile(gomock.Any()).Return(len(buf), nil) + written := cfuseFS.Write("/file", buf, 0, uint64(fh)) + suite.assert.Equal(len(buf), written) +} + +func testWriteError(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := []byte("data") + suite.mock.EXPECT().WriteFile(gomock.Any()).Return(0, errors.New("boom")) + written := cfuseFS.Write("/file", buf, 0, uint64(fh)) + suite.assert.Equal(-fuse.EIO, written) +} + +func testWriteAccessDenied(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + buf := []byte("data") + suite.mock.EXPECT().WriteFile(gomock.Any()).Return(0, os.ErrPermission) + written := cfuseFS.Write("/file", buf, 0, uint64(fh)) + suite.assert.Equal(-fuse.EACCES, written) +} + +func testFlushNotDirty(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + result := cfuseFS.Flush("/file", uint64(fh)) + suite.assert.Equal(0, result) +} + +func testFlushErrors(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + handle.Flags.Set(handlemap.HandleFlagDirty) + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + suite.mock.EXPECT().FlushFile(gomock.Any()).Return(syscall.ENOENT) + suite.assert.Equal(-fuse.ENOENT, cfuseFS.Flush("/file", uint64(fh))) + + suite.mock.EXPECT().FlushFile(gomock.Any()).Return(syscall.EACCES) + suite.assert.Equal(-fuse.EACCES, cfuseFS.Flush("/file", uint64(fh))) + + suite.mock.EXPECT().FlushFile(gomock.Any()).Return(errors.New("boom")) + suite.assert.Equal(-fuse.EIO, cfuseFS.Flush("/file", uint64(fh))) +} + +func testReleaseMissingHandle(suite *libfuseTestSuite) { + defer suite.cleanupTest() + err := cfuseFS.Release("/file", 999) + suite.assert.Equal(-fuse.EBADF, err) +} + +func testReleaseError(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + suite.mock.EXPECT().ReleaseFile(gomock.Any()).Return(syscall.ENOENT) + err := cfuseFS.Release("/file", uint64(fh)) + suite.assert.Equal(-fuse.ENOENT, err) +} + +func testReleaseErrorAccess(suite *libfuseTestSuite) { + defer suite.cleanupTest() + handle := handlemap.NewHandle("file") + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + suite.mock.EXPECT().ReleaseFile(gomock.Any()).Return(syscall.EACCES) + err := cfuseFS.Release("/file", uint64(fh)) + suite.assert.Equal(-fuse.EACCES, err) +} + +func testUnsupportedOps(suite *libfuseTestSuite) { + defer suite.cleanupTest() + suite.assert.Equal(-fuse.ENOSYS, cfuseFS.Access("/path", 0)) + ret, data := cfuseFS.Getxattr("/path", "x") + suite.assert.Equal(-fuse.ENOSYS, ret) + suite.assert.Nil(data) + suite.assert.Equal(-fuse.ENOSYS, cfuseFS.Link("/a", "/b")) + suite.assert.Equal( + -fuse.ENOSYS, + cfuseFS.Listxattr("/path", func(name string) bool { return true }), + ) + suite.assert.Equal(-fuse.ENOSYS, cfuseFS.Mknod("/path", 0, 0)) + suite.assert.Equal(-fuse.ENOSYS, cfuseFS.Removexattr("/path", "x")) + suite.assert.Equal(-fuse.ENOSYS, cfuseFS.Setxattr("/path", "x", []byte("v"), 0)) +} + // TODO: ReadDir test func testReaddirMissingHandle(suite *libfuseTestSuite) { @@ -260,6 +663,164 @@ func testReaddirEmptyPageToken(suite *libfuseTestSuite) { suite.assert.False(fillCalled) } +func testReaddirPermissionError(suite *libfuseTestSuite) { + defer suite.cleanupTest() + fill := func(name string, stat *fuse.Stat_t, ofst int64) bool { return true } + + handle := handlemap.NewHandle("dir/") + cacheInfo := &dirChildCache{ + sIndex: 0, + eIndex: 2, + token: "next", + length: 2, + children: make([]*internal.ObjAttr, 0), + lastPage: false, + } + cacheDots(cacheInfo) + cacheInfo.token = "next" + cacheInfo.lastPage = false + handle.SetValue("cache", cacheInfo) + fh := handlemap.Add(handle) + defer handlemap.Delete(handle.ID) + + suite.mock.EXPECT(). + StreamDir(internal.StreamDirOptions{Name: "dir/", Token: "next"}). + Return(nil, "", os.ErrPermission) + + err := cfuseFS.Readdir("/dir", fill, 2, uint64(fh)) + suite.assert.Equal(-fuse.EACCES, err) +} + +func testCreateFuseOptionsFlags(suite *libfuseTestSuite) { + defer suite.cleanupTest() + host := fuse.NewFileSystemHost(&CgofuseFS{}) + fuseFS.directIO = false + + umask := uint32(0022) + options := createFuseOptions(host, true, true, true, false, 128, umask) + if !strings.Contains(options, "allow_other") { + suite.T().Fatal("expected allow_other in options") + } + if !strings.Contains(options, "allow_root") { + suite.T().Fatal("expected allow_root in options") + } + if !strings.Contains(options, "ro") { + suite.T().Fatal("expected ro in options") + } + expectedUmask := fmt.Sprintf("umask=%04d", umask) + if !strings.Contains(options, expectedUmask) { + suite.T().Fatal("expected umask in options") + } + if !strings.Contains(options, "kernel_cache") { + suite.T().Fatal("expected kernel_cache in options") + } +} + +func testCreateFuseOptionsDirectIO(suite *libfuseTestSuite) { + defer suite.cleanupTest() + host := fuse.NewFileSystemHost(&CgofuseFS{}) + fuseFS.directIO = true + + options := createFuseOptions(host, false, false, false, false, 128, 0) + if strings.Contains(options, "kernel_cache") { + suite.T().Fatal("did not expect kernel_cache in direct-io options") + } +} + +func testPopulateDirChildCacheReplaceCache(suite *libfuseTestSuite) { + defer suite.cleanupTest() + + handle := handlemap.NewHandle("dir/") + cacheInfo := &dirChildCache{ + sIndex: 0, + eIndex: 0, + token: "", + length: common.MaxDirListCount - 1, + children: make([]*internal.ObjAttr, 0), + lastPage: false, + } + + returnedAttrs := []*internal.ObjAttr{ + {Name: "a", Flags: internal.NewFileBitMap()}, + {Name: "b", Flags: internal.NewFileBitMap()}, + } + suite.mock.EXPECT(). + StreamDir(internal.StreamDirOptions{Name: "dir/", Token: ""}). + Return(returnedAttrs, "", nil) + + errorCode := populateDirChildCache(handle, cacheInfo, 10) + suite.assert.Equal(0, errorCode) + suite.assert.Equal(uint64(10), cacheInfo.sIndex) + suite.assert.Equal(uint64(12), cacheInfo.eIndex) + suite.assert.Equal(uint64(2), cacheInfo.length) + suite.assert.True(cacheInfo.lastPage) +} + +func testPopulateDirChildCacheLastPage(suite *libfuseTestSuite) { + defer suite.cleanupTest() + + handle := handlemap.NewHandle("dir/") + cacheInfo := &dirChildCache{ + sIndex: 0, + eIndex: 0, + token: "", + length: 0, + children: make([]*internal.ObjAttr, 0), + lastPage: true, + } + + suite.mock.EXPECT().StreamDir(gomock.Any()).Times(0) + + errorCode := populateDirChildCache(handle, cacheInfo, 0) + suite.assert.Equal(0, errorCode) +} + +func testPopulateDirChildCacheNotFound(suite *libfuseTestSuite) { + defer suite.cleanupTest() + + handle := handlemap.NewHandle("dir/") + cacheInfo := &dirChildCache{ + sIndex: 0, + eIndex: 0, + token: "", + length: 0, + children: make([]*internal.ObjAttr, 0), + lastPage: false, + } + + suite.mock.EXPECT(). + StreamDir(internal.StreamDirOptions{Name: "dir/", Token: ""}). + Return(nil, "", os.ErrNotExist) + + errorCode := populateDirChildCache(handle, cacheInfo, 0) + suite.assert.Equal(-fuse.ENOENT, errorCode) +} + +func testPopulateDirChildCacheAppend(suite *libfuseTestSuite) { + defer suite.cleanupTest() + + handle := handlemap.NewHandle("dir/") + cacheInfo := &dirChildCache{ + sIndex: 0, + eIndex: 0, + token: "", + length: 0, + children: make([]*internal.ObjAttr, 0), + lastPage: false, + } + + returnedAttrs := []*internal.ObjAttr{{Name: "a", Flags: internal.NewFileBitMap()}} + suite.mock.EXPECT(). + StreamDir(internal.StreamDirOptions{Name: "dir/", Token: ""}). + Return(returnedAttrs, "next", nil) + + errorCode := populateDirChildCache(handle, cacheInfo, 0) + suite.assert.Equal(0, errorCode) + suite.assert.Equal(uint64(1), cacheInfo.eIndex) + suite.assert.Equal(uint64(1), cacheInfo.length) + suite.assert.False(cacheInfo.lastPage) +} + func testRmDir(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -297,6 +858,32 @@ func testRmDirError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testRmDirNotExists(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + isDirEmptyOptions := internal.IsDirEmptyOptions{Name: name} + suite.mock.EXPECT().IsDirEmpty(isDirEmptyOptions).Return(true) + deleteDirOptions := internal.DeleteDirOptions{Name: name} + suite.mock.EXPECT().DeleteDir(deleteDirOptions).Return(os.ErrNotExist) + + err := cfuseFS.Rmdir(path) + suite.assert.Equal(-fuse.ENOENT, err) +} + +func testRmDirPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + isDirEmptyOptions := internal.IsDirEmptyOptions{Name: name} + suite.mock.EXPECT().IsDirEmpty(isDirEmptyOptions).Return(true) + deleteDirOptions := internal.DeleteDirOptions{Name: name} + suite.mock.EXPECT().DeleteDir(deleteDirOptions).Return(os.ErrPermission) + + err := cfuseFS.Rmdir(path) + suite.assert.Equal(-fuse.EACCES, err) +} + func testCreate(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -331,6 +918,30 @@ func testCreateError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testCreateErrorExists(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + mode := fs.FileMode(0775) + options := internal.CreateFileOptions{Name: name, Mode: mode} + suite.mock.EXPECT().CreateFile(options).Return(&handlemap.Handle{}, os.ErrExist) + + err, _ := cfuseFS.Create(path, 0, uint32(mode)) + suite.assert.Equal(-fuse.EEXIST, err) +} + +func testCreateErrorPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + mode := fs.FileMode(0775) + options := internal.CreateFileOptions{Name: name, Mode: mode} + suite.mock.EXPECT().CreateFile(options).Return(&handlemap.Handle{}, os.ErrPermission) + + err, _ := cfuseFS.Create(path, 0, uint32(mode)) + suite.assert.Equal(-fuse.EACCES, err) +} + func testRenameFileFastPathSuccess(suite *libfuseTestSuite) { defer suite.cleanupTest() @@ -385,6 +996,54 @@ func testRenameFileFastPathError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testRenameDirNotEmpty(suite *libfuseTestSuite) { + defer suite.cleanupTest() + + src := "src" + dst := "dst" + srcAttr := &internal.ObjAttr{Flags: internal.NewDirBitMap()} + dstAttr := &internal.ObjAttr{Flags: internal.NewDirBitMap()} + + suite.mock.EXPECT().GetAttr(internal.GetAttrOptions{Name: src}).Return(srcAttr, nil) + suite.mock.EXPECT().GetAttr(internal.GetAttrOptions{Name: dst}).Return(dstAttr, nil) + suite.mock.EXPECT().IsDirEmpty(internal.IsDirEmptyOptions{Name: dst}).Return(false) + + err := cfuseFS.Rename("/"+src, "/"+dst) + suite.assert.Equal(-fuse.ENOTEMPTY, err) +} + +func testRenameDirDstNotDir(suite *libfuseTestSuite) { + defer suite.cleanupTest() + + src := "src" + dst := "dst" + srcAttr := &internal.ObjAttr{Flags: internal.NewDirBitMap()} + dstAttr := &internal.ObjAttr{Flags: internal.NewFileBitMap()} + + suite.mock.EXPECT().GetAttr(internal.GetAttrOptions{Name: src}).Return(srcAttr, nil) + suite.mock.EXPECT().GetAttr(internal.GetAttrOptions{Name: dst}).Return(dstAttr, nil) + + err := cfuseFS.Rename("/"+src, "/"+dst) + suite.assert.Equal(-fuse.ENOTDIR, err) +} + +func testRenameDirPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + + src := "src" + dst := "dst" + srcAttr := &internal.ObjAttr{Flags: internal.NewDirBitMap()} + + suite.mock.EXPECT().GetAttr(internal.GetAttrOptions{Name: src}).Return(srcAttr, nil) + suite.mock.EXPECT().GetAttr(internal.GetAttrOptions{Name: dst}).Return(nil, syscall.ENOENT) + suite.mock.EXPECT(). + RenameDir(internal.RenameDirOptions{Src: src, Dst: dst}). + Return(os.ErrPermission) + + err := cfuseFS.Rename("/"+src, "/"+dst) + suite.assert.Equal(-fuse.EACCES, err) +} + func testOpen(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -509,6 +1168,19 @@ func testOpenError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testOpenPermissionError(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + mode := fs.FileMode(fuseFS.filePermission) + flags := fuse.O_RDWR & 0xffffffff + options := internal.OpenFileOptions{Name: name, Flags: flags, Mode: mode} + suite.mock.EXPECT().OpenFile(options).Return(&handlemap.Handle{}, os.ErrPermission) + + err, _ := cfuseFS.Open(path, flags) + suite.assert.Equal(-fuse.EACCES, err) +} + func testTruncate(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -533,6 +1205,18 @@ func testTruncateError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testTruncatePermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + size := int64(1024) + options := internal.TruncateFileOptions{Name: name, OldSize: -1, NewSize: size} + suite.mock.EXPECT().TruncateFile(options).Return(os.ErrPermission) + + err := cfuseFS.Truncate(path, size, 0) + suite.assert.Equal(-fuse.EACCES, err) +} + func testFTruncate(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -587,6 +1271,17 @@ func testUnlinkNotExists(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.ENOENT, err) } +func testUnlinkPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + options := internal.DeleteFileOptions{Name: name} + suite.mock.EXPECT().DeleteFile(options).Return(os.ErrPermission) + + err := cfuseFS.Unlink(path) + suite.assert.Equal(-fuse.EACCES, err) +} + func testUnlinkError(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -626,6 +1321,18 @@ func testSymlinkError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testSymlinkPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + target := "target" + path := "/" + name + options := internal.CreateLinkOptions{Name: name, Target: target} + suite.mock.EXPECT().CreateLink(options).Return(os.ErrPermission) + + err := cfuseFS.Symlink(target, path) + suite.assert.Equal(-fuse.EACCES, err) +} + func testReadLink(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -670,6 +1377,20 @@ func testReadLinkError(suite *libfuseTestSuite) { suite.assert.NotEqual("target", target) } +func testReadLinkPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + options := internal.ReadLinkOptions{Name: name} + suite.mock.EXPECT().ReadLink(options).Return("", os.ErrPermission) + getAttrOpt := internal.GetAttrOptions{Name: name} + suite.mock.EXPECT().GetAttr(getAttrOpt).Return(nil, nil) + + err, target := cfuseFS.Readlink(path) + suite.assert.Equal(-fuse.EACCES, err) + suite.assert.Empty(target) +} + func testFsync(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -725,6 +1446,29 @@ func testFsyncError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testFsyncPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + mode := fs.FileMode(fuseFS.filePermission) + flags := fuse.O_RDWR & 0xffffffff + handle := &handlemap.Handle{} + + openOptions := internal.OpenFileOptions{Name: name, Flags: flags, Mode: mode} + suite.mock.EXPECT().OpenFile(openOptions).Return(handle, nil) + _, fh := cfuseFS.Open(path, flags) + suite.assert.NotEqual(uint64(0), fh) + + // Need to convert the ID to the correct filehandle for mocking + handle.ID = (handlemap.HandleID)(fh) + + options := internal.SyncFileOptions{Handle: handle} + suite.mock.EXPECT().SyncFile(options).Return(os.ErrPermission) + + err := cfuseFS.Fsync(path, false, fh) + suite.assert.Equal(-fuse.EACCES, err) +} + func testFsyncDir(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" @@ -747,6 +1491,17 @@ func testFsyncDirError(suite *libfuseTestSuite) { suite.assert.Equal(-fuse.EIO, err) } +func testFsyncDirPermission(suite *libfuseTestSuite) { + defer suite.cleanupTest() + name := "path" + path := "/" + name + options := internal.SyncDirOptions{Name: name} + suite.mock.EXPECT().SyncDir(options).Return(os.ErrPermission) + + err := cfuseFS.Fsyncdir(path, false, 0) + suite.assert.Equal(-fuse.EACCES, err) +} + func testChmod(suite *libfuseTestSuite) { defer suite.cleanupTest() name := "path" diff --git a/component/libfuse/libfuse_handler_test.go b/component/libfuse/libfuse_handler_test.go index c16214fec..c3569fb89 100644 --- a/component/libfuse/libfuse_handler_test.go +++ b/component/libfuse/libfuse_handler_test.go @@ -27,9 +27,11 @@ package libfuse import ( "io/fs" + "strings" "testing" "github.com/Seagate/cloudfuse/common" + "github.com/Seagate/cloudfuse/common/config" "github.com/stretchr/testify/suite" ) @@ -82,6 +84,32 @@ func (suite *libfuseTestSuite) TestConfig() { suite.assert.False(suite.libfuse.directIO) } +func (suite *libfuseTestSuite) TestGenConfigDirectIO() { + defer suite.cleanupTest() + suite.cleanupTest() + + err := config.ReadConfigFromReader(strings.NewReader("direct-io: true\n")) + suite.assert.NoError(err) + + gen := suite.libfuse.GenConfig() + suite.assert.Contains(gen, "attribute-expiration-sec: 0") + suite.assert.Contains(gen, "entry-expiration-sec: 0") + suite.assert.Contains(gen, "negative-entry-expiration-sec: 0") +} + +func (suite *libfuseTestSuite) TestGenConfigDefault() { + defer suite.cleanupTest() + suite.cleanupTest() + + err := config.ReadConfigFromReader(strings.NewReader("direct-io: false\n")) + suite.assert.NoError(err) + + gen := suite.libfuse.GenConfig() + suite.assert.Contains(gen, "attribute-expiration-sec: 120") + suite.assert.Contains(gen, "entry-expiration-sec: 120") + suite.assert.Contains(gen, "negative-entry-expiration-sec: 120") +} + func (suite *libfuseTestSuite) TestConfigDirectIO() { defer suite.cleanupTest() suite.cleanupTest() // clean up the default libfuse generated @@ -157,6 +185,20 @@ func (suite *libfuseTestSuite) TestConfigDefaultPermission() { suite.assert.True(suite.libfuse.directIO) } +func (suite *libfuseTestSuite) TestConfigRootAndThreads() { + defer suite.cleanupTest() + suite.cleanupTest() + config := "allow-root: true\nnonempty: true\nlibfuse:\n max-fuse-threads: 256\n umask: 22\n uid: 1001\n gid: 1002\n" + suite.setupTestHelper(config) + + suite.assert.True(suite.libfuse.allowRoot) + suite.assert.True(suite.libfuse.nonEmptyMount) + suite.assert.Equal(uint32(256), suite.libfuse.maxFuseThreads) + suite.assert.Equal(uint32(22), suite.libfuse.umask) + suite.assert.Equal(uint32(1001), suite.libfuse.ownerUID) + suite.assert.Equal(uint32(1002), suite.libfuse.ownerGID) +} + func (suite *libfuseTestSuite) TestConfigDisableKernelCache() { defer suite.cleanupTest() suite.cleanupTest() // clean up the default libfuse generated @@ -230,6 +272,26 @@ func (suite *libfuseTestSuite) TestIgnoreAppendFlag() { suite.assert.True(suite.libfuse.ignoreOpenFlags) } +func (suite *libfuseTestSuite) TestTrimFusePath() { + testTrimFusePath(suite) +} + +func (suite *libfuseTestSuite) TestNewCgofuseFS() { + testNewCgofuseFS(suite) +} + +func (suite *libfuseTestSuite) TestGetAttrRoot() { + testGetAttrRoot(suite) +} + +func (suite *libfuseTestSuite) TestGetAttrIgnoredFile() { + testGetAttrIgnoredFile(suite) +} + +func (suite *libfuseTestSuite) TestGetAttrErrors() { + testGetAttrErrors(suite) +} + // getattr func (suite *libfuseTestSuite) TestMkDir() { @@ -240,6 +302,14 @@ func (suite *libfuseTestSuite) TestMkDirError() { testMkDirError(suite) } +func (suite *libfuseTestSuite) TestMkDirErrorPermission() { + testMkDirErrorPermission(suite) +} + +func (suite *libfuseTestSuite) TestMkDirErrorExist() { + testMkDirErrorExist(suite) +} + func (suite *libfuseTestSuite) TestMkDirErrorAttrExist() { testMkDirErrorAttrExist(suite) } @@ -258,6 +328,58 @@ func (suite *libfuseTestSuite) TestReaddirEmptyPageToken() { testReaddirEmptyPageToken(suite) } +func (suite *libfuseTestSuite) TestReleasedirMissingHandle() { + testReleasedirMissingHandle(suite) +} + +func (suite *libfuseTestSuite) TestReaddirPermissionError() { + testReaddirPermissionError(suite) +} + +func (suite *libfuseTestSuite) TestPopulateDirChildCacheReplaceCache() { + testPopulateDirChildCacheReplaceCache(suite) +} + +func (suite *libfuseTestSuite) TestPopulateDirChildCacheLastPage() { + testPopulateDirChildCacheLastPage(suite) +} + +func (suite *libfuseTestSuite) TestPopulateDirChildCacheNotFound() { + testPopulateDirChildCacheNotFound(suite) +} + +func (suite *libfuseTestSuite) TestPopulateDirChildCacheAppend() { + testPopulateDirChildCacheAppend(suite) +} + +func (suite *libfuseTestSuite) TestCreateFuseOptionsFlags() { + testCreateFuseOptionsFlags(suite) +} + +func (suite *libfuseTestSuite) TestCreateFuseOptionsDirectIO() { + testCreateFuseOptionsDirectIO(suite) +} + +func (suite *libfuseTestSuite) TestFillStatModes() { + testFillStatModes(suite) +} + +func (suite *libfuseTestSuite) TestFillStatModeDefault() { + testFillStatModeDefault(suite) +} + +func (suite *libfuseTestSuite) TestOpendirAndReleasedir() { + testOpendirAndReleasedir(suite) +} + +func (suite *libfuseTestSuite) TestServeCachedEntries() { + testServeCachedEntries(suite) +} + +func (suite *libfuseTestSuite) TestServeCachedEntriesStopEarly() { + testServeCachedEntriesStopEarly(suite) +} + func (suite *libfuseTestSuite) TestRmDir() { testRmDir(suite) } @@ -270,6 +392,14 @@ func (suite *libfuseTestSuite) TestRmDirError() { testRmDirError(suite) } +func (suite *libfuseTestSuite) TestRmDirNotExists() { + testRmDirNotExists(suite) +} + +func (suite *libfuseTestSuite) TestRmDirPermission() { + testRmDirPermission(suite) +} + func (suite *libfuseTestSuite) TestCreate() { testCreate(suite) } @@ -278,6 +408,14 @@ func (suite *libfuseTestSuite) TestCreateError() { testCreateError(suite) } +func (suite *libfuseTestSuite) TestCreateErrorExists() { + testCreateErrorExists(suite) +} + +func (suite *libfuseTestSuite) TestCreateErrorPermission() { + testCreateErrorPermission(suite) +} + func (suite *libfuseTestSuite) TestOpen() { testOpen(suite) } @@ -302,12 +440,64 @@ func (suite *libfuseTestSuite) TestOpenError() { testOpenError(suite) } +func (suite *libfuseTestSuite) TestOpenPermissionError() { + testOpenPermissionError(suite) +} + // read +func (suite *libfuseTestSuite) TestReadMissingHandle() { + testReadMissingHandle(suite) +} + +func (suite *libfuseTestSuite) TestReadCachedHandle() { + testReadCachedHandle(suite) +} + +func (suite *libfuseTestSuite) TestReadCachedHandleEOF() { + testReadCachedHandleEOF(suite) +} + +func (suite *libfuseTestSuite) TestReadFromComponent() { + testReadFromComponent(suite) +} + +func (suite *libfuseTestSuite) TestReadAccessDenied() { + testReadAccessDenied(suite) +} + +func (suite *libfuseTestSuite) TestReadError() { + testReadError(suite) +} + // write +func (suite *libfuseTestSuite) TestWriteMissingHandle() { + testWriteMissingHandle(suite) +} + +func (suite *libfuseTestSuite) TestWriteSuccess() { + testWriteSuccess(suite) +} + +func (suite *libfuseTestSuite) TestWriteError() { + testWriteError(suite) +} + +func (suite *libfuseTestSuite) TestWriteAccessDenied() { + testWriteAccessDenied(suite) +} + // flush +func (suite *libfuseTestSuite) TestFlushNotDirty() { + testFlushNotDirty(suite) +} + +func (suite *libfuseTestSuite) TestFlushErrors() { + testFlushErrors(suite) +} + func (suite *libfuseTestSuite) TestTruncate() { testTruncate(suite) } @@ -316,6 +506,10 @@ func (suite *libfuseTestSuite) TestTruncateError() { testTruncateError(suite) } +func (suite *libfuseTestSuite) TestTruncatePermission() { + testTruncatePermission(suite) +} + func (suite *libfuseTestSuite) TestFTruncate() { testFTruncate(suite) } @@ -326,6 +520,18 @@ func (suite *libfuseTestSuite) TestFTruncateError() { // release +func (suite *libfuseTestSuite) TestReleaseMissingHandle() { + testReleaseMissingHandle(suite) +} + +func (suite *libfuseTestSuite) TestReleaseError() { + testReleaseError(suite) +} + +func (suite *libfuseTestSuite) TestReleaseErrorAccess() { + testReleaseErrorAccess(suite) +} + func (suite *libfuseTestSuite) TestUnlink() { testUnlink(suite) } @@ -334,6 +540,10 @@ func (suite *libfuseTestSuite) TestUnlinkNotExists() { testUnlinkNotExists(suite) } +func (suite *libfuseTestSuite) TestUnlinkPermission() { + testUnlinkPermission(suite) +} + func (suite *libfuseTestSuite) TestUnlinkError() { testUnlinkError(suite) } @@ -350,6 +560,18 @@ func (suite *libfuseTestSuite) TestRenameFileFastPathError() { testRenameFileFastPathError(suite) } +func (suite *libfuseTestSuite) TestRenameDirNotEmpty() { + testRenameDirNotEmpty(suite) +} + +func (suite *libfuseTestSuite) TestRenameDirDstNotDir() { + testRenameDirDstNotDir(suite) +} + +func (suite *libfuseTestSuite) TestRenameDirPermission() { + testRenameDirPermission(suite) +} + func (suite *libfuseTestSuite) TestSymlink() { testSymlink(suite) } @@ -358,6 +580,10 @@ func (suite *libfuseTestSuite) TestSymlinkError() { testSymlinkError(suite) } +func (suite *libfuseTestSuite) TestSymlinkPermission() { + testSymlinkPermission(suite) +} + func (suite *libfuseTestSuite) TestReadLink() { testReadLink(suite) } @@ -370,6 +596,10 @@ func (suite *libfuseTestSuite) TestReadLinkError() { testReadLinkError(suite) } +func (suite *libfuseTestSuite) TestReadLinkPermission() { + testReadLinkPermission(suite) +} + func (suite *libfuseTestSuite) TestFsync() { testFsync(suite) } @@ -382,6 +612,10 @@ func (suite *libfuseTestSuite) TestFsyncError() { testFsyncError(suite) } +func (suite *libfuseTestSuite) TestFsyncPermission() { + testFsyncPermission(suite) +} + func (suite *libfuseTestSuite) TestFsyncDir() { testFsyncDir(suite) } @@ -390,6 +624,10 @@ func (suite *libfuseTestSuite) TestFsyncDirError() { testFsyncDirError(suite) } +func (suite *libfuseTestSuite) TestFsyncDirPermission() { + testFsyncDirPermission(suite) +} + func (suite *libfuseTestSuite) TestChmod() { testChmod(suite) } @@ -406,6 +644,10 @@ func (suite *libfuseTestSuite) TestStatFsNotPopulated() { testStatFsNotPopulated(suite) } +func (suite *libfuseTestSuite) TestStatFsCloudStorageCapacity() { + testStatFsCloudStorageCapacity(suite) +} + func (suite *libfuseTestSuite) TestStatFsError() { testStatFsError(suite) } @@ -422,6 +664,10 @@ func (suite *libfuseTestSuite) TestUtimens() { testUtimens(suite) } +func (suite *libfuseTestSuite) TestUnsupportedOps() { + testUnsupportedOps(suite) +} + // In order for 'go test' to run this suite, we need to create // a normal test function and pass our suite to suite.Run func TestLibfuseTestSuite(t *testing.T) { From 0c8b1c548ba296ab59351447db5b01b6c09184aa Mon Sep 17 00:00:00 2001 From: James Fantin-Hardesty <24646452+jfantinhardesty@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:53:49 -0700 Subject: [PATCH 2/2] Fix lint issues --- component/libfuse/libfuse2_handler_test_wrapper.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/component/libfuse/libfuse2_handler_test_wrapper.go b/component/libfuse/libfuse2_handler_test_wrapper.go index e9e3d7962..f56e99bb6 100644 --- a/component/libfuse/libfuse2_handler_test_wrapper.go +++ b/component/libfuse/libfuse2_handler_test_wrapper.go @@ -382,9 +382,9 @@ func testFillStatModes(suite *libfuseTestSuite) { lf.fillStat(fileAttr, fileStat) lf.fillStat(linkAttr, linkStat) - suite.assert.NotEqual(dirStat.Mode&fuse.S_IFDIR, 0) - suite.assert.NotEqual(fileStat.Mode&fuse.S_IFREG, 0) - suite.assert.NotEqual(linkStat.Mode&fuse.S_IFLNK, 0) + suite.assert.NotEqual(0, dirStat.Mode&fuse.S_IFDIR) + suite.assert.NotEqual(0, fileStat.Mode&fuse.S_IFREG) + suite.assert.NotEqual(0, linkStat.Mode&fuse.S_IFLNK) } func testFillStatModeDefault(suite *libfuseTestSuite) { @@ -399,7 +399,7 @@ func testFillStatModeDefault(suite *libfuseTestSuite) { st := &fuse.Stat_t{} lf.fillStat(attr, st) - suite.assert.NotEqual(st.Mode&fuse.S_IFDIR, 0) + suite.assert.NotEqual(0, st.Mode&fuse.S_IFDIR) suite.assert.Equal(uint32(lf.dirPermission), st.Mode&0x1ff) }