Skip to content

Commit 26655db

Browse files
author
Cédric CARRÉE
committed
Added IOCTL support to python-fuse
1 parent 7e29c2a commit 26655db

4 files changed

Lines changed: 272 additions & 17 deletions

File tree

example/fioc.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
#!/usr/bin/python
2+
3+
# Copyright (C) 2016 Cedric CARREE <beg0@free.fr>
4+
#
5+
# This program can be distributed under the terms of the GNU LGPL.
6+
# See the file COPYING.
7+
#
8+
import os, stat, errno, struct
9+
10+
try:
11+
import _find_fuse_parts
12+
except ImportError:
13+
pass
14+
import fuse
15+
from fuse import Fuse
16+
17+
18+
if not hasattr(fuse, '__version__'):
19+
raise RuntimeError, \
20+
"your fuse-py doesn't know of fuse.__version__, probably it's too old."
21+
22+
fuse.fuse_python_api = (0, 2)
23+
24+
# this mimics asm-generic/ioctl.h header
25+
# I'm not sure this is really portable, you'd better use ioctl-opt package or something similar
26+
class IOCTL:
27+
_IOC_NRBITS = 8
28+
_IOC_TYPEBITS = 8
29+
_IOC_SIZEBITS = 14
30+
_IOC_DIRBITS = 2
31+
32+
_IOC_NRMASK = (1 << _IOC_NRBITS)-1
33+
_IOC_TYPEMASK = (1 << _IOC_TYPEBITS)-1
34+
_IOC_SIZEMASK = (1 << _IOC_SIZEBITS)-1
35+
_IOC_DIRMASK = (1 << _IOC_DIRBITS)-1
36+
37+
_IOC_NRSHIFT = 0
38+
_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS
39+
_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS
40+
_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS
41+
42+
_IOC_NONE = 0
43+
_IOC_WRITE = 1
44+
_IOC_READ = 2
45+
46+
@classmethod
47+
def _IOC(cls, d,t,nr,size):
48+
return (((d) << cls._IOC_DIRSHIFT) |
49+
((t) << cls._IOC_TYPESHIFT) | \
50+
((nr) << cls._IOC_NRSHIFT) | \
51+
((size) << cls._IOC_SIZESHIFT))
52+
53+
@classmethod
54+
def _IO(cls, t,nr):
55+
return cls._IOC(cls._IOC_NONE, t, nr, 0)
56+
57+
@classmethod
58+
def _IOR(cls, t,nr,size):
59+
return cls._IOC(cls._IOC_READ, t, nr, size)
60+
61+
@classmethod
62+
def _IOW(cls, t,nr,size):
63+
return cls._IOC(cls._IOC_WRITE, t, nr, size)
64+
65+
@classmethod
66+
def _IOWR(cls,t,nr,size):
67+
return cls._IOC(cls._IOC_WRITE|cls._IOC_READ, t, nr, size)
68+
69+
70+
# IOCTL (as defined in fioc.h)
71+
# Note: on my system, size_t is an unsigned long
72+
FIOC_GET_SIZE = IOCTL._IOR(ord('E'),0, struct.calcsize("L"));
73+
FIOC_SET_SIZE = IOCTL._IOW(ord('E'),1, struct.calcsize("L"));
74+
75+
# object type
76+
FIOC_NONE = 0
77+
FIOC_ROOT = 1
78+
FIOC_FILE = 2
79+
80+
FIOC_NAME = "fioc"
81+
82+
class MyStat(fuse.Stat):
83+
def __init__(self):
84+
self.st_mode = 0
85+
self.st_ino = 0
86+
self.st_dev = 0
87+
self.st_nlink = 0
88+
self.st_uid = 0
89+
self.st_gid = 0
90+
self.st_size = 0
91+
self.st_atime = 0
92+
self.st_mtime = 0
93+
self.st_ctime = 0
94+
95+
96+
class FiocFS(Fuse):
97+
98+
def __init__(self, *args, **kw):
99+
Fuse.__init__(self, *args, **kw)
100+
self.buf = ""
101+
102+
def resize(self, new_size):
103+
old_size = len(self.buf)
104+
if new_size == old_size:
105+
return 0
106+
107+
if new_size < old_size:
108+
self.buf = self.buf[0:new_size]
109+
else:
110+
self.buf = self.buf + str(bytearray(new_size - old_size))
111+
112+
return 0
113+
114+
def file_type(self, path):
115+
if not type(path) == str:
116+
return FIOC_NONE
117+
if path == "/":
118+
return FIOC_ROOT
119+
elif path == "/" + FIOC_NAME:
120+
return FIOC_FILE
121+
else:
122+
return FIOC_NONE
123+
124+
def getattr(self, path):
125+
st = MyStat()
126+
ft = self.file_type(path)
127+
if ft == FIOC_ROOT:
128+
st.st_mode = stat.S_IFDIR | 0755
129+
st.st_nlink = 2
130+
elif ft == FIOC_FILE:
131+
st.st_mode = stat.S_IFREG | 0444
132+
st.st_nlink = 1
133+
st.st_size = len(self.buf)
134+
else:
135+
return -errno.ENOENT
136+
return st
137+
138+
def open(self, path, flags):
139+
if self.file_type(path) != FIOC_NONE:
140+
return 0
141+
142+
return -errno.ENOENT
143+
144+
def do_read(self, path, size, offset):
145+
146+
if offset >= len(self.buf):
147+
return 0
148+
149+
if size > (len(self.buf) - offset):
150+
size = len(self.buf) - offset
151+
152+
return self.buf[offset:offset+size]
153+
154+
def read(self, path, size, offset):
155+
if self.file_type(path) != FIOC_FILE:
156+
return -errno.EINVAL;
157+
158+
return self.do_read(path, size, offset)
159+
160+
def do_write(self, path, buf, size, offset):
161+
162+
self.buf = self.buf[0:offset-1] + buf + self.buf[offset+size+1:len(self.buf)]
163+
164+
def write(self, path, buf, size, offset):
165+
if self.file_type(path) != FIOC_FILE:
166+
return -errno.EINVAL;
167+
168+
self.do_write(path, buf, size, offset)
169+
170+
def trunate(self, path, size):
171+
if self.file_type(path) != FIOC_FILE:
172+
return -error.EINVAL
173+
174+
return self.resize(size)
175+
176+
def readdir(self, path, offset):
177+
for r in '.', '..', FIOC_NAME:
178+
yield fuse.Direntry(r)
179+
180+
def ioctl(self, path, cmd, arg, flags):
181+
if cmd == FIOC_GET_SIZE:
182+
data = struct.pack("L",len(self.buf))
183+
return data
184+
elif cmd == FIOC_SET_SIZE:
185+
(l,) = struct.unpack("L",arg);
186+
self.resize(l)
187+
return 0
188+
189+
return -errno.EINVAL
190+
191+
def main():
192+
usage="""
193+
Userspace ioctl example
194+
195+
""" + Fuse.fusage
196+
server = FiocFS(version="%prog " + fuse.__version__,
197+
usage=usage,
198+
dash_s_do='setsingle')
199+
200+
server.parse(errex=1)
201+
server.main()
202+
203+
if __name__ == '__main__':
204+
main()
205+

example/hello.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ def open(self, path, flags):
6161
if path != hello_path:
6262
return -errno.ENOENT
6363
accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
64-
if (flags & accmode) != os.O_RDONLY:
65-
return -errno.EACCES
64+
#if (flags & accmode) != os.O_RDONLY:
65+
# return -errno.EACCES
6666

6767
def read(self, path, size, offset):
6868
if path != hello_path:
@@ -76,6 +76,18 @@ def read(self, path, size, offset):
7676
buf = ''
7777
return buf
7878

79+
def ioctl(self, path, cmd, arg, flags):
80+
l = -1
81+
if arg is not None:
82+
l = len(arg)
83+
print "ioctl with path >%s< cmd: %d arglen %d, flags %x" % (path, cmd, l,flags)
84+
85+
if cmd == -2146941696: # get
86+
return self.the_buffer_size;
87+
elif cmd == 1074283777: # set
88+
self.the_buffer_size = arg;
89+
return 0
90+
7991
def main():
8092
usage="""
8193
Userspace hello example

fuse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ class Fuse(object):
647647
'statfs', 'fsync', 'create', 'opendir', 'releasedir', 'fsyncdir',
648648
'flush', 'fgetattr', 'ftruncate', 'getxattr', 'listxattr',
649649
'setxattr', 'removexattr', 'access', 'lock', 'utimens', 'bmap',
650-
'fsinit', 'fsdestroy']
650+
'fsinit', 'fsdestroy', 'ioctl']
651651

652652
fusage = "%prog [mountpoint] [options]"
653653

fuseparts/_fusemodule.c

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
#include <Python.h>
3636
#include <fuse.h>
3737

38+
#include <sys/ioctl.h>
39+
3840
static PyObject *getattr_cb=NULL, *readlink_cb=NULL, *readdir_cb=NULL,
3941
*mknod_cb=NULL, *mkdir_cb=NULL, *unlink_cb=NULL, *rmdir_cb=NULL,
4042
*symlink_cb=NULL, *rename_cb=NULL, *link_cb=NULL, *chmod_cb=NULL,
@@ -44,7 +46,8 @@ static PyObject *getattr_cb=NULL, *readlink_cb=NULL, *readdir_cb=NULL,
4446
*releasedir_cb=NULL, *fsyncdir_cb=NULL, *flush_cb=NULL, *ftruncate_cb=NULL,
4547
*fgetattr_cb=NULL, *getxattr_cb=NULL, *listxattr_cb=NULL, *setxattr_cb=NULL,
4648
*removexattr_cb=NULL, *access_cb=NULL, *lock_cb = NULL, *utimens_cb = NULL,
47-
*bmap_cb = NULL, *fsinit_cb=NULL, *fsdestroy_cb = NULL;
49+
*bmap_cb = NULL, *fsinit_cb=NULL, *fsdestroy_cb = NULL, *ioctl_cb = NULL;
50+
4851

4952
static PyObject *Py_FuseError;
5053
static PyInterpreterState *interp;
@@ -281,8 +284,7 @@ opendir_func(const char *path, struct fuse_file_info *fi)
281284

282285
fi->fh = (uintptr_t) v;
283286

284-
ret = 0;
285-
goto OUT;
287+
return 0;
286288

287289
EPILOGUE
288290
}
@@ -529,15 +531,11 @@ open_func(const char *path, struct fuse_file_info *fi)
529531
if (pytmp1) {
530532
fi->keep_cache = PyObject_IsTrue(pytmp1);
531533
Py_DECREF(pytmp1);
532-
} else {
533-
PyErr_Clear();
534534
}
535535
pytmp1 = PyObject_GetAttrString(pytmp, "direct_io");
536536
if (pytmp1) {
537537
fi->direct_io = PyObject_IsTrue(pytmp1);
538538
Py_DECREF(pytmp1);
539-
} else {
540-
PyErr_Clear();
541539
}
542540

543541
if (PyObject_IsTrue(PyTuple_GetItem(v, 1)))
@@ -577,15 +575,11 @@ create_func(const char *path, mode_t mode, struct fuse_file_info *fi)
577575
if (pytmp1) {
578576
fi->keep_cache = PyObject_IsTrue(pytmp1);
579577
Py_DECREF(pytmp1);
580-
} else {
581-
PyErr_Clear();
582578
}
583579
pytmp1 = PyObject_GetAttrString(pytmp, "direct_io");
584580
if (pytmp1) {
585581
fi->direct_io = PyObject_IsTrue(pytmp1);
586582
Py_DECREF(pytmp1);
587-
} else {
588-
PyErr_Clear();
589583
}
590584

591585
if (PyObject_IsTrue(PyTuple_GetItem(v, 1))) {
@@ -909,6 +903,47 @@ bmap_func(const char *path, size_t blocksize, uint64_t *idx)
909903
}
910904
#endif
911905

906+
#if FUSE_VERSION >= 28
907+
static int
908+
ioctl_func(const char *path, int cmd, void *arg,
909+
struct fuse_file_info *fi, unsigned int flags, void *data)
910+
{
911+
char* s;
912+
char* input_data;
913+
int input_data_size, output_data_size;
914+
915+
input_data = (char*) data;
916+
input_data_size = _IOC_SIZE(cmd);
917+
918+
// If not a "write" ioctl, do not send input data
919+
if(!(_IOC_DIR(cmd) & _IOC_WRITE)) {
920+
input_data = NULL;
921+
input_data_size = 0;
922+
}
923+
924+
PROLOGUE(PYO_CALLWITHFI(fi, ioctl_cb, sIs#I, path,cmd,(char*)input_data,input_data_size,flags));
925+
926+
// get returned value if this is a "read" ioctl
927+
if(_IOC_DIR(cmd) & _IOC_READ) {
928+
929+
if(!PyString_Check(v)) {
930+
ret = -EINVAL;
931+
goto OUT_DECREF;
932+
}
933+
934+
output_data_size = PyString_Size(v);
935+
936+
if(output_data_size > _IOC_SIZE(cmd))
937+
output_data_size = _IOC_SIZE(cmd);
938+
939+
s = PyString_AsString(v);
940+
memcpy(data,s,output_data_size);
941+
ret = 0;
942+
}
943+
EPILOGUE
944+
}
945+
#endif
946+
912947
static int
913948
pyfuse_loop_mt(struct fuse *f)
914949
{
@@ -952,13 +987,13 @@ Fuse_main(PyObject *self, PyObject *args, PyObject *kw)
952987
"create", "opendir", "releasedir", "fsyncdir", "flush",
953988
"ftruncate", "fgetattr", "getxattr", "listxattr", "setxattr",
954989
"removexattr", "access", "lock", "utimens", "bmap",
955-
"fsinit", "fsdestroy", "fuse_args", "multithreaded", NULL
990+
"fsinit", "fsdestroy", "ioctl", "fuse_args", "multithreaded", NULL
956991
};
957992

958993
memset(&op, 0, sizeof(op));
959994

960995
if (!PyArg_ParseTupleAndKeywords(args, kw,
961-
"|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOi",
996+
"|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOi",
962997
kwlist, &getattr_cb, &readlink_cb,
963998
&readdir_cb, &mknod_cb, &mkdir_cb,
964999
&unlink_cb, &rmdir_cb, &symlink_cb,
@@ -973,7 +1008,7 @@ Fuse_main(PyObject *self, PyObject *args, PyObject *kw)
9731008
&listxattr_cb, &setxattr_cb,
9741009
&removexattr_cb, &access_cb,
9751010
&lock_cb, &utimens_cb, &bmap_cb,
976-
&fsinit_cb, &fsdestroy_cb,
1011+
&fsinit_cb, &fsdestroy_cb, &ioctl_cb,
9771012
&fargseq, &multithreaded))
9781013
return NULL;
9791014

@@ -1034,6 +1069,9 @@ Fuse_main(PyObject *self, PyObject *args, PyObject *kw)
10341069
DO_ONE_ATTR_AS(init, fsinit);
10351070
DO_ONE_ATTR_AS(destroy, fsdestroy);
10361071
#endif
1072+
#if FUSE_VERSION >= 28
1073+
DO_ONE_ATTR(ioctl);
1074+
#endif
10371075

10381076
#undef DO_ONE_ATTR
10391077
#undef DO_ONE_ATTR_AS

0 commit comments

Comments
 (0)