diff options
-rw-r--r-- | AUTHORS | 9 | ||||
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | README.python | 60 | ||||
-rw-r--r-- | python/_fusemodule.c | 266 | ||||
-rw-r--r-- | python/fuse.py | 101 |
5 files changed, 448 insertions, 0 deletions
@@ -1 +1,10 @@ +FUSE core +--------- + Miklos Szeredi <mszeredi@inf.bme.hu> + + +Python bindings +--------------- + +Jeff Epler <jepler@unpythonic.dhs.org> @@ -1,3 +1,15 @@ 2001-11-09 Miklos Szeredi <mszeredi@inf.bme.hu> * Started ChangeLog + +2001-11-13 Miklos Szeredi <mszeredi@inf.bme.hu> + + * Fixed vfsmount reference leak in fuse_follow_link + + * FS blocksize is set to PAGE_CACHE_SIZE, blksize attribute from + userspace is ignored + +2001-11-14 Miklos Szeredi <mszeredi@inf.bme.hu> + + * Python bindings by Jeff Epler added + diff --git a/README.python b/README.python new file mode 100644 index 0000000..451c56c --- /dev/null +++ b/README.python @@ -0,0 +1,60 @@ +General Information +=================== + +This is a Python[1] interface to FUSE[2]. + +FUSE (Filesystem in USErspace) is a simple interface for userspace +programs to export a virtual filesystem to the linux kernel. FUSE +also aims to provide a secure method for non privileged users to +create and mount their own filesystem implementations. + +When run from the commandline, "fuse.py" simply reexports the root +filesystem within the mount point as example/fusexmp does in the main +FUSE distribution. It also offers a class, fuse.Fuse, which can be +subclassed to create a filesystem. fuse.Xmp is the example filesystem +implementation. + +In your subclass of fuse, add attributes with the expected names +("getattr", "readlink", etc) and call signatures (refer to fuse.Xmp) +then call main(). Make it runnable as a #! script, and mount with + fusermount <mount point> <script name> +for some reason, + fusermount <mount point> python <script name> +does not seem to work. (why?) + +Limitations +=========== + +This is minimally tested, though I think I have exercised each function. +There's no documentation, docstrings, or tests. + +Python's lstat() does not return some fields which must be filled in +(st_blksize, st_blocks, st_ino), and _fusemodule assumes that the return +value from the lstat() method is identical to Python's lstat(). This +limitation should be lifted, and some standard order chosen for these +three values. For now, though, default values are chosen and du returns a +number similar to the "real" one. + +The Python Global Interpreter Lock is not handled, so using +fuse.MULTITHREAD will not work. Modifying the PROLOGUE and EPILOGUE +functions may take care of this. For now, just run without +fuse.MULTITHREAD in flags. + +Author +====== + +I'm Jeff Epler <jepler@unpythonic.dhs.org>. I've been dabbling in +Python for nearly 7 years now, and interested (despite the lack of a +real practical use) in userspace filesystems ever since I couldn't get +userfs to compile way back in '93 or so. FUSE is cool, but i'm still +not sure what it's good for in practical terms. + +I don't know how high a level of interest I'll maintain in this project, +so if you want to do something with it feel free to do so. Like FUSE, +this software is distributed under the terms of the GNU General Public +License, Version 2. Future versions, if any, will be available at [3]. + + +[1] http://www.python.org +[2] http://sourceforge.net/projects/avf/ +[3] http://unpythonic.dhs.org/~jepler/fuse/ diff --git a/python/_fusemodule.c b/python/_fusemodule.c new file mode 100644 index 0000000..eee4fda --- /dev/null +++ b/python/_fusemodule.c @@ -0,0 +1,266 @@ +#include "Python.h" +#include "fuse.h" +#include <time.h> + +static PyObject *ErrorObject; + +PyObject *getattr_cb=NULL, *readlink_cb=NULL, *getdir_cb=NULL, + *mknod_cb=NULL, *mkdir_cb=NULL, *unlink_cb=NULL, *rmdir_cb=NULL, + *symlink_cb=NULL, *rename_cb=NULL, *link_cb=NULL, *chmod_cb=NULL, + *chown_cb=NULL, *truncate_cb=NULL, *utime_cb=NULL, + *open_cb=NULL, *read_cb=NULL, *write_cb=NULL; +struct fuse *fuse=NULL; + + +#define PROLOGUE \ + int ret = -EINVAL; \ + if (!v) { PyErr_Print(); goto OUT; } \ + if(v == Py_None) { ret = 0; goto OUT_DECREF; } \ + if(PyInt_Check(v)) { ret = PyInt_AsLong(v); goto OUT_DECREF; } + +#define EPILOGUE \ + OUT_DECREF: \ + Py_DECREF(v); \ + OUT: \ + return ret; +static int getattr_func(const char *path, struct stat *st) { + int i; + PyObject *v = PyObject_CallFunction(getattr_cb, "s", path); + PROLOGUE + + if(!PyTuple_Check(v)) { goto OUT_DECREF; } + if(PyTuple_Size(v) < 10) { goto OUT_DECREF; } + for(i=0; i<10; i++) { + if (!PyInt_Check(PyTuple_GetItem(v, 0))) goto OUT_DECREF; + } + + st->st_mode = PyInt_AsLong(PyTuple_GetItem(v, 0)); + st->st_ino = PyInt_AsLong(PyTuple_GetItem(v, 1)); + st->st_dev = PyInt_AsLong(PyTuple_GetItem(v, 2)); + st->st_nlink= PyInt_AsLong(PyTuple_GetItem(v, 3)); + st->st_uid = PyInt_AsLong(PyTuple_GetItem(v, 4)); + st->st_gid = PyInt_AsLong(PyTuple_GetItem(v, 5)); + st->st_size = PyInt_AsLong(PyTuple_GetItem(v, 6)); + st->st_atime= PyInt_AsLong(PyTuple_GetItem(v, 7)); + st->st_mtime= PyInt_AsLong(PyTuple_GetItem(v, 8)); + st->st_ctime= PyInt_AsLong(PyTuple_GetItem(v, 9)); + + /* Fill in fields not provided by Python lstat() */ + st->st_blksize= 4096; + st->st_blocks= (st->st_size + 511)/512; + st->st_ino = 0; + + ret = 0; + EPILOGUE +} + +static int readlink_func(const char *path, char *link, size_t size) { + PyObject *v = PyObject_CallFunction(readlink_cb, "s", path); + char *s; + PROLOGUE + + if(!PyString_Check(v)) { ret = -EINVAL; goto OUT_DECREF; } + s = PyString_AsString(v); + strncpy(link, s, size); + link[size-1] = '\0'; + ret = 0; + + EPILOGUE +} + +static int getdir_func(const char *path, fuse_dirh_t dh, fuse_dirfil_t df) { + PyObject *v = PyObject_CallFunction(getdir_cb, "s", path); + int i; + PROLOGUE + + if(!PySequence_Check(v)) { printf("getdir_func not sequence\n");goto OUT_DECREF; } + for(i=0; i < PySequence_Length(v); i++) { + PyObject *w = PySequence_GetItem(v, i); + printf("getdir_func validate %d\n", i); + if(!PySequence_Check(w)) { printf("getdir item not sequence\n"); goto OUT_DECREF; } + if(PySequence_Length(w) != 2) { printf("getdir item not len 2\n"); goto OUT_DECREF; } + if(!PyString_Check(PySequence_GetItem(w,0))){ printf("getdir item[0] not string"); goto OUT_DECREF; } + if(!PyInt_Check(PySequence_GetItem(w, 1))) { printf("getdir item[1] not int"); goto OUT_DECREF; } + } + + for(i=0; i < PySequence_Length(v); i++) { + PyObject *w = PySequence_GetItem(v, i); + printf("getdir_func %d\n", i); + ret = df(dh, PyString_AsString(PySequence_GetItem(w, 0)), + PyInt_AsLong(PySequence_GetItem(w, 1))); + if(ret) goto OUT_DECREF; + } + + ret = 0; + + EPILOGUE +} + +int mknod_func(const char *path, mode_t m, dev_t d) { + PyObject *v = PyObject_CallFunction(mknod_cb, "sii", path, m, d); + PROLOGUE + EPILOGUE +} + +int mkdir_func(const char *path, mode_t m) { + PyObject *v = PyObject_CallFunction(mkdir_cb, "si", path, m); + PROLOGUE + EPILOGUE +} + +int unlink_func(const char *path) { + PyObject *v = PyObject_CallFunction(unlink_cb, "s", path); + PROLOGUE + EPILOGUE +} + +int rmdir_func(const char *path) { + PyObject *v = PyObject_CallFunction(rmdir_cb, "s", path); + PROLOGUE + EPILOGUE +} + +int symlink_func(const char *path, const char *path1) { + PyObject *v = PyObject_CallFunction(symlink_cb, "ss", path, path1); + PROLOGUE + EPILOGUE +} + +int rename_func(const char *path, const char *path1) { + PyObject *v = PyObject_CallFunction(rename_cb, "ss", path, path1); + PROLOGUE + EPILOGUE +} + +int link_func(const char *path, const char *path1) { + PyObject *v = PyObject_CallFunction(link_cb, "ss", path, path1); + PROLOGUE + EPILOGUE +} + +int chmod_func(const char *path, mode_t m) { + PyObject *v = PyObject_CallFunction(chmod_cb, "si", path, m); + PROLOGUE + EPILOGUE +} + +int chown_func(const char *path, uid_t u, gid_t g) { + PyObject *v = PyObject_CallFunction(chown_cb, "sii", path, u, g); + PROLOGUE + EPILOGUE +} + +int truncate_func(const char *path, off_t o) { + PyObject *v = PyObject_CallFunction(truncate_cb, "si", path, o); + PROLOGUE + EPILOGUE +} + +int utime_func(const char *path, struct utimbuf *u) { + int actime = u ? u->actime : time(NULL); + int modtime = u ? u->modtime : actime; + PyObject *v = PyObject_CallFunction(utime_cb, "s(ii)", + path, actime, modtime); + PROLOGUE + EPILOGUE +} + +int read_func(const char *path, char *buf, size_t s, off_t off) { + PyObject *v = PyObject_CallFunction(read_cb, "sii", path, s, off); + PROLOGUE + if(PyString_Check(v)) { + if(PyString_Size(v) > s) goto OUT_DECREF; + memcpy(buf, PyString_AsString(v), PyString_Size(v)); + ret = PyString_Size(v); + } + EPILOGUE +} + +int write_func(const char *path, const char *buf, size_t t, off_t off) { + PyObject *v = PyObject_CallFunction(write_cb,"ss#i", path, buf, t, off); + PROLOGUE + EPILOGUE +} + +int open_func(const char *path, int mode) { + PyObject *v = PyObject_CallFunction(open_cb, "si", path, mode); + PROLOGUE + EPILOGUE +} + +static PyObject * +Fuse_main(PyObject *self, PyObject *args, PyObject *kw) +{ + PyObject *list, *item; + + int flags=0; + + struct fuse_operations op; + + static char *kwlist[] = { + "getattr", "readlink", "getdir", "mknod", + "mkdir", "unlink", "rmdir", "symlink", "rename", + "link", "chmod", "chown", "truncate", "utime", + "open", "read", "write", "flags", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kw, "|OOOOOOOOOOOOOOOOOi", + kwlist, &getattr_cb, &readlink_cb, &getdir_cb, &mknod_cb, + &mkdir_cb, &unlink_cb, &rmdir_cb, &symlink_cb, &rename_cb, + &link_cb, &chmod_cb, &chown_cb, &truncate_cb, &utime_cb, + &open_cb, &read_cb, &write_cb, &flags)) + return NULL; + +#define DO_ONE_ATTR(name) if(name ## _cb) { Py_INCREF(name ## _cb); op.name = name ## _func; } else { op.name = NULL; } + + DO_ONE_ATTR(getattr); + DO_ONE_ATTR(readlink); + DO_ONE_ATTR(getdir); + DO_ONE_ATTR(mknod); + DO_ONE_ATTR(mkdir); + DO_ONE_ATTR(unlink); + DO_ONE_ATTR(rmdir); + DO_ONE_ATTR(symlink); + DO_ONE_ATTR(rename); + DO_ONE_ATTR(link); + DO_ONE_ATTR(chmod); + DO_ONE_ATTR(chown); + DO_ONE_ATTR(truncate); + DO_ONE_ATTR(utime); + DO_ONE_ATTR(open); + DO_ONE_ATTR(read); + DO_ONE_ATTR(write); + + fuse = fuse_new(0, flags); + fuse_set_operations(fuse, &op); + fuse_loop(fuse); + + Py_INCREF(Py_None); + return Py_None; +} + +/* List of functions defined in the module */ + +static PyMethodDef Fuse_methods[] = { + {"main", (PyCFunction)Fuse_main, METH_VARARGS|METH_KEYWORDS}, + {NULL, NULL} /* sentinel */ +}; + + +/* Initialization function for the module (*must* be called init_fuse) */ + +DL_EXPORT(void) +init_fuse(void) +{ + PyObject *m, *d; + + /* Create the module and add the functions */ + m = Py_InitModule("_fuse", Fuse_methods); + + /* Add some symbolic constants to the module */ + d = PyModule_GetDict(m); + ErrorObject = PyErr_NewException("fuse.error", NULL, NULL); + PyDict_SetItemString(d, "error", ErrorObject); + PyDict_SetItemString(d, "MULTITHREAD", PyInt_FromLong(FUSE_MULTITHREAD)); + PyDict_SetItemString(d, "DEBUG", PyInt_FromLong(FUSE_DEBUG)); + +} diff --git a/python/fuse.py b/python/fuse.py new file mode 100644 index 0000000..ec40262 --- /dev/null +++ b/python/fuse.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +from _fuse import main, DEBUG +import os +from stat import * +from errno import * + +class ErrnoWrapper: + def __init__(self, func): + self.func = func + + def __call__(self, *args, **kw): + try: + return apply(self.func, args, kw) + except (IOError, OSError), detail: + # Sometimes this is an int, sometimes an instance... + if hasattr(detail, "errno"): detail = detail.errno + return -detail + +class Fuse: + _attrs = ['getattr', 'readlink', 'getdir', 'mknod', 'mkdir', + 'unlink', 'rmdir', 'symlink', 'rename', 'link', 'chmod', + 'chown', 'truncate', 'utime', 'open', 'read', 'write'] + + flags = 0 + def main(self): + d = {'flags': self.flags} + for a in self._attrs: + if hasattr(self,a): + d[a] = ErrnoWrapper(getattr(self, a)) + apply(main, (), d) + +class Xmp(Fuse): + flags = 1 + + def getattr(self, path): + return os.lstat(path) + + def readlink(self, path): + return os.readlink(path) + + def getdir(self, path): + return map(lambda x: (x,0), os.listdir(path)) + + def unlink(self, path): + return os.unlink(path) + + def rmdir(self, path): + return os.rmdir(path) + + def symlink(self, path, path1): + return os.symlink(path, path1) + + def rename(self, path, path1): + return os.rename(path, path1) + + def link(self, path, path1): + return os.link(path, path1) + + def chmod(self, path, mode): + return os.chmod(path, mode) + + def chown(self, path, user, group): + return os.lchown(path, user, group) + + def truncate(self, path, size): + f = open(path, "w+") + return f.truncate(size) + + def mknod(self, path, mode, dev): + """ Python has no os.mknod, so we can only do some things """ + if S_ISREG(mode): + open(path, "w") + else: + return -EINVAL + + def mkdir(self, path, mode): + return os.mkdir(path, mode) + + def utime(self, path, times): + return os.utime(path, times) + + def open(self, path, flags): + os.close(os.open(path, flags)) + return 0 + + def read(self, path, len, offset): + f = open(path, "r") + f.seek(offset) + return f.read(len) + + def write(self, path, buf, off): + f = open(path, "r+") + f.seek(off) + f.write(buf) + return len(buf) + +if __name__ == '__main__': + server = Xmp() + server.flags = DEBUG + server.main() |