diff --git a/internal/fusefrontend/file2_api_check.go b/internal/fusefrontend/file2_api_check.go index 4a6d6a1..01f9a46 100644 --- a/internal/fusefrontend/file2_api_check.go +++ b/internal/fusefrontend/file2_api_check.go @@ -13,11 +13,11 @@ var _ = (fs.FileWriter)((*File2)(nil)) var _ = (fs.FileFsyncer)((*File2)(nil)) var _ = (fs.FileFlusher)((*File2)(nil)) var _ = (fs.FileAllocater)((*File2)(nil)) +var _ = (fs.FileLseeker)((*File2)(nil)) /* TODO var _ = (fs.FileHandle)((*File2)(nil)) var _ = (fs.FileGetlker)((*File2)(nil)) var _ = (fs.FileSetlker)((*File2)(nil)) var _ = (fs.FileSetlkwer)((*File2)(nil)) -var _ = (fs.FileLseeker)((*File2)(nil)) */ diff --git a/internal/fusefrontend/file2_holes.go b/internal/fusefrontend/file2_holes.go index 83918d2..5c314d3 100644 --- a/internal/fusefrontend/file2_holes.go +++ b/internal/fusefrontend/file2_holes.go @@ -3,7 +3,7 @@ package fusefrontend // Helper functions for sparse files (files with holes) import ( - "runtime" + "context" "syscall" "github.com/hanwen/go-fuse/v2/fs" @@ -56,37 +56,13 @@ func (f *File2) zeroPad(plainSize uint64) syscall.Errno { return errno } -// SeekData calls the lseek syscall with SEEK_DATA. It returns the offset of the -// next data bytes, skipping over file holes. -func (f *File2) SeekData(oldOffset int64) (int64, error) { - if runtime.GOOS != "linux" { - // Does MacOS support something like this? - return 0, syscall.EOPNOTSUPP - } - const SEEK_DATA = 3 - - // Convert plaintext offset to ciphertext offset and round down to the - // start of the current block. File holes smaller than a full block will - // be ignored. - blockNo := f.contentEnc.PlainOffToBlockNo(uint64(oldOffset)) - oldCipherOff := int64(f.contentEnc.BlockNoToCipherOff(blockNo)) - - // Determine the next data offset. If the old offset points to (or beyond) - // the end of the file, the Seek syscall fails with syscall.ENXIO. - newCipherOff, err := syscall.Seek(f.intFd(), oldCipherOff, SEEK_DATA) +// Lseek - FUSE call. +func (f *File2) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, syscall.Errno) { + cipherOff := f.rootNode.contentEnc.PlainSizeToCipherSize(off) + newCipherOff, err := syscall.Seek(f.intFd(), int64(cipherOff), int(whence)) if err != nil { - return 0, err + return uint64(newCipherOff), fs.ToErrno(err) } - - // Convert ciphertext offset back to plaintext offset. At this point, - // newCipherOff should always be >= contentenc.HeaderLen. Round down, - // but ensure that the result is never smaller than the initial offset - // (to avoid endless loops). - blockNo = f.contentEnc.CipherOffToBlockNo(uint64(newCipherOff)) - newOffset := int64(f.contentEnc.BlockNoToPlainOff(blockNo)) - if newOffset < oldOffset { - newOffset = oldOffset - } - - return newOffset, nil + newOff := f.contentEnc.CipherSizeToPlainSize(uint64(newCipherOff)) + return newOff, 0 } diff --git a/tests/defaults/main_test.go b/tests/defaults/main_test.go index 51f5cd2..f59ea38 100644 --- a/tests/defaults/main_test.go +++ b/tests/defaults/main_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "runtime" "strings" "sync" @@ -261,3 +262,34 @@ func TestCpWarnings(t *testing.T) { t.Fatalf("Got warnings from cp -a:\n%s", string(out)) } } + +func TestSeekData(t *testing.T) { + fn := filepath.Join(test_helpers.DefaultPlainDir, t.Name()) + f, err := os.Create(fn) + if err != nil { + t.Fatal(err) + } + var oneTiB int64 = 1024 * 1024 * 1024 * 1024 + if _, err = f.Seek(oneTiB, 0); err != nil { + t.Fatal(err) + } + if _, err = f.Write([]byte("foo")); err != nil { + t.Fatal(err) + } + f.Close() + + const SEEK_DATA = 3 + + f, err = os.Open(fn) + if err != nil { + t.Fatal(err) + } + off, err := f.Seek(1024*1024, SEEK_DATA) + if err != nil { + t.Fatal(err) + } + if off < oneTiB-1024*1024 { + t.Errorf("off=%d, expected=%d\n", off, oneTiB) + } + f.Close() +}