diff --git a/fs/serve.go b/fs/serve.go index 79c77557..8a67e6ec 100644 --- a/fs/serve.go +++ b/fs/serve.go @@ -413,6 +413,10 @@ type HandleFAllocater interface { FAllocate(ctx context.Context, req *fuse.FAllocateRequest) error } +type HandleIoctler interface { + Ioctl(ctx context.Context, req *fuse.IoctlRequest, resp *fuse.IoctlResponse) error +} + type Config struct { // Function to send debug log messages to. If nil, use fuse.Debug. // Note that changing this or fuse.Debug may not affect existing @@ -1696,6 +1700,25 @@ 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 + } + h, ok := shandle.handle.(HandleIoctler) + if !ok { + return syscall.ENOTSUP + } + s := &fuse.IoctlResponse{ + Result: 0, + } + if err := h.Ioctl(ctx, r, s); err != nil { + return err + } + done(s) + r.Respond(s) + return nil + /* case *FsyncdirRequest: return ENOSYS diff --git a/fuse.go b/fuse.go index d75294a1..a5881233 100644 --- a/fuse.go +++ b/fuse.go @@ -1118,6 +1118,25 @@ func (c *Conn) ReadRequest() (Request, error) { Length: in.Length, Mode: FAllocateFlags(in.Mode), } + + case opIoctl: + in := (*ioctlIn)(m.data()) + if m.len() < unsafe.Sizeof(*in) { + goto corrupt + } + m.off += int(unsafe.Sizeof(*in)) + if m.len() < uintptr(in.InSize) { + goto corrupt + } + req = &IoctlRequest{ + Header: m.Header(), + Handle: HandleID(in.Fh), + Flags: IoctlFlags(in.Flags), + Cmd: in.Cmd, + Arg: in.Arg, + InData: m.bytes(), + OutSize: in.OutSize, + } } return req, nil @@ -2743,3 +2762,38 @@ func (r *FAllocateRequest) Respond() { buf := newBuffer(0) r.respond(buf) } + +// An IoctlRequest asks to perform an ioctl on an open file. +type IoctlRequest struct { + Header + Handle HandleID + Flags IoctlFlags + Cmd uint32 + Arg uint64 + InData []byte + OutSize uint32 +} + +var _ Request = (*IoctlRequest)(nil) + +func (r *IoctlRequest) String() string { + return fmt.Sprintf("Ioctl [%s] %v fl=%v cmd=%d arg=%d in=%d out=%d", &r.Header, r.Handle, r.Flags, r.Cmd, r.Arg, len(r.InData), r.OutSize) +} + +// Respond replies to the request with the given response. +func (r *IoctlRequest) Respond(resp *IoctlResponse) { + buf := newBuffer(unsafe.Sizeof(ioctlOut{})) + out := (*ioctlOut)(buf.alloc(unsafe.Sizeof(ioctlOut{}))) + out.Result = int32(resp.Result) + buf = append(buf, resp.OutData...) + r.respond(buf) +} + +type IoctlResponse struct { + Result int + OutData []byte +} + +func (r *IoctlResponse) String() string { + return fmt.Sprintf("Ioctl %d", len(r.OutData)) +} diff --git a/fuse_kernel.go b/fuse_kernel.go index 84285205..dbc4a4e8 100644 --- a/fuse_kernel.go +++ b/fuse_kernel.go @@ -933,6 +933,51 @@ type notifyRetrieveIn struct { _ uint64 } +type IoctlFlags uint32 + +const ( + IoctlCompat IoctlFlags = 1 << 0 // 32bit compat ioctl on 64bit machine + IoctlUnrestricted = 1 << 1 // not restricted to well-formed ioctls, retry allowed + IoctlRetry = 1 << 2 // retry with new iovecs + Ioctl32Bit = 1 << 3 // 32bit ioctl + IoctlDir = 1 << 4 // is a directory + IoctlCompatX32 = 1 << 5 // x32 compat ioctl on 64bit machine (64bit time_t) +) + +var ioctlFlagNames = []flagName{ + {uint32(IoctlCompat), "IoctlCompatX32"}, + {uint32(IoctlUnrestricted), "IoctlUnrestricted"}, + {uint32(IoctlRetry), "IoctlRetry"}, + {uint32(Ioctl32Bit), "Ioctl32Bit"}, + {uint32(IoctlDir), "IoctlDir"}, + {uint32(IoctlCompatX32), "IoctlCompatX32"}, +} + +func (fl IoctlFlags) String() string { + return flagString(uint32(fl), ioctlFlagNames) +} + +type ioctlIn struct { + Fh uint64 + Flags uint32 + Cmd uint32 + Arg uint64 + InSize uint32 + OutSize uint32 +} + +type ioctlIovec struct { + Base uint64 + Len uint64 +} + +type ioctlOut struct { + Result int32 + Flags uint32 + InIovs uint32 + OutIovs uint32 +} + // PollFlags are passed in PollRequest.Flags type PollFlags uint32