From 63976fa237c9728ea85838896fcf17927d23785d Mon Sep 17 00:00:00 2001 From: Mark Gordon Date: Wed, 17 Mar 2021 16:42:45 -0700 Subject: [PATCH 1/2] Added ioctl support Signed-off-by: Mark Gordon --- fuse.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ fuse_kernel.go | 27 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/fuse.go b/fuse.go index c301b421..dbb0f7c6 100644 --- a/fuse.go +++ b/fuse.go @@ -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 @@ -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 +} diff --git a/fuse_kernel.go b/fuse_kernel.go index 18e51126..2d5539c5 100644 --- a/fuse_kernel.go +++ b/fuse_kernel.go @@ -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) +) From 75f383b56e6e2fd600d8301fce868d4655d6b4a3 Mon Sep 17 00:00:00 2001 From: Mark Gordon Date: Wed, 17 Mar 2021 18:16:11 -0700 Subject: [PATCH 2/2] Add fs Ioctl function and tests Signed-off-by: Mark Gordon --- fs/serve.go | 22 ++++++++++++ fs/serve_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/fs/serve.go b/fs/serve.go index 32ad8ee6..e2084111 100644 --- a/fs/serve.go +++ b/fs/serve.go @@ -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. @@ -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() diff --git a/fs/serve_test.go b/fs/serve_test.go index 55059823..b102ed4b 100644 --- a/fs/serve_test.go +++ b/fs/serve_test.go @@ -19,6 +19,7 @@ import ( "syscall" "testing" "time" + "unsafe" "bazil.org/fuse" "bazil.org/fuse/fs" @@ -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) + } +}