Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Ioctl support #273

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions fs/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,10 @@ type HandleReleaser interface {
Release(ctx context.Context, req *fuse.ReleaseRequest) error
}

type HandleIoctlHandler interface {
Ioctl(ctx context.Context, req *fuse.IoctlRequest, resp *fuse.IoctlResponse) error
}

type HandlePoller interface {
// Poll checks whether the handle is currently ready for I/O, and
// may request a wakeup when it is.
Expand Down Expand Up @@ -1475,6 +1479,24 @@ func (c *Server) handleRequest(ctx context.Context, node Node, snode *serveNode,
r.Respond()
return nil

case *fuse.IoctlRequest:
shandle := c.getHandle(r.Handle)
if shandle == nil {
return syscall.ESTALE
}
handle := shandle.handle

s := &fuse.IoctlResponse{Data: make([]byte, 0, r.OutSize)}
if h, ok := handle.(HandleIoctlHandler); ok {
if err := h.Ioctl(ctx, r, s); err != nil {
return err
}
}

done(s)
r.Respond(s)
return nil

case *fuse.DestroyRequest:
if fs, ok := c.fs.(FSDestroyer); ok {
fs.Destroy()
Expand Down
92 changes: 92 additions & 0 deletions fs/serve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"syscall"
"testing"
"time"
"unsafe"

"bazil.org/fuse"
"bazil.org/fuse/fs"
Expand Down Expand Up @@ -4693,3 +4694,94 @@ func TestLocking(t *testing.T) {
})

}

const IOCTL_TEST_READ_DATA = "howareyoudoing"
const IOCTL_TEST_WRITE_DATA = "imdoingwellthx"
const IOCTL_TEST_RESULT = 123

// Recreates the _IOC macro in asm-generic/ioctl.h. The ioctl command encodes
// important information used by the fuse driver to control whether data is
// being read/written and how much data to transfer
func computeIoctlCommand(read, write bool, iotype uint32, size uint32, nr uint32) uint32 {
result := (size << 16) | (iotype << 8) | nr
if read {
result |= 1 << 31
}
if write {
result |= 1 << 30
}
return result
}

func doIoctlRequest(ctx context.Context, path string) (*ioctlResult, error) {
fd, err := syscall.Open(path, syscall.O_RDONLY, 0)
if err != nil {
return nil, err
}
defer syscall.Close(fd)

mydata := []byte(IOCTL_TEST_READ_DATA)
cmd := computeIoctlCommand(true, true, 0, uint32(len(mydata)), 0)

// No syscall.Ioctl so we manually invoke ioctl through syscall.Syscall
result, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(cmd), uintptr(unsafe.Pointer(&mydata[0])))

return &ioctlResult{
Result: uint32(result),
WriteData: mydata,
}, nil
}

var doIoctlHelper = helpers.Register("doIoctl", httpjson.ServePOST(doIoctlRequest))

type ioctlData struct {
readData []byte
}

type ioctlResult struct {
Result uint32
WriteData []byte
}

func (r *ioctlData) Attr(ctx context.Context, a *fuse.Attr) error {
a.Mode = 0o666
return nil
}

func (r *ioctlData) Ioctl(ctx context.Context, req *fuse.IoctlRequest, resp *fuse.IoctlResponse) error {
r.readData = make([]byte, len(req.InData))
copy(r.readData, req.InData)
resp.Data = append(resp.Data, IOCTL_TEST_WRITE_DATA...)
fmt.Println("WRITE:", resp.Data)
resp.Result = IOCTL_TEST_RESULT
return nil
}

func TestIoctl(t *testing.T) {
maybeParallel(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
r := &ioctlData{}
mnt, err := fstestutil.MountedT(t, fstestutil.SimpleFS{&fstestutil.ChildMap{"child": r}}, nil)
if err != nil {
t.Fatal(err)
}
defer mnt.Close()

control := doIoctlHelper.Spawn(ctx, t)
defer control.Close()
var result ioctlResult
if err := control.JSON("/").Call(ctx, mnt.Dir+"/child", &result); err != nil {
t.Fatalf("calling helper: %v", err)
}

if string(r.readData) != IOCTL_TEST_READ_DATA {
t.Errorf("bad read data:\ngot\t%s\nwant\t%s", string(r.readData), IOCTL_TEST_READ_DATA)
}
if string(result.WriteData) != IOCTL_TEST_WRITE_DATA {
t.Errorf("bad write data:\ngot\t%s\nwant\t%s", string(result.WriteData), IOCTL_TEST_WRITE_DATA)
}
if result.Result != IOCTL_TEST_RESULT {
t.Errorf("bad result:\ngot\t%d\nwant\t%d", result.Result, IOCTL_TEST_RESULT)
}
}
55 changes: 55 additions & 0 deletions fuse.go
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,28 @@ func (c *Conn) ReadRequest() (Request, error) {
},
LockFlags: LockFlags(in.LkFlags),
}

case opIoctl:
in := (*ioctlIn)(m.data())
if m.len() < unsafe.Sizeof(*in) {
goto corrupt
}
if unsafe.Sizeof(*in)+uintptr(in.InSize) != m.len() {
goto corrupt
}
var inData []byte
if in.InSize != 0 {
inData = m.bytes()[unsafe.Sizeof(*in):]
}
req = &IoctlRequest{
Header: m.Header(),
Handle: HandleID(in.Fh),
Flags: in.Flags,
Cmd: in.Cmd,
Arg: in.Arg,
InData: inData,
OutSize: in.OutSize,
}
}

return req, nil
Expand Down Expand Up @@ -2701,3 +2723,36 @@ type QueryLockResponse struct {
func (r *QueryLockResponse) String() string {
return fmt.Sprintf("QueryLock range=%d..%d type=%v pid=%v", r.Lock.Start, r.Lock.End, r.Lock.Type, r.Lock.PID)
}

type IoctlRequest struct {
Header
Handle HandleID
Flags uint32
Cmd uint32
Arg uint64
InData []byte
OutSize uint32
}

func (r *IoctlRequest) String() string {
return fmt.Sprintf("Ioctl [%s] %v flags=%v cmd=%d arg=%d insize=%d outsize=%d", &r.Header, r.Handle, r.Flags, r.Cmd, r.Arg, len(r.InData), r.OutSize)
}

func (r *IoctlRequest) Respond(resp *IoctlResponse) {
buf := newBuffer(unsafe.Sizeof(ioctlOut{}) + uintptr(len(resp.Data)))

out := (*ioctlOut)(buf.alloc(unsafe.Sizeof(ioctlOut{})))
out.Result = resp.Result
out.Flags = uint32(resp.Flags)
out.InIovs = 0
out.OutIovs = 0

buf = append(buf, resp.Data...)
r.respond(buf)
}

type IoctlResponse struct {
Result int32
Flags IoctlFlags
Data []byte
}
27 changes: 27 additions & 0 deletions fuse_kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -913,3 +913,30 @@ type pollOut struct {
type notifyPollWakeupOut struct {
Kh uint64
}

type ioctlIn struct {
Fh uint64
Flags uint32
Cmd uint32
Arg uint64
InSize uint32
OutSize uint32
}

type ioctlOut struct {
Result int32
Flags uint32
InIovs uint32 // fuse servers cannot do unresticted ioctls so these are always 0
OutIovs uint32
}

type IoctlFlags uint32

const (
IoctlCompat IoctlFlags = 1 << 0 // 32bit compat ioctl on 64bit machine
IoctlUnrestricted IoctlFlags = 1 << 1 // not restricted to well-formed ioctls, retry allowed
IoctlRetry IoctlFlags = 1 << 2 // retry with new iovecs
Ioctl32Bit IoctlFlags = 1 << 3 // 32bit ioctl
IoctlDir IoctlFlags = 1 << 4 // is a directory
IoctlCompatX32 IoctlFlags = 1 << 5 // x32 compat ioctl on 64bit machine (64bit time_t)
)