added python bindings
authorMiklos Szeredi <miklos@szeredi.hu>
Wed, 14 Nov 2001 08:16:20 +0000 (08:16 +0000)
committerMiklos Szeredi <miklos@szeredi.hu>
Wed, 14 Nov 2001 08:16:20 +0000 (08:16 +0000)
AUTHORS
ChangeLog
README.python [new file with mode: 0644]
python/_fusemodule.c [new file with mode: 0644]
python/fuse.py [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
index f74e6de1050bcf7b40c758c21e5c23eb21eed815..5d0a86a98d24d0ff0f145693b01ee55e9fffe2b1 100644 (file)
--- a/AUTHORS
+++ b/AUTHORS
@@ -1 +1,10 @@
+FUSE core
+---------
+
 Miklos Szeredi     <mszeredi@inf.bme.hu>
+
+
+Python bindings
+---------------
+
+Jeff Epler         <jepler@unpythonic.dhs.org>
index 48cc69994b61e06737cbfd8ff85546e3010783cf..cfa7aef5cb31c241d97ba3964ed62224f111c293 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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 (file)
index 0000000..451c56c
--- /dev/null
@@ -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 (file)
index 0000000..eee4fda
--- /dev/null
@@ -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 (file)
index 0000000..ec40262
--- /dev/null
@@ -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()