From cda8cc22bcd2cea397ee833f39bd1bad0b4b4ea0 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Tue, 23 Aug 2022 10:47:18 +0200 Subject: [PATCH] Implement lseek syscall in FUSE (#3491) See the man page of lseek: https://man7.org/linux/man-pages/man2/lseek.2.html --- weed/mount/weedfs_file_lseek.go | 110 +++++++++++++++++++++++++++++++ weed/mount/weedfs_unsupported.go | 7 -- 2 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 weed/mount/weedfs_file_lseek.go diff --git a/weed/mount/weedfs_file_lseek.go b/weed/mount/weedfs_file_lseek.go new file mode 100644 index 000000000..ed495f5b5 --- /dev/null +++ b/weed/mount/weedfs_file_lseek.go @@ -0,0 +1,110 @@ +package mount + +import ( + "syscall" + + "github.com/hanwen/go-fuse/v2/fuse" + + "github.com/seaweedfs/seaweedfs/weed/filer" + "github.com/seaweedfs/seaweedfs/weed/glog" +) + +// These are non-POSIX extensions +const ( + SEEK_DATA uint32 = 3 // seek to next data after the offset + SEEK_HOLE uint32 = 4 // seek to next hole after the offset + ENXIO = fuse.Status(syscall.ENXIO) +) + +// Lseek finds next data or hole segments after the specified offset +// See https://man7.org/linux/man-pages/man2/lseek.2.html +func (wfs *WFS) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status { + // not a documented feature + if in.Padding != 0 { + return fuse.EINVAL + } + + if in.Whence != SEEK_DATA && in.Whence != SEEK_HOLE { + return fuse.EINVAL + } + + // file must exist + fh := wfs.GetHandle(FileHandleId(in.Fh)) + if fh == nil { + return fuse.EBADF + } + + // lock the file until the proper offset was calculated + fh.Lock() + defer fh.Unlock() + fh.entryLock.Lock() + defer fh.entryLock.Unlock() + + fileSize := int64(filer.FileSize(fh.entry)) + offset := max(int64(in.Offset), 0) + + glog.V(4).Infof( + "Lseek %s fh %d in [%d,%d], whence %d", + fh.FullPath(), fh.fh, offset, fileSize, in.Whence, + ) + + // can neither seek beyond file nor seek to a hole at the end of the file with SEEK_DATA + if offset > fileSize { + return ENXIO + } else if in.Whence == SEEK_DATA && offset == fileSize { + return ENXIO + } + + // refresh view cache if necessary + if fh.entryViewCache == nil { + var err error + fh.entryViewCache, err = filer.NonOverlappingVisibleIntervals(fh.wfs.LookupFn(), fh.entry.Chunks, 0, fileSize) + if err != nil { + return fuse.EIO + } + } + + // search chunks for the offset + found, offset := searchChunks(fh, offset, fileSize, in.Whence) + if found { + out.Offset = uint64(offset) + return fuse.OK + } + + // in case we found no exact matches, we return the recommended fallbacks, that is: + // original offset for SEEK_DATA or end of file for an implicit hole + if in.Whence == SEEK_DATA { + out.Offset = in.Offset + } else { + out.Offset = uint64(fileSize) + } + + return fuse.OK +} + +// searchChunks goes through all chunks to find the correct offset +func searchChunks(fh *FileHandle, offset, fileSize int64, whence uint32) (found bool, out int64) { + chunkViews := filer.ViewFromVisibleIntervals(fh.entryViewCache, offset, fileSize) + + for _, chunkView := range chunkViews { + if offset < chunkView.LogicOffset { + if whence == SEEK_HOLE { + out = offset + } else { + out = chunkView.LogicOffset + } + + return true, out + } + + if offset >= chunkView.LogicOffset && offset < chunkView.Offset+int64(chunkView.Size) && whence == SEEK_DATA { + out = offset + + return true, out + } + + offset += int64(chunkView.Size) + } + + return +} diff --git a/weed/mount/weedfs_unsupported.go b/weed/mount/weedfs_unsupported.go index 08347aec1..a8342a2fc 100644 --- a/weed/mount/weedfs_unsupported.go +++ b/weed/mount/weedfs_unsupported.go @@ -16,13 +16,6 @@ func (wfs *WFS) Fallocate(cancel <-chan struct{}, in *fuse.FallocateIn) (code fu return fuse.ENOSYS } -/** - * Find next data or hole after the specified offset - */ -func (wfs *WFS) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status { - return fuse.ENOSYS -} - func (wfs *WFS) GetLk(cancel <-chan struct{}, in *fuse.LkIn, out *fuse.LkOut) (code fuse.Status) { return fuse.ENOSYS }