Commit bc89752c authored by lch's avatar lch Committed by lchopn

fs: unify the loopback fs implementation for Linux and Darwin

Change-Id: I214834639a835b61db2fc0d283ffabb948535ff5
parent 90eabd70
......@@ -12,6 +12,16 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
)
// Like syscall.Dirent, but without the [256]byte name.
type dirent struct {
Ino uint64
Off int64
Reclen uint16
Namlen uint16
Type uint8
Name [1]uint8 // align to 4 bytes for 32 bits.
}
func NewLoopbackDirStream(nm string) (DirStream, syscall.Errno) {
f, err := os.Open(nm)
if err != nil {
......
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"sync"
"syscall"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
type loopbackDirStream struct {
buf []byte
todo []byte
todoErrno syscall.Errno
// Protects fd so we can guard against double close
mu sync.Mutex
fd int
}
// NewLoopbackDirStream open a directory for reading as a DirStream
func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) {
fd, err := syscall.Open(name, syscall.O_DIRECTORY, 0755)
if err != nil {
return nil, ToErrno(err)
}
ds := &loopbackDirStream{
buf: make([]byte, 4096),
fd: fd,
}
if err := ds.load(); err != 0 {
ds.Close()
return nil, err
}
return ds, OK
}
func (ds *loopbackDirStream) Close() {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.fd != -1 {
syscall.Close(ds.fd)
ds.fd = -1
}
}
func (ds *loopbackDirStream) HasNext() bool {
ds.mu.Lock()
defer ds.mu.Unlock()
return len(ds.todo) > 0 || ds.todoErrno != 0
}
// Like syscall.Dirent, but without the [256]byte name.
type dirent struct {
Ino uint64
......@@ -64,50 +8,3 @@ type dirent struct {
Type uint8
Name [1]uint8 // align to 4 bytes for 32 bits.
}
func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.todoErrno != 0 {
return fuse.DirEntry{}, ds.todoErrno
}
// We can't use syscall.Dirent here, because it declares a
// [256]byte name, which may run beyond the end of ds.todo.
// when that happens in the race detector, it causes a panic
// "converted pointer straddles multiple allocations"
de := (*dirent)(unsafe.Pointer(&ds.todo[0]))
nameBytes := ds.todo[unsafe.Offsetof(dirent{}.Name):de.Reclen]
ds.todo = ds.todo[de.Reclen:]
// After the loop, l contains the index of the first '\0'.
l := 0
for l = range nameBytes {
if nameBytes[l] == 0 {
break
}
}
nameBytes = nameBytes[:l]
result := fuse.DirEntry{
Ino: de.Ino,
Mode: (uint32(de.Type) << 12),
Name: string(nameBytes),
}
return result, ds.load()
}
func (ds *loopbackDirStream) load() syscall.Errno {
if len(ds.todo) > 0 {
return OK
}
n, err := syscall.Getdents(ds.fd, ds.buf)
if n < 0 {
n = 0
}
ds.todo = ds.buf[:n]
ds.todoErrno = ToErrno(err)
return OK
}
//go:build !darwin
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fs
import (
"sync"
"syscall"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
"golang.org/x/sys/unix"
)
type loopbackDirStream struct {
buf []byte
todo []byte
todoErrno syscall.Errno
// Protects fd so we can guard against double close
mu sync.Mutex
fd int
}
// NewLoopbackDirStream open a directory for reading as a DirStream
func NewLoopbackDirStream(name string) (DirStream, syscall.Errno) {
fd, err := syscall.Open(name, syscall.O_DIRECTORY, 0755)
if err != nil {
return nil, ToErrno(err)
}
ds := &loopbackDirStream{
buf: make([]byte, 4096),
fd: fd,
}
if err := ds.load(); err != 0 {
ds.Close()
return nil, err
}
return ds, OK
}
func (ds *loopbackDirStream) Close() {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.fd != -1 {
syscall.Close(ds.fd)
ds.fd = -1
}
}
func (ds *loopbackDirStream) HasNext() bool {
ds.mu.Lock()
defer ds.mu.Unlock()
return len(ds.todo) > 0 || ds.todoErrno != 0
}
func (ds *loopbackDirStream) Next() (fuse.DirEntry, syscall.Errno) {
ds.mu.Lock()
defer ds.mu.Unlock()
if ds.todoErrno != 0 {
return fuse.DirEntry{}, ds.todoErrno
}
// We can't use syscall.Dirent here, because it declares a
// [256]byte name, which may run beyond the end of ds.todo.
// when that happens in the race detector, it causes a panic
// "converted pointer straddles multiple allocations"
de := (*dirent)(unsafe.Pointer(&ds.todo[0]))
nameBytes := ds.todo[unsafe.Offsetof(dirent{}.Name):de.Reclen]
ds.todo = ds.todo[de.Reclen:]
// After the loop, l contains the index of the first '\0'.
l := 0
for l = range nameBytes {
if nameBytes[l] == 0 {
break
}
}
nameBytes = nameBytes[:l]
result := fuse.DirEntry{
Ino: de.Ino,
Mode: (uint32(de.Type) << 12),
Name: string(nameBytes),
}
return result, ds.load()
}
func (ds *loopbackDirStream) load() syscall.Errno {
if len(ds.todo) > 0 {
return OK
}
n, err := unix.Getdents(ds.fd, ds.buf)
if n < 0 {
n = 0
}
ds.todo = ds.buf[:n]
ds.todoErrno = ToErrno(err)
return OK
}
......@@ -13,6 +13,7 @@ import (
"syscall"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/fallocate"
"golang.org/x/sys/unix"
)
......@@ -241,3 +242,13 @@ func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (ui
n, err := unix.Seek(f.fd, int64(off), int(whence))
return uint64(n), ToErrno(err)
}
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
err := fallocate.Fallocate(f.fd, mode, int64(off), int64(sz))
if err != nil {
return ToErrno(err)
}
return OK
}
......@@ -4,7 +4,29 @@
package fs
import "github.com/hanwen/go-fuse/v2/fuse"
import (
"context"
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/utimens"
)
func setBlocks(out *fuse.Attr) {
}
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra Getattr() calls.
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var attr fuse.AttrOut
if a == nil || m == nil {
errno := f.Getattr(context.Background(), &attr)
if errno != 0 {
return errno
}
}
tv := utimens.Fill(a, m, &attr.Attr)
err := syscall.Futimes(int(f.fd), tv)
return ToErrno(err)
}
......@@ -5,32 +5,9 @@
package fs
import (
"context"
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
)
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
f.mu.Lock()
defer f.mu.Unlock()
err := syscall.Fallocate(f.fd, mode, int64(off), int64(sz))
if err != nil {
return ToErrno(err)
}
return OK
}
// Utimens - file handle based version of loopbackFileSystem.Utimens()
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var ts [2]syscall.Timespec
ts[0] = fuse.UtimeToTimespec(a)
ts[1] = fuse.UtimeToTimespec(m)
err := futimens(int(f.fd), &ts)
return ToErrno(err)
}
func setBlocks(out *fuse.Attr) {
if out.Blksize > 0 {
return
......
// Copyright 2016 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !darwin
package fs
import (
"syscall"
"time"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
)
// Utimens - file handle based version of loopbackFileSystem.Utimens()
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var ts [2]syscall.Timespec
ts[0] = fuse.UtimeToTimespec(a)
ts[1] = fuse.UtimeToTimespec(m)
err := futimens(int(f.fd), &ts)
return ToErrno(err)
}
// futimens - futimens(3) calls utimensat(2) with "pathname" set to null and
// "flags" set to zero
func futimens(fd int, times *[2]syscall.Timespec) (err error) {
......
......@@ -11,10 +11,6 @@ import (
"context"
"syscall"
"time"
"unsafe"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/hanwen/go-fuse/v2/internal/utimens"
)
var _ = (NodeGetxattrer)((*LoopbackNode)(nil))
......@@ -41,54 +37,6 @@ func (n *LoopbackNode) Listxattr(ctx context.Context, dest []byte) (uint32, sysc
return 0, syscall.ENOSYS
}
func (f *loopbackFile) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32) syscall.Errno {
// TODO: Handle `mode` parameter.
// From `man fcntl` on OSX:
// The F_PREALLOCATE command operates on the following structure:
//
// typedef struct fstore {
// u_int32_t fst_flags; /* IN: flags word */
// int fst_posmode; /* IN: indicates offset field */
// off_t fst_offset; /* IN: start of the region */
// off_t fst_length; /* IN: size of the region */
// off_t fst_bytesalloc; /* OUT: number of bytes allocated */
// } fstore_t;
//
// The flags (fst_flags) for the F_PREALLOCATE command are as follows:
//
// F_ALLOCATECONTIG Allocate contiguous space.
//
// F_ALLOCATEALL Allocate all requested space or no space at all.
//
// The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as fol-
// lows:
//
// F_PEOFPOSMODE Allocate from the physical end of file.
//
// F_VOLPOSMODE Allocate from the volume offset.
k := struct {
Flags uint32 // u_int32_t
Posmode int64 // int
Offset int64 // off_t
Length int64 // off_t
Bytesalloc int64 // off_t
}{
0,
0,
int64(off),
int64(sz),
0,
}
// Linux version for reference:
// err := syscall.Fallocate(int(f.File.Fd()), mode, int64(off), int64(sz))
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, uintptr(f.fd), uintptr(syscall.F_PREALLOCATE), uintptr(unsafe.Pointer(&k)))
return errno
}
// timeToTimeval - Convert time.Time to syscall.Timeval
//
// Note: This does not use syscall.NsecToTimespec because
......@@ -101,21 +49,6 @@ func timeToTimeval(t *time.Time) syscall.Timeval {
return tv
}
// MacOS before High Sierra lacks utimensat() and UTIME_OMIT.
// We emulate using utimes() and extra Getattr() calls.
func (f *loopbackFile) utimens(a *time.Time, m *time.Time) syscall.Errno {
var attr fuse.AttrOut
if a == nil || m == nil {
errno := f.Getattr(context.Background(), &attr)
if errno != 0 {
return errno
}
}
tv := utimens.Fill(a, m, &attr.Attr)
err := syscall.Futimes(int(f.fd), tv)
return ToErrno(err)
}
var _ = (NodeCopyFileRanger)((*LoopbackNode)(nil))
func (n *LoopbackNode) CopyFileRange(ctx context.Context, fhIn FileHandle,
......
......@@ -6,6 +6,7 @@ import (
"syscall"
"testing"
"github.com/hanwen/go-fuse/v2/internal/renameat"
"github.com/kylelemons/godebug/pretty"
"golang.org/x/sys/unix"
)
......@@ -38,7 +39,7 @@ func TestRenameExchange(t *testing.T) {
t.Fatalf("Fstatat: %v", err)
}
if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err != nil {
if err := renameat.Renameat(f1, "file", f2, "file", renameat.RENAME_EXCHANGE); err != nil {
t.Errorf("rename EXCHANGE: %v", err)
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment