├── README ├── MANIFEST.in ├── .svnignore ├── .gitignore ├── setup.py ├── README.rst ├── examples ├── context.py ├── sftp.py ├── loopback.py ├── memory.py └── memoryll.py ├── fusell.py └── fuse.py /README: -------------------------------------------------------------------------------- 1 | README.rst -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README* 2 | -------------------------------------------------------------------------------- /.svnignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | build 4 | dist 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[oc] 2 | *~ 3 | *.swp 4 | /bin 5 | /include 6 | /lib 7 | /lib64 8 | /system 9 | /build 10 | /dist 11 | /fusepy.egg-info 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | 5 | from setuptools import setup 6 | 7 | try: 8 | from lib2to3 import refactor 9 | fixers = set(refactor.get_fixers_from_package('lib2to3.fixes')) 10 | except ImportError: 11 | fixers = set() 12 | 13 | with open('README') as readme: 14 | documentation = readme.read() 15 | 16 | setup( 17 | name = 'fusepy', 18 | version = '2.0.2', 19 | 20 | description = 'Simple ctypes bindings for FUSE', 21 | long_description = documentation, 22 | author = 'Giorgos Verigakis', 23 | author_email = 'verigak@gmail.com', 24 | maintainer = 'Terence Honles', 25 | maintainer_email = 'terence@honles.com', 26 | license = 'ISC', 27 | py_modules=['fuse'], 28 | url = 'http://github.com/terencehonles/fusepy', 29 | 30 | use_2to3 = True, 31 | # only use the following fixers (everything else is already compatible) 32 | use_2to3_exclude_fixers = fixers - set([ 33 | 'lib2to3.fixes.fix_except', 34 | 'lib2to3.fixes.fix_future', 35 | 'lib2to3.fixes.fix_numliterals', 36 | ]), 37 | 38 | classifiers = [ 39 | 'Intended Audience :: Developers', 40 | 'License :: OSI Approved :: ISC License (ISCL)', 41 | 'Operating System :: MacOS', 42 | 'Operating System :: POSIX', 43 | 'Operating System :: Unix', 44 | 'Programming Language :: Python :: 2', 45 | 'Programming Language :: Python :: 3', 46 | 'Topic :: System :: Filesystems', 47 | ] 48 | ) 49 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | fusepy 2 | ====== 3 | 4 | ``fusepy`` is a Python module that provides a simple interface to FUSE_ and 5 | MacFUSE_. It's just one file and is implemented using ctypes. 6 | 7 | The original version of ``fusepy`` was hosted on `Google Code`_, but is now 8 | `officially hosted on GitHub`_. 9 | 10 | ``fusepy`` is written in 2x syntax, but trying to pay attention to bytes and 11 | other changes 3x would care about. The only incompatible changes between 2x and 12 | 3x are the change in syntax for number literals and exceptions. These issues 13 | are fixed using the 2to3 tool when installing the package, or runnning:: 14 | 15 | 2to3 -f numliterals -f except -w fuse.py 16 | 17 | 18 | examples 19 | -------- 20 | See some examples of how you can use fusepy: 21 | 22 | :memory_: A simple memory filesystem 23 | :loopback_: A loopback filesystem 24 | :context_: Sample usage of fuse_get_context() 25 | :sftp_: A simple SFTP filesystem (requires paramiko) 26 | 27 | To get started download_ fusepy or just browse the source_. 28 | 29 | fusepy requires FUSE 2.6 (or later) and runs on: 30 | 31 | - Linux (i386, x86_64, PPC) 32 | - Mac OS X (Intel, PowerPC) 33 | - FreeBSD (i386, amd64) 34 | 35 | 36 | .. _FUSE: http://fuse.sourceforge.net/ 37 | .. _MacFUSE: http://code.google.com/p/macfuse/ 38 | .. _`Google Code`: http://code.google.com/p/fusepy/ 39 | 40 | .. _officially hosted on GitHub: source_ 41 | .. _download: https://github.com/terencehonles/fusepy/zipball/master 42 | .. _source: http://github.com/terencehonles/fusepy 43 | 44 | .. examples 45 | .. _memory: http://github.com/terencehonles/fusepy/blob/master/examples/memory.py 46 | .. _loopback: http://github.com/terencehonles/fusepy/blob/master/examples/loopback.py 47 | .. _context: http://github.com/terencehonles/fusepy/blob/master/examples/context.py 48 | .. _sftp: http://github.com/terencehonles/fusepy/blob/master/examples/sftp.py 49 | -------------------------------------------------------------------------------- /examples/context.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from errno import ENOENT 4 | from stat import S_IFDIR, S_IFREG 5 | from sys import argv, exit 6 | from time import time 7 | 8 | from fuse import FUSE, FuseOSError, Operations, LoggingMixIn, fuse_get_context 9 | 10 | 11 | class Context(LoggingMixIn, Operations): 12 | 'Example filesystem to demonstrate fuse_get_context()' 13 | 14 | def getattr(self, path, fh=None): 15 | uid, gid, pid = fuse_get_context() 16 | if path == '/': 17 | st = dict(st_mode=(S_IFDIR | 0755), st_nlink=2) 18 | elif path == '/uid': 19 | size = len('%s\n' % uid) 20 | st = dict(st_mode=(S_IFREG | 0444), st_size=size) 21 | elif path == '/gid': 22 | size = len('%s\n' % gid) 23 | st = dict(st_mode=(S_IFREG | 0444), st_size=size) 24 | elif path == '/pid': 25 | size = len('%s\n' % pid) 26 | st = dict(st_mode=(S_IFREG | 0444), st_size=size) 27 | else: 28 | raise FuseOSError(ENOENT) 29 | st['st_ctime'] = st['st_mtime'] = st['st_atime'] = time() 30 | return st 31 | 32 | def read(self, path, size, offset, fh): 33 | uid, gid, pid = fuse_get_context() 34 | encoded = lambda x: ('%s\n' % x).encode('utf-8') 35 | 36 | if path == '/uid': 37 | return encoded(uid) 38 | elif path == '/gid': 39 | return encoded(gid) 40 | elif path == '/pid': 41 | return encoded(pid) 42 | 43 | raise RuntimeError('unexpected path: %r' % path) 44 | 45 | def readdir(self, path, fh): 46 | return ['.', '..', 'uid', 'gid', 'pid'] 47 | 48 | # Disable unused operations: 49 | access = None 50 | flush = None 51 | getxattr = None 52 | listxattr = None 53 | open = None 54 | opendir = None 55 | release = None 56 | releasedir = None 57 | statfs = None 58 | 59 | 60 | if __name__ == '__main__': 61 | if len(argv) != 2: 62 | print('usage: %s ' % argv[0]) 63 | exit(1) 64 | 65 | fuse = FUSE(Context(), argv[1], foreground=True, ro=True) 66 | -------------------------------------------------------------------------------- /examples/sftp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from sys import argv, exit 4 | from time import time 5 | 6 | from paramiko import SSHClient 7 | 8 | from fuse import FUSE, Operations, LoggingMixIn 9 | 10 | 11 | class SFTP(LoggingMixIn, Operations): 12 | ''' 13 | A simple SFTP filesystem. Requires paramiko: http://www.lag.net/paramiko/ 14 | 15 | You need to be able to login to remote host without entering a password. 16 | ''' 17 | 18 | def __init__(self, host, path='.'): 19 | self.client = SSHClient() 20 | self.client.load_system_host_keys() 21 | self.client.connect(host) 22 | self.sftp = self.client.open_sftp() 23 | self.root = path 24 | 25 | def chmod(self, path, mode): 26 | return self.sftp.chmod(path, mode) 27 | 28 | def chown(self, path, uid, gid): 29 | return self.sftp.chown(path, uid, gid) 30 | 31 | def create(self, path, mode): 32 | f = self.sftp.open(path, 'w') 33 | f.chmod(mode) 34 | f.close() 35 | return 0 36 | 37 | def destroy(self, path): 38 | self.sftp.close() 39 | self.client.close() 40 | 41 | def getattr(self, path, fh=None): 42 | st = self.sftp.lstat(path) 43 | return dict((key, getattr(st, key)) for key in ('st_atime', 'st_gid', 44 | 'st_mode', 'st_mtime', 'st_size', 'st_uid')) 45 | 46 | def mkdir(self, path, mode): 47 | return self.sftp.mkdir(path, mode) 48 | 49 | def read(self, path, size, offset, fh): 50 | f = self.sftp.open(path) 51 | f.seek(offset, 0) 52 | buf = f.read(size) 53 | f.close() 54 | return buf 55 | 56 | def readdir(self, path, fh): 57 | return ['.', '..'] + [name.encode('utf-8') 58 | for name in self.sftp.listdir(path)] 59 | 60 | def readlink(self, path): 61 | return self.sftp.readlink(path) 62 | 63 | def rename(self, old, new): 64 | return self.sftp.rename(old, self.root + new) 65 | 66 | def rmdir(self, path): 67 | return self.sftp.rmdir(path) 68 | 69 | def symlink(self, target, source): 70 | return self.sftp.symlink(source, target) 71 | 72 | def truncate(self, path, length, fh=None): 73 | return self.sftp.truncate(path, length) 74 | 75 | def unlink(self, path): 76 | return self.sftp.unlink(path) 77 | 78 | def utimens(self, path, times=None): 79 | return self.sftp.utime(path, times) 80 | 81 | def write(self, path, data, offset, fh): 82 | f = self.sftp.open(path, 'r+') 83 | f.seek(offset, 0) 84 | f.write(data) 85 | f.close() 86 | return len(data) 87 | 88 | 89 | if __name__ == '__main__': 90 | if len(argv) != 3: 91 | print('usage: %s ' % argv[0]) 92 | exit(1) 93 | 94 | fuse = FUSE(SFTP(argv[1]), argv[2], foreground=True, nothreads=True) 95 | -------------------------------------------------------------------------------- /examples/loopback.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | 5 | from errno import EACCES 6 | from os.path import realpath 7 | from sys import argv, exit 8 | from threading import Lock 9 | 10 | import os 11 | 12 | from fuse import FUSE, FuseOSError, Operations, LoggingMixIn 13 | 14 | 15 | class Loopback(LoggingMixIn, Operations): 16 | def __init__(self, root): 17 | self.root = realpath(root) 18 | self.rwlock = Lock() 19 | 20 | def __call__(self, op, path, *args): 21 | return super(Loopback, self).__call__(op, self.root + path, *args) 22 | 23 | def access(self, path, mode): 24 | if not os.access(path, mode): 25 | raise FuseOSError(EACCES) 26 | 27 | chmod = os.chmod 28 | chown = os.chown 29 | 30 | def create(self, path, mode): 31 | return os.open(path, os.O_WRONLY | os.O_CREAT, mode) 32 | 33 | def flush(self, path, fh): 34 | return os.fsync(fh) 35 | 36 | def fsync(self, path, datasync, fh): 37 | return os.fsync(fh) 38 | 39 | def getattr(self, path, fh=None): 40 | st = os.lstat(path) 41 | return dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime', 42 | 'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid')) 43 | 44 | getxattr = None 45 | 46 | def link(self, target, source): 47 | return os.link(source, target) 48 | 49 | listxattr = None 50 | mkdir = os.mkdir 51 | mknod = os.mknod 52 | open = os.open 53 | 54 | def read(self, path, size, offset, fh): 55 | with self.rwlock: 56 | os.lseek(fh, offset, 0) 57 | return os.read(fh, size) 58 | 59 | def readdir(self, path, fh): 60 | return ['.', '..'] + os.listdir(path) 61 | 62 | readlink = os.readlink 63 | 64 | def release(self, path, fh): 65 | return os.close(fh) 66 | 67 | def rename(self, old, new): 68 | return os.rename(old, self.root + new) 69 | 70 | rmdir = os.rmdir 71 | 72 | def statfs(self, path): 73 | stv = os.statvfs(path) 74 | return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree', 75 | 'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag', 76 | 'f_frsize', 'f_namemax')) 77 | 78 | def symlink(self, target, source): 79 | return os.symlink(source, target) 80 | 81 | def truncate(self, path, length, fh=None): 82 | with open(path, 'r+') as f: 83 | f.truncate(length) 84 | 85 | unlink = os.unlink 86 | utimens = os.utime 87 | 88 | def write(self, path, data, offset, fh): 89 | with self.rwlock: 90 | os.lseek(fh, offset, 0) 91 | return os.write(fh, data) 92 | 93 | 94 | if __name__ == '__main__': 95 | if len(argv) != 3: 96 | print('usage: %s ' % argv[0]) 97 | exit(1) 98 | 99 | fuse = FUSE(Loopback(argv[1]), argv[2], foreground=True) 100 | -------------------------------------------------------------------------------- /examples/memory.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | 5 | from collections import defaultdict 6 | from errno import ENOENT 7 | from stat import S_IFDIR, S_IFLNK, S_IFREG 8 | from sys import argv, exit 9 | from time import time 10 | 11 | from fuse import FUSE, FuseOSError, Operations, LoggingMixIn 12 | 13 | if not hasattr(__builtins__, 'bytes'): 14 | bytes = str 15 | 16 | class Memory(LoggingMixIn, Operations): 17 | 'Example memory filesystem. Supports only one level of files.' 18 | 19 | def __init__(self): 20 | self.files = {} 21 | self.data = defaultdict(bytes) 22 | self.fd = 0 23 | now = time() 24 | self.files['/'] = dict(st_mode=(S_IFDIR | 0755), st_ctime=now, 25 | st_mtime=now, st_atime=now, st_nlink=2) 26 | 27 | def chmod(self, path, mode): 28 | self.files[path]['st_mode'] &= 0770000 29 | self.files[path]['st_mode'] |= mode 30 | return 0 31 | 32 | def chown(self, path, uid, gid): 33 | self.files[path]['st_uid'] = uid 34 | self.files[path]['st_gid'] = gid 35 | 36 | def create(self, path, mode): 37 | self.files[path] = dict(st_mode=(S_IFREG | mode), st_nlink=1, 38 | st_size=0, st_ctime=time(), st_mtime=time(), 39 | st_atime=time()) 40 | 41 | self.fd += 1 42 | return self.fd 43 | 44 | def getattr(self, path, fh=None): 45 | if path not in self.files: 46 | raise FuseOSError(ENOENT) 47 | 48 | return self.files[path] 49 | 50 | def getxattr(self, path, name, position=0): 51 | attrs = self.files[path].get('attrs', {}) 52 | 53 | try: 54 | return attrs[name] 55 | except KeyError: 56 | return '' # Should return ENOATTR 57 | 58 | def listxattr(self, path): 59 | attrs = self.files[path].get('attrs', {}) 60 | return attrs.keys() 61 | 62 | def mkdir(self, path, mode): 63 | self.files[path] = dict(st_mode=(S_IFDIR | mode), st_nlink=2, 64 | st_size=0, st_ctime=time(), st_mtime=time(), 65 | st_atime=time()) 66 | 67 | self.files['/']['st_nlink'] += 1 68 | 69 | def open(self, path, flags): 70 | self.fd += 1 71 | return self.fd 72 | 73 | def read(self, path, size, offset, fh): 74 | return self.data[path][offset:offset + size] 75 | 76 | def readdir(self, path, fh): 77 | return ['.', '..'] + [x[1:] for x in self.files if x != '/'] 78 | 79 | def readlink(self, path): 80 | return self.data[path] 81 | 82 | def removexattr(self, path, name): 83 | attrs = self.files[path].get('attrs', {}) 84 | 85 | try: 86 | del attrs[name] 87 | except KeyError: 88 | pass # Should return ENOATTR 89 | 90 | def rename(self, old, new): 91 | self.files[new] = self.files.pop(old) 92 | 93 | def rmdir(self, path): 94 | self.files.pop(path) 95 | self.files['/']['st_nlink'] -= 1 96 | 97 | def setxattr(self, path, name, value, options, position=0): 98 | # Ignore options 99 | attrs = self.files[path].setdefault('attrs', {}) 100 | attrs[name] = value 101 | 102 | def statfs(self, path): 103 | return dict(f_bsize=512, f_blocks=4096, f_bavail=2048) 104 | 105 | def symlink(self, target, source): 106 | self.files[target] = dict(st_mode=(S_IFLNK | 0777), st_nlink=1, 107 | st_size=len(source)) 108 | 109 | self.data[target] = source 110 | 111 | def truncate(self, path, length, fh=None): 112 | self.data[path] = self.data[path][:length] 113 | self.files[path]['st_size'] = length 114 | 115 | def unlink(self, path): 116 | self.files.pop(path) 117 | 118 | def utimens(self, path, times=None): 119 | now = time() 120 | atime, mtime = times if times else (now, now) 121 | self.files[path]['st_atime'] = atime 122 | self.files[path]['st_mtime'] = mtime 123 | 124 | def write(self, path, data, offset, fh): 125 | self.data[path] = self.data[path][:offset] + data 126 | self.files[path]['st_size'] = len(self.data[path]) 127 | return len(data) 128 | 129 | 130 | if __name__ == '__main__': 131 | if len(argv) != 2: 132 | print('usage: %s ' % argv[0]) 133 | exit(1) 134 | 135 | logging.getLogger().setLevel(logging.DEBUG) 136 | fuse = FUSE(Memory(), argv[1], foreground=True) 137 | -------------------------------------------------------------------------------- /examples/memoryll.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from collections import defaultdict 4 | from errno import ENOENT, EROFS 5 | from stat import S_IFMT, S_IMODE, S_IFDIR, S_IFREG 6 | from sys import argv, exit 7 | from time import time 8 | 9 | from fusell import FUSELL 10 | 11 | 12 | class Memory(FUSELL): 13 | def create_ino(self): 14 | self.ino += 1 15 | return self.ino 16 | 17 | def init(self, userdata, conn): 18 | self.ino = 1 19 | self.attr = defaultdict(dict) 20 | self.data = defaultdict(str) 21 | self.parent = {} 22 | self.children = defaultdict(dict) 23 | 24 | self.attr[1] = {'st_ino': 1, 'st_mode': S_IFDIR | 0777, 'st_nlink': 2} 25 | self.parent[1] = 1 26 | 27 | forget = None 28 | 29 | def getattr(self, req, ino, fi): 30 | print 'getattr:', ino 31 | attr = self.attr[ino] 32 | if attr: 33 | self.reply_attr(req, attr, 1.0) 34 | else: 35 | self.reply_err(req, ENOENT) 36 | 37 | def lookup(self, req, parent, name): 38 | print 'lookup:', parent, name 39 | children = self.children[parent] 40 | ino = children.get(name, 0) 41 | attr = self.attr[ino] 42 | 43 | if attr: 44 | entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0} 45 | self.reply_entry(req, entry) 46 | else: 47 | self.reply_err(req, ENOENT) 48 | 49 | def mkdir(self, req, parent, name, mode): 50 | print 'mkdir:', parent, name 51 | ino = self.create_ino() 52 | ctx = self.req_ctx(req) 53 | now = time() 54 | attr = { 55 | 'st_ino': ino, 56 | 'st_mode': S_IFDIR | mode, 57 | 'st_nlink': 2, 58 | 'st_uid': ctx['uid'], 59 | 'st_gid': ctx['gid'], 60 | 'st_atime': now, 61 | 'st_mtime': now, 62 | 'st_ctime': now} 63 | 64 | self.attr[ino] = attr 65 | self.attr[parent]['st_nlink'] += 1 66 | self.parent[ino] = parent 67 | self.children[parent][name] = ino 68 | 69 | entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0} 70 | self.reply_entry(req, entry) 71 | 72 | def mknod(self, req, parent, name, mode, rdev): 73 | print 'mknod:', parent, name 74 | ino = self.create_ino() 75 | ctx = self.req_ctx(req) 76 | now = time() 77 | attr = { 78 | 'st_ino': ino, 79 | 'st_mode': mode, 80 | 'st_nlink': 1, 81 | 'st_uid': ctx['uid'], 82 | 'st_gid': ctx['gid'], 83 | 'st_rdev': rdev, 84 | 'st_atime': now, 85 | 'st_mtime': now, 86 | 'st_ctime': now} 87 | 88 | self.attr[ino] = attr 89 | self.attr[parent]['st_nlink'] += 1 90 | self.children[parent][name] = ino 91 | 92 | entry = {'ino': ino, 'attr': attr, 'atttr_timeout': 1.0, 'entry_timeout': 1.0} 93 | self.reply_entry(req, entry) 94 | 95 | def open(self, req, ino, fi): 96 | print 'open:', ino 97 | self.reply_open(req, fi) 98 | 99 | def read(self, req, ino, size, off, fi): 100 | print 'read:', ino, size, off 101 | buf = self.data[ino][off:(off + size)] 102 | self.reply_buf(req, buf) 103 | 104 | def readdir(self, req, ino, size, off, fi): 105 | print 'readdir:', ino 106 | parent = self.parent[ino] 107 | entries = [('.', {'st_ino': ino, 'st_mode': S_IFDIR}), 108 | ('..', {'st_ino': parent, 'st_mode': S_IFDIR})] 109 | for name, child in self.children[ino].items(): 110 | entries.append((name, self.attr[child])) 111 | self.reply_readdir(req, size, off, entries) 112 | 113 | def rename(self, req, parent, name, newparent, newname): 114 | print 'rename:', parent, name, newparent, newname 115 | ino = self.children[parent].pop(name) 116 | self.children[newparent][newname] = ino 117 | self.parent[ino] = newparent 118 | self.reply_err(req, 0) 119 | 120 | def setattr(self, req, ino, attr, to_set, fi): 121 | print 'setattr:', ino, to_set 122 | a = self.attr[ino] 123 | for key in to_set: 124 | if key == 'st_mode': 125 | # Keep the old file type bit fields 126 | a['st_mode'] = S_IFMT(a['st_mode']) | S_IMODE(attr['st_mode']) 127 | else: 128 | a[key] = attr[key] 129 | self.attr[ino] = a 130 | self.reply_attr(req, a, 1.0) 131 | 132 | def write(self, req, ino, buf, off, fi): 133 | print 'write:', ino, off, len(buf) 134 | self.data[ino] = self.data[ino][:off] + buf 135 | self.attr[ino]['st_size'] = len(self.data[ino]) 136 | self.reply_write(req, len(buf)) 137 | 138 | if __name__ == '__main__': 139 | if len(argv) != 2: 140 | print 'usage: %s ' % argv[0] 141 | exit(1) 142 | fuse = Memory(argv[1]) 143 | -------------------------------------------------------------------------------- /fusell.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Giorgos Verigakis 2 | # 3 | # Permission to use, copy, modify, and distribute this software for any 4 | # purpose with or without fee is hereby granted, provided that the above 5 | # copyright notice and this permission notice appear in all copies. 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | from __future__ import division 16 | 17 | from ctypes import * 18 | from ctypes.util import find_library 19 | from errno import * 20 | from functools import partial, wraps 21 | from inspect import getmembers, ismethod 22 | from platform import machine, system 23 | from stat import S_IFDIR, S_IFREG 24 | 25 | 26 | _system = system() 27 | _machine = machine() 28 | 29 | class LibFUSE(CDLL): 30 | def __init__(self): 31 | if _system == 'Darwin': 32 | self.libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL) 33 | super(LibFUSE, self).__init__(find_library('fuse')) 34 | 35 | self.fuse_mount.argtypes = (c_char_p, POINTER(fuse_args)) 36 | self.fuse_mount.restype = c_void_p 37 | self.fuse_lowlevel_new.argtypes = (POINTER(fuse_args), POINTER(fuse_lowlevel_ops), 38 | c_size_t, c_void_p) 39 | self.fuse_lowlevel_new.restype = c_void_p 40 | self.fuse_set_signal_handlers.argtypes = (c_void_p,) 41 | self.fuse_session_add_chan.argtypes = (c_void_p, c_void_p) 42 | self.fuse_session_loop.argtypes = (c_void_p,) 43 | self.fuse_remove_signal_handlers.argtypes = (c_void_p,) 44 | self.fuse_session_remove_chan.argtypes = (c_void_p,) 45 | self.fuse_session_destroy.argtypes = (c_void_p,) 46 | self.fuse_unmount.argtypes = (c_char_p, c_void_p) 47 | 48 | self.fuse_req_ctx.restype = POINTER(fuse_ctx) 49 | self.fuse_req_ctx.argtypes = (fuse_req_t,) 50 | 51 | self.fuse_reply_err.argtypes = (fuse_req_t, c_int) 52 | self.fuse_reply_attr.argtypes = (fuse_req_t, c_void_p, c_double) 53 | self.fuse_reply_entry.argtypes = (fuse_req_t, c_void_p) 54 | self.fuse_reply_open.argtypes = (fuse_req_t, c_void_p) 55 | self.fuse_reply_buf.argtypes = (fuse_req_t, c_char_p, c_size_t) 56 | self.fuse_reply_write.argtypes = (fuse_req_t, c_size_t) 57 | 58 | self.fuse_add_direntry.argtypes = (c_void_p, c_char_p, c_size_t, c_char_p, 59 | c_stat_p, c_off_t) 60 | 61 | class fuse_args(Structure): 62 | _fields_ = [('argc', c_int), ('argv', POINTER(c_char_p)), ('allocated', c_int)] 63 | 64 | class c_timespec(Structure): 65 | _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)] 66 | 67 | class c_stat(Structure): 68 | pass # Platform dependent 69 | 70 | if _system == 'Darwin': 71 | ENOTSUP = 45 72 | c_dev_t = c_int32 73 | c_fsblkcnt_t = c_ulong 74 | c_fsfilcnt_t = c_ulong 75 | c_gid_t = c_uint32 76 | c_mode_t = c_uint16 77 | c_off_t = c_int64 78 | c_pid_t = c_int32 79 | c_uid_t = c_uint32 80 | c_stat._fields_ = [ 81 | ('st_dev', c_dev_t), 82 | ('st_ino', c_uint32), 83 | ('st_mode', c_mode_t), 84 | ('st_nlink', c_uint16), 85 | ('st_uid', c_uid_t), 86 | ('st_gid', c_gid_t), 87 | ('st_rdev', c_dev_t), 88 | ('st_atimespec', c_timespec), 89 | ('st_mtimespec', c_timespec), 90 | ('st_ctimespec', c_timespec), 91 | ('st_size', c_off_t), 92 | ('st_blocks', c_int64), 93 | ('st_blksize', c_int32)] 94 | elif _system == 'Linux': 95 | ENOTSUP = 95 96 | c_dev_t = c_ulonglong 97 | c_fsblkcnt_t = c_ulonglong 98 | c_fsfilcnt_t = c_ulonglong 99 | c_gid_t = c_uint 100 | c_mode_t = c_uint 101 | c_off_t = c_longlong 102 | c_pid_t = c_int 103 | c_uid_t = c_uint 104 | 105 | if _machine == 'x86_64': 106 | c_stat._fields_ = [ 107 | ('st_dev', c_dev_t), 108 | ('st_ino', c_ulong), 109 | ('st_nlink', c_ulong), 110 | ('st_mode', c_mode_t), 111 | ('st_uid', c_uid_t), 112 | ('st_gid', c_gid_t), 113 | ('__pad0', c_int), 114 | ('st_rdev', c_dev_t), 115 | ('st_size', c_off_t), 116 | ('st_blksize', c_long), 117 | ('st_blocks', c_long), 118 | ('st_atimespec', c_timespec), 119 | ('st_mtimespec', c_timespec), 120 | ('st_ctimespec', c_timespec)] 121 | elif _machine == 'ppc': 122 | c_stat._fields_ = [ 123 | ('st_dev', c_dev_t), 124 | ('st_ino', c_ulonglong), 125 | ('st_mode', c_mode_t), 126 | ('st_nlink', c_uint), 127 | ('st_uid', c_uid_t), 128 | ('st_gid', c_gid_t), 129 | ('st_rdev', c_dev_t), 130 | ('__pad2', c_ushort), 131 | ('st_size', c_off_t), 132 | ('st_blksize', c_long), 133 | ('st_blocks', c_longlong), 134 | ('st_atimespec', c_timespec), 135 | ('st_mtimespec', c_timespec), 136 | ('st_ctimespec', c_timespec)] 137 | else: 138 | # i686, use as fallback for everything else 139 | c_stat._fields_ = [ 140 | ('st_dev', c_dev_t), 141 | ('__pad1', c_ushort), 142 | ('__st_ino', c_ulong), 143 | ('st_mode', c_mode_t), 144 | ('st_nlink', c_uint), 145 | ('st_uid', c_uid_t), 146 | ('st_gid', c_gid_t), 147 | ('st_rdev', c_dev_t), 148 | ('__pad2', c_ushort), 149 | ('st_size', c_off_t), 150 | ('st_blksize', c_long), 151 | ('st_blocks', c_longlong), 152 | ('st_atimespec', c_timespec), 153 | ('st_mtimespec', c_timespec), 154 | ('st_ctimespec', c_timespec), 155 | ('st_ino', c_ulonglong)] 156 | else: 157 | raise NotImplementedError('%s is not supported.' % _system) 158 | 159 | class c_statvfs(Structure): 160 | _fields_ = [ 161 | ('f_bsize', c_ulong), 162 | ('f_frsize', c_ulong), 163 | ('f_blocks', c_fsblkcnt_t), 164 | ('f_bfree', c_fsblkcnt_t), 165 | ('f_bavail', c_fsblkcnt_t), 166 | ('f_files', c_fsfilcnt_t), 167 | ('f_ffree', c_fsfilcnt_t), 168 | ('f_favail', c_fsfilcnt_t)] 169 | 170 | class fuse_file_info(Structure): 171 | _fields_ = [ 172 | ('flags', c_int), 173 | ('fh_old', c_ulong), 174 | ('writepage', c_int), 175 | ('direct_io', c_uint, 1), 176 | ('keep_cache', c_uint, 1), 177 | ('flush', c_uint, 1), 178 | ('padding', c_uint, 29), 179 | ('fh', c_uint64), 180 | ('lock_owner', c_uint64)] 181 | 182 | class fuse_ctx(Structure): 183 | _fields_ = [('uid', c_uid_t), ('gid', c_gid_t), ('pid', c_pid_t)] 184 | 185 | fuse_ino_t = c_ulong 186 | fuse_req_t = c_void_p 187 | c_stat_p = POINTER(c_stat) 188 | fuse_file_info_p = POINTER(fuse_file_info) 189 | 190 | FUSE_SET_ATTR = ('st_mode', 'st_uid', 'st_gid', 'st_size', 'st_atime', 'st_mtime') 191 | 192 | class fuse_entry_param(Structure): 193 | _fields_ = [ 194 | ('ino', fuse_ino_t), 195 | ('generation', c_ulong), 196 | ('attr', c_stat), 197 | ('attr_timeout', c_double), 198 | ('entry_timeout', c_double)] 199 | 200 | class fuse_lowlevel_ops(Structure): 201 | _fields_ = [ 202 | ('init', CFUNCTYPE(None, c_void_p, c_void_p)), 203 | ('destroy', CFUNCTYPE(None, c_void_p)), 204 | ('lookup', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)), 205 | ('forget', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_ulong)), 206 | ('getattr', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)), 207 | ('setattr', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_stat_p, c_int, fuse_file_info_p)), 208 | ('readlink', CFUNCTYPE(None, fuse_req_t, fuse_ino_t)), 209 | ('mknod', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_mode_t, c_dev_t)), 210 | ('mkdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_mode_t)), 211 | ('unlink', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)), 212 | ('rmdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p)), 213 | ('symlink', CFUNCTYPE(None, fuse_req_t, c_char_p, fuse_ino_t, c_char_p)), 214 | ('rename', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, fuse_ino_t, c_char_p)), 215 | ('link', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_ino_t, c_char_p)), 216 | ('open', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)), 217 | ('read', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_size_t, c_off_t, fuse_file_info_p)), 218 | ('write', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_char_p, c_size_t, c_off_t, 219 | fuse_file_info_p)), 220 | ('flush', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)), 221 | ('release', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)), 222 | ('fsync', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_int, fuse_file_info_p)), 223 | ('opendir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)), 224 | ('readdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_size_t, c_off_t, fuse_file_info_p)), 225 | ('releasedir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, fuse_file_info_p)), 226 | ('fsyncdir', CFUNCTYPE(None, fuse_req_t, fuse_ino_t, c_int, fuse_file_info_p))] 227 | 228 | 229 | def struct_to_dict(p): 230 | try: 231 | x = p.contents 232 | return dict((key, getattr(x, key)) for key, type in x._fields_) 233 | except ValueError: 234 | return {} 235 | 236 | def stat_to_dict(p): 237 | try: 238 | d = {} 239 | x = p.contents 240 | for key, type in x._fields_: 241 | if key in ('st_atimespec', 'st_mtimespec', 'st_ctimespec'): 242 | ts = getattr(x, key) 243 | key = key[:-4] # Lose the "spec" 244 | d[key] = ts.tv_sec + ts.tv_nsec / 10 ** 9 245 | else: 246 | d[key] = getattr(x, key) 247 | return d 248 | except ValueError: 249 | return {} 250 | 251 | def dict_to_stat(d): 252 | for key in ('st_atime', 'st_mtime', 'st_ctime'): 253 | if key in d: 254 | val = d[key] 255 | sec = int(val) 256 | nsec = int((val - sec) * 10 ** 9) 257 | d[key + 'spec'] = c_timespec(sec, nsec) 258 | return c_stat(**d) 259 | 260 | def setattr_mask_to_list(mask): 261 | return [FUSE_SET_ATTR[i] for i in range(len(FUSE_SET_ATTR)) if mask & (1 << i)] 262 | 263 | class FUSELL(object): 264 | def __init__(self, mountpoint): 265 | self.libfuse = LibFUSE() 266 | 267 | fuse_ops = fuse_lowlevel_ops() 268 | 269 | for name, prototype in fuse_lowlevel_ops._fields_: 270 | method = getattr(self, 'fuse_' + name, None) or getattr(self, name, None) 271 | if method: 272 | setattr(fuse_ops, name, prototype(method)) 273 | 274 | args = ['fuse'] 275 | argv = fuse_args(len(args), (c_char_p * len(args))(*args), 0) 276 | 277 | # TODO: handle initialization errors 278 | 279 | chan = self.libfuse.fuse_mount(mountpoint, argv) 280 | assert chan 281 | 282 | session = self.libfuse.fuse_lowlevel_new(argv, byref(fuse_ops), sizeof(fuse_ops), None) 283 | assert session 284 | 285 | err = self.libfuse.fuse_set_signal_handlers(session) 286 | assert err == 0 287 | 288 | self.libfuse.fuse_session_add_chan(session, chan) 289 | 290 | err = self.libfuse.fuse_session_loop(session) 291 | assert err == 0 292 | 293 | err = self.libfuse.fuse_remove_signal_handlers(session) 294 | assert err == 0 295 | 296 | self.libfuse.fuse_session_remove_chan(chan) 297 | self.libfuse.fuse_session_destroy(session) 298 | self.libfuse.fuse_unmount(mountpoint, chan) 299 | 300 | def reply_err(self, req, err): 301 | return self.libfuse.fuse_reply_err(req, err) 302 | 303 | def reply_none(self, req): 304 | self.libfuse.fuse_reply_none(req) 305 | 306 | def reply_entry(self, req, entry): 307 | entry['attr'] = c_stat(**entry['attr']) 308 | e = fuse_entry_param(**entry) 309 | self.libfuse.fuse_reply_entry(req, byref(e)) 310 | 311 | def reply_create(self, req, *args): 312 | pass # XXX 313 | 314 | def reply_attr(self, req, attr, attr_timeout): 315 | st = dict_to_stat(attr) 316 | return self.libfuse.fuse_reply_attr(req, byref(st), c_double(attr_timeout)) 317 | 318 | def reply_readlink(self, req, *args): 319 | pass # XXX 320 | 321 | def reply_open(self, req, d): 322 | fi = fuse_file_info(**d) 323 | return self.libfuse.fuse_reply_open(req, byref(fi)) 324 | 325 | def reply_write(self, req, count): 326 | return self.libfuse.fuse_reply_write(req, count) 327 | 328 | def reply_buf(self, req, buf): 329 | return self.libfuse.fuse_reply_buf(req, buf, len(buf)) 330 | 331 | def reply_readdir(self, req, size, off, entries): 332 | bufsize = 0 333 | sized_entries = [] 334 | for name, attr in entries: 335 | entsize = self.libfuse.fuse_add_direntry(req, None, 0, name, None, 0) 336 | sized_entries.append((name, attr, entsize)) 337 | bufsize += entsize 338 | 339 | next = 0 340 | buf = create_string_buffer(bufsize) 341 | for name, attr, entsize in sized_entries: 342 | entbuf = cast(addressof(buf) + next, c_char_p) 343 | st = c_stat(**attr) 344 | next += entsize 345 | self.libfuse.fuse_add_direntry(req, entbuf, entsize, name, byref(st), next) 346 | 347 | if off < bufsize: 348 | buf = cast(addressof(buf) + off, c_char_p) if off else buf 349 | return self.libfuse.fuse_reply_buf(req, buf, min(bufsize - off, size)) 350 | else: 351 | return self.libfuse.fuse_reply_buf(req, None, 0) 352 | 353 | 354 | # If you override the following methods you should reply directly 355 | # with the self.libfuse.fuse_reply_* methods. 356 | 357 | def fuse_getattr(self, req, ino, fi): 358 | self.getattr(req, ino, struct_to_dict(fi)) 359 | 360 | def fuse_setattr(self, req, ino, attr, to_set, fi): 361 | attr_dict = stat_to_dict(attr) 362 | to_set_list = setattr_mask_to_list(to_set) 363 | fi_dict = struct_to_dict(fi) 364 | self.setattr(req, ino, attr_dict, to_set_list, fi_dict) 365 | 366 | def fuse_open(self, req, ino, fi): 367 | self.open(req, ino, struct_to_dict(fi)) 368 | 369 | def fuse_read(self, req, ino, size, off, fi): 370 | self.read(req, ino, size, off, fi) 371 | 372 | def fuse_write(self, req, ino, buf, size, off, fi): 373 | buf_str = string_at(buf, size) 374 | fi_dict = struct_to_dict(fi) 375 | self.write(req, ino, buf_str, off, fi_dict) 376 | 377 | def fuse_flush(self, req, ino, fi): 378 | self.flush(req, ino, struct_to_dict(fi)) 379 | 380 | def fuse_release(self, req, ino, fi): 381 | self.release(req, ino, struct_to_dict(fi)) 382 | 383 | def fuse_fsync(self, req, ino, datasync, fi): 384 | self.fsyncdir(req, ino, datasync, struct_to_dict(fi)) 385 | 386 | def fuse_opendir(self, req, ino, fi): 387 | self.opendir(req, ino, struct_to_dict(fi)) 388 | 389 | def fuse_readdir(self, req, ino, size, off, fi): 390 | self.readdir(req, ino, size, off, struct_to_dict(fi)) 391 | 392 | def fuse_releasedir(self, req, ino, fi): 393 | self.releasedir(req, ino, struct_to_dict(fi)) 394 | 395 | def fuse_fsyncdir(self, req, ino, datasync, fi): 396 | self.fsyncdir(req, ino, datasync, struct_to_dict(fi)) 397 | 398 | 399 | # Utility methods 400 | 401 | def req_ctx(self, req): 402 | ctx = self.libfuse.fuse_req_ctx(req) 403 | return struct_to_dict(ctx) 404 | 405 | 406 | # Methods to be overridden in subclasses. 407 | # Reply with the self.reply_* methods. 408 | 409 | def init(self, userdata, conn): 410 | """Initialize filesystem 411 | 412 | There's no reply to this method 413 | """ 414 | pass 415 | 416 | def destroy(self, userdata): 417 | """Clean up filesystem 418 | 419 | There's no reply to this method 420 | """ 421 | pass 422 | 423 | def lookup(self, req, parent, name): 424 | """Look up a directory entry by name and get its attributes. 425 | 426 | Valid replies: 427 | reply_entry 428 | reply_err 429 | """ 430 | self.reply_err(req, ENOENT) 431 | 432 | def forget(self, req, ino, nlookup): 433 | """Forget about an inode 434 | 435 | Valid replies: 436 | reply_none 437 | """ 438 | self.reply_none(req) 439 | 440 | def getattr(self, req, ino, fi): 441 | """Get file attributes 442 | 443 | Valid replies: 444 | reply_attr 445 | reply_err 446 | """ 447 | if ino == 1: 448 | attr = {'st_ino': 1, 'st_mode': S_IFDIR | 0755, 'st_nlink': 2} 449 | self.reply_attr(req, attr, 1.0) 450 | else: 451 | self.reply_err(req, ENOENT) 452 | 453 | def setattr(self, req, ino, attr, to_set, fi): 454 | """Set file attributes 455 | 456 | Valid replies: 457 | reply_attr 458 | reply_err 459 | """ 460 | self.reply_err(req, EROFS) 461 | 462 | def readlink(self, req, ino): 463 | """Read symbolic link 464 | 465 | Valid replies: 466 | reply_readlink 467 | reply_err 468 | """ 469 | self.reply_err(req, ENOENT) 470 | 471 | def mknod(self, req, parent, name, mode, rdev): 472 | """Create file node 473 | 474 | Valid replies: 475 | reply_entry 476 | reply_err 477 | """ 478 | self.reply_err(req, EROFS) 479 | 480 | def mkdir(self, req, parent, name, mode): 481 | """Create a directory 482 | 483 | Valid replies: 484 | reply_entry 485 | reply_err 486 | """ 487 | self.reply_err(req, EROFS) 488 | 489 | def unlink(self, req, parent, name): 490 | """Remove a file 491 | 492 | Valid replies: 493 | reply_err 494 | """ 495 | self.reply_err(req, EROFS) 496 | 497 | def rmdir(self, req, parent, name): 498 | """Remove a directory 499 | 500 | Valid replies: 501 | reply_err 502 | """ 503 | self.reply_err(req, EROFS) 504 | 505 | def symlink(self, req, link, parent, name): 506 | """Create a symbolic link 507 | 508 | Valid replies: 509 | reply_entry 510 | reply_err 511 | """ 512 | self.reply_err(req, EROFS) 513 | 514 | def rename(self, req, parent, name, newparent, newname): 515 | """Rename a file 516 | 517 | Valid replies: 518 | reply_err 519 | """ 520 | self.reply_err(req, EROFS) 521 | 522 | def link(self, req, ino, newparent, newname): 523 | """Create a hard link 524 | 525 | Valid replies: 526 | reply_entry 527 | reply_err 528 | """ 529 | self.reply_err(req, EROFS) 530 | 531 | def open(self, req, ino, fi): 532 | """Open a file 533 | 534 | Valid replies: 535 | reply_open 536 | reply_err 537 | """ 538 | self.reply_open(req, fi) 539 | 540 | def read(self, req, ino, size, off, fi): 541 | """Read data 542 | 543 | Valid replies: 544 | reply_buf 545 | reply_err 546 | """ 547 | self.reply_err(req, EIO) 548 | 549 | def write(self, req, ino, buf, off, fi): 550 | """Write data 551 | 552 | Valid replies: 553 | reply_write 554 | reply_err 555 | """ 556 | self.reply_err(req, EROFS) 557 | 558 | def flush(self, req, ino, fi): 559 | """Flush method 560 | 561 | Valid replies: 562 | reply_err 563 | """ 564 | self.reply_err(req, 0) 565 | 566 | def release(self, req, ino, fi): 567 | """Release an open file 568 | 569 | Valid replies: 570 | reply_err 571 | """ 572 | self.reply_err(req, 0) 573 | 574 | def fsync(self, req, ino, datasync, fi): 575 | """Synchronize file contents 576 | 577 | Valid replies: 578 | reply_err 579 | """ 580 | self.reply_err(req, 0) 581 | 582 | def opendir(self, req, ino, fi): 583 | """Open a directory 584 | 585 | Valid replies: 586 | reply_open 587 | reply_err 588 | """ 589 | self.reply_open(req, fi) 590 | 591 | def readdir(self, req, ino, size, off, fi): 592 | """Read directory 593 | 594 | Valid replies: 595 | reply_readdir 596 | reply_err 597 | """ 598 | if ino == 1: 599 | attr = {'st_ino': 1, 'st_mode': S_IFDIR} 600 | entries = [('.', attr), ('..', attr)] 601 | self.reply_readdir(req, size, off, entries) 602 | else: 603 | self.reply_err(req, ENOENT) 604 | 605 | def releasedir(self, req, ino, fi): 606 | """Release an open directory 607 | 608 | Valid replies: 609 | reply_err 610 | """ 611 | self.reply_err(req, 0) 612 | 613 | def fsyncdir(self, req, ino, datasync, fi): 614 | """Synchronize directory contents 615 | 616 | Valid replies: 617 | reply_err 618 | """ 619 | self.reply_err(req, 0) -------------------------------------------------------------------------------- /fuse.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012 Terence Honles (maintainer) 2 | # Copyright (c) 2008 Giorgos Verigakis (author) 3 | # 4 | # Permission to use, copy, modify, and distribute this software for any 5 | # purpose with or without fee is hereby granted, provided that the above 6 | # copyright notice and this permission notice appear in all copies. 7 | # 8 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | 16 | from __future__ import division 17 | 18 | from ctypes import * 19 | from ctypes.util import find_library 20 | from errno import * 21 | from os import strerror 22 | from platform import machine, system 23 | from signal import signal, SIGINT, SIG_DFL 24 | from stat import S_IFDIR 25 | from traceback import print_exc 26 | 27 | import logging 28 | 29 | try: 30 | from functools import partial 31 | except ImportError: 32 | # http://docs.python.org/library/functools.html#functools.partial 33 | def partial(func, *args, **keywords): 34 | def newfunc(*fargs, **fkeywords): 35 | newkeywords = keywords.copy() 36 | newkeywords.update(fkeywords) 37 | return func(*(args + fargs), **newkeywords) 38 | 39 | newfunc.func = func 40 | newfunc.args = args 41 | newfunc.keywords = keywords 42 | return newfunc 43 | 44 | try: 45 | basestring 46 | except NameError: 47 | basestring = str 48 | 49 | class c_timespec(Structure): 50 | _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)] 51 | 52 | class c_utimbuf(Structure): 53 | _fields_ = [('actime', c_timespec), ('modtime', c_timespec)] 54 | 55 | class c_stat(Structure): 56 | pass # Platform dependent 57 | 58 | _system = system() 59 | _machine = machine() 60 | 61 | if _system == 'Darwin': 62 | _libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL) # libfuse dependency 63 | _libfuse_path = (find_library('fuse4x') or find_library('osxfuse') or 64 | find_library('fuse')) 65 | else: 66 | _libfuse_path = find_library('fuse') 67 | 68 | if not _libfuse_path: 69 | raise EnvironmentError('Unable to find libfuse') 70 | else: 71 | _libfuse = CDLL(_libfuse_path) 72 | 73 | if _system == 'Darwin' and hasattr(_libfuse, 'macfuse_version'): 74 | _system = 'Darwin-MacFuse' 75 | 76 | 77 | if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'): 78 | ENOTSUP = 45 79 | c_dev_t = c_int32 80 | c_fsblkcnt_t = c_ulong 81 | c_fsfilcnt_t = c_ulong 82 | c_gid_t = c_uint32 83 | c_mode_t = c_uint16 84 | c_off_t = c_int64 85 | c_pid_t = c_int32 86 | c_uid_t = c_uint32 87 | setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), 88 | c_size_t, c_int, c_uint32) 89 | getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), 90 | c_size_t, c_uint32) 91 | if _system == 'Darwin': 92 | c_stat._fields_ = [ 93 | ('st_dev', c_dev_t), 94 | ('st_mode', c_mode_t), 95 | ('st_nlink', c_uint16), 96 | ('st_ino', c_uint64), 97 | ('st_uid', c_uid_t), 98 | ('st_gid', c_gid_t), 99 | ('st_rdev', c_dev_t), 100 | ('st_atimespec', c_timespec), 101 | ('st_mtimespec', c_timespec), 102 | ('st_ctimespec', c_timespec), 103 | ('st_birthtimespec', c_timespec), 104 | ('st_size', c_off_t), 105 | ('st_blocks', c_int64), 106 | ('st_blksize', c_int32), 107 | ('st_flags', c_int32), 108 | ('st_gen', c_int32), 109 | ('st_lspare', c_int32), 110 | ('st_qspare', c_int64)] 111 | else: 112 | c_stat._fields_ = [ 113 | ('st_dev', c_dev_t), 114 | ('st_ino', c_uint32), 115 | ('st_mode', c_mode_t), 116 | ('st_nlink', c_uint16), 117 | ('st_uid', c_uid_t), 118 | ('st_gid', c_gid_t), 119 | ('st_rdev', c_dev_t), 120 | ('st_atimespec', c_timespec), 121 | ('st_mtimespec', c_timespec), 122 | ('st_ctimespec', c_timespec), 123 | ('st_size', c_off_t), 124 | ('st_blocks', c_int64), 125 | ('st_blksize', c_int32)] 126 | elif _system == 'Linux': 127 | ENOTSUP = 95 128 | c_dev_t = c_ulonglong 129 | c_fsblkcnt_t = c_ulonglong 130 | c_fsfilcnt_t = c_ulonglong 131 | c_gid_t = c_uint 132 | c_mode_t = c_uint 133 | c_off_t = c_longlong 134 | c_pid_t = c_int 135 | c_uid_t = c_uint 136 | setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), 137 | c_size_t, c_int) 138 | 139 | getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), 140 | c_size_t) 141 | 142 | if _machine == 'x86_64': 143 | c_stat._fields_ = [ 144 | ('st_dev', c_dev_t), 145 | ('st_ino', c_ulong), 146 | ('st_nlink', c_ulong), 147 | ('st_mode', c_mode_t), 148 | ('st_uid', c_uid_t), 149 | ('st_gid', c_gid_t), 150 | ('__pad0', c_int), 151 | ('st_rdev', c_dev_t), 152 | ('st_size', c_off_t), 153 | ('st_blksize', c_long), 154 | ('st_blocks', c_long), 155 | ('st_atimespec', c_timespec), 156 | ('st_mtimespec', c_timespec), 157 | ('st_ctimespec', c_timespec)] 158 | elif _machine == 'ppc': 159 | c_stat._fields_ = [ 160 | ('st_dev', c_dev_t), 161 | ('st_ino', c_ulonglong), 162 | ('st_mode', c_mode_t), 163 | ('st_nlink', c_uint), 164 | ('st_uid', c_uid_t), 165 | ('st_gid', c_gid_t), 166 | ('st_rdev', c_dev_t), 167 | ('__pad2', c_ushort), 168 | ('st_size', c_off_t), 169 | ('st_blksize', c_long), 170 | ('st_blocks', c_longlong), 171 | ('st_atimespec', c_timespec), 172 | ('st_mtimespec', c_timespec), 173 | ('st_ctimespec', c_timespec)] 174 | else: 175 | # i686, use as fallback for everything else 176 | c_stat._fields_ = [ 177 | ('st_dev', c_dev_t), 178 | ('__pad1', c_ushort), 179 | ('__st_ino', c_ulong), 180 | ('st_mode', c_mode_t), 181 | ('st_nlink', c_uint), 182 | ('st_uid', c_uid_t), 183 | ('st_gid', c_gid_t), 184 | ('st_rdev', c_dev_t), 185 | ('__pad2', c_ushort), 186 | ('st_size', c_off_t), 187 | ('st_blksize', c_long), 188 | ('st_blocks', c_longlong), 189 | ('st_atimespec', c_timespec), 190 | ('st_mtimespec', c_timespec), 191 | ('st_ctimespec', c_timespec), 192 | ('st_ino', c_ulonglong)] 193 | else: 194 | raise NotImplementedError('%s is not supported.' % _system) 195 | 196 | 197 | class c_statvfs(Structure): 198 | _fields_ = [ 199 | ('f_bsize', c_ulong), 200 | ('f_frsize', c_ulong), 201 | ('f_blocks', c_fsblkcnt_t), 202 | ('f_bfree', c_fsblkcnt_t), 203 | ('f_bavail', c_fsblkcnt_t), 204 | ('f_files', c_fsfilcnt_t), 205 | ('f_ffree', c_fsfilcnt_t), 206 | ('f_favail', c_fsfilcnt_t)] 207 | 208 | if _system == 'FreeBSD': 209 | c_fsblkcnt_t = c_uint64 210 | c_fsfilcnt_t = c_uint64 211 | setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), 212 | c_size_t, c_int) 213 | 214 | getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), 215 | c_size_t) 216 | 217 | class c_statvfs(Structure): 218 | _fields_ = [ 219 | ('f_bavail', c_fsblkcnt_t), 220 | ('f_bfree', c_fsblkcnt_t), 221 | ('f_blocks', c_fsblkcnt_t), 222 | ('f_favail', c_fsfilcnt_t), 223 | ('f_ffree', c_fsfilcnt_t), 224 | ('f_files', c_fsfilcnt_t), 225 | ('f_bsize', c_ulong), 226 | ('f_flag', c_ulong), 227 | ('f_frsize', c_ulong)] 228 | 229 | class fuse_file_info(Structure): 230 | _fields_ = [ 231 | ('flags', c_int), 232 | ('fh_old', c_ulong), 233 | ('writepage', c_int), 234 | ('direct_io', c_uint, 1), 235 | ('keep_cache', c_uint, 1), 236 | ('flush', c_uint, 1), 237 | ('padding', c_uint, 29), 238 | ('fh', c_uint64), 239 | ('lock_owner', c_uint64)] 240 | 241 | class fuse_context(Structure): 242 | _fields_ = [ 243 | ('fuse', c_voidp), 244 | ('uid', c_uid_t), 245 | ('gid', c_gid_t), 246 | ('pid', c_pid_t), 247 | ('private_data', c_voidp)] 248 | 249 | _libfuse.fuse_get_context.restype = POINTER(fuse_context) 250 | 251 | 252 | class fuse_operations(Structure): 253 | _fields_ = [ 254 | ('getattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat))), 255 | ('readlink', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)), 256 | ('getdir', c_voidp), # Deprecated, use readdir 257 | ('mknod', CFUNCTYPE(c_int, c_char_p, c_mode_t, c_dev_t)), 258 | ('mkdir', CFUNCTYPE(c_int, c_char_p, c_mode_t)), 259 | ('unlink', CFUNCTYPE(c_int, c_char_p)), 260 | ('rmdir', CFUNCTYPE(c_int, c_char_p)), 261 | ('symlink', CFUNCTYPE(c_int, c_char_p, c_char_p)), 262 | ('rename', CFUNCTYPE(c_int, c_char_p, c_char_p)), 263 | ('link', CFUNCTYPE(c_int, c_char_p, c_char_p)), 264 | ('chmod', CFUNCTYPE(c_int, c_char_p, c_mode_t)), 265 | ('chown', CFUNCTYPE(c_int, c_char_p, c_uid_t, c_gid_t)), 266 | ('truncate', CFUNCTYPE(c_int, c_char_p, c_off_t)), 267 | ('utime', c_voidp), # Deprecated, use utimens 268 | ('open', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), 269 | 270 | ('read', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, 271 | c_off_t, POINTER(fuse_file_info))), 272 | 273 | ('write', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, 274 | c_off_t, POINTER(fuse_file_info))), 275 | 276 | ('statfs', CFUNCTYPE(c_int, c_char_p, POINTER(c_statvfs))), 277 | ('flush', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), 278 | ('release', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), 279 | ('fsync', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))), 280 | ('setxattr', setxattr_t), 281 | ('getxattr', getxattr_t), 282 | ('listxattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)), 283 | ('removexattr', CFUNCTYPE(c_int, c_char_p, c_char_p)), 284 | ('opendir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), 285 | 286 | ('readdir', CFUNCTYPE(c_int, c_char_p, c_voidp, 287 | CFUNCTYPE(c_int, c_voidp, c_char_p, 288 | POINTER(c_stat), c_off_t), 289 | c_off_t, POINTER(fuse_file_info))), 290 | 291 | ('releasedir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))), 292 | 293 | ('fsyncdir', CFUNCTYPE(c_int, c_char_p, c_int, 294 | POINTER(fuse_file_info))), 295 | 296 | ('init', CFUNCTYPE(c_voidp, c_voidp)), 297 | ('destroy', CFUNCTYPE(c_voidp, c_voidp)), 298 | ('access', CFUNCTYPE(c_int, c_char_p, c_int)), 299 | 300 | ('create', CFUNCTYPE(c_int, c_char_p, c_mode_t, 301 | POINTER(fuse_file_info))), 302 | 303 | ('ftruncate', CFUNCTYPE(c_int, c_char_p, c_off_t, 304 | POINTER(fuse_file_info))), 305 | 306 | ('fgetattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat), 307 | POINTER(fuse_file_info))), 308 | 309 | ('lock', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info), 310 | c_int, c_voidp)), 311 | 312 | ('utimens', CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))), 313 | ('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong))), 314 | ] 315 | 316 | 317 | def time_of_timespec(ts): 318 | return ts.tv_sec + ts.tv_nsec / 10 ** 9 319 | 320 | def set_st_attrs(st, attrs): 321 | for key, val in attrs.items(): 322 | if key in ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime'): 323 | timespec = getattr(st, key + 'spec') 324 | timespec.tv_sec = int(val) 325 | timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9) 326 | elif hasattr(st, key): 327 | setattr(st, key, val) 328 | 329 | 330 | def fuse_get_context(): 331 | 'Returns a (uid, gid, pid) tuple' 332 | 333 | ctxp = _libfuse.fuse_get_context() 334 | ctx = ctxp.contents 335 | return ctx.uid, ctx.gid, ctx.pid 336 | 337 | 338 | class FuseOSError(OSError): 339 | def __init__(self, errno): 340 | super(FuseOSError, self).__init__(errno, strerror(errno)) 341 | 342 | 343 | class FUSE(object): 344 | ''' 345 | This class is the lower level interface and should not be subclassed under 346 | normal use. Its methods are called by fuse. 347 | 348 | Assumes API version 2.6 or later. 349 | ''' 350 | 351 | OPTIONS = ( 352 | ('foreground', '-f'), 353 | ('debug', '-d'), 354 | ('nothreads', '-s'), 355 | ) 356 | 357 | def __init__(self, operations, mountpoint, raw_fi=False, encoding='utf-8', 358 | **kwargs): 359 | 360 | ''' 361 | Setting raw_fi to True will cause FUSE to pass the fuse_file_info 362 | class as is to Operations, instead of just the fh field. 363 | 364 | This gives you access to direct_io, keep_cache, etc. 365 | ''' 366 | 367 | self.operations = operations 368 | self.raw_fi = raw_fi 369 | self.encoding = encoding 370 | 371 | args = ['fuse'] 372 | 373 | args.extend(flag for arg, flag in self.OPTIONS 374 | if kwargs.pop(arg, False)) 375 | 376 | kwargs.setdefault('fsname', operations.__class__.__name__) 377 | args.append('-o') 378 | args.append(','.join(self._normalize_fuse_options(**kwargs))) 379 | args.append(mountpoint) 380 | 381 | args = [arg.encode(encoding) for arg in args] 382 | argv = (c_char_p * len(args))(*args) 383 | 384 | fuse_ops = fuse_operations() 385 | for name, prototype in fuse_operations._fields_: 386 | if prototype != c_voidp and getattr(operations, name, None): 387 | op = partial(self._wrapper, getattr(self, name)) 388 | setattr(fuse_ops, name, prototype(op)) 389 | 390 | try: 391 | old_handler = signal(SIGINT, SIG_DFL) 392 | except ValueError: 393 | old_handler = SIG_DFL 394 | 395 | err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops), 396 | sizeof(fuse_ops), None) 397 | 398 | try: 399 | signal(SIGINT, old_handler) 400 | except ValueError: 401 | pass 402 | 403 | del self.operations # Invoke the destructor 404 | if err: 405 | raise RuntimeError(err) 406 | 407 | @staticmethod 408 | def _normalize_fuse_options(**kargs): 409 | for key, value in kargs.items(): 410 | if isinstance(value, bool): 411 | if value is True: yield key 412 | else: 413 | yield '%s=%s' % (key, value) 414 | 415 | @staticmethod 416 | def _wrapper(func, *args, **kwargs): 417 | 'Decorator for the methods that follow' 418 | 419 | try: 420 | return func(*args, **kwargs) or 0 421 | except OSError, e: 422 | return -(e.errno or EFAULT) 423 | except: 424 | print_exc() 425 | return -EFAULT 426 | 427 | def getattr(self, path, buf): 428 | return self.fgetattr(path, buf, None) 429 | 430 | def readlink(self, path, buf, bufsize): 431 | ret = self.operations('readlink', path.decode(self.encoding)) \ 432 | .encode(self.encoding) 433 | 434 | # copies a string into the given buffer 435 | # (null terminated and truncated if necessary) 436 | data = create_string_buffer(ret[:bufsize - 1]) 437 | memmove(buf, data, len(data)) 438 | return 0 439 | 440 | def mknod(self, path, mode, dev): 441 | return self.operations('mknod', path.decode(self.encoding), mode, dev) 442 | 443 | def mkdir(self, path, mode): 444 | return self.operations('mkdir', path.decode(self.encoding), mode) 445 | 446 | def unlink(self, path): 447 | return self.operations('unlink', path.decode(self.encoding)) 448 | 449 | def rmdir(self, path): 450 | return self.operations('rmdir', path.decode(self.encoding)) 451 | 452 | def symlink(self, source, target): 453 | 'creates a symlink `target -> source` (e.g. ln -s source target)' 454 | 455 | return self.operations('symlink', target.decode(self.encoding), 456 | source.decode(self.encoding)) 457 | 458 | def rename(self, old, new): 459 | return self.operations('rename', old.decode(self.encoding), 460 | new.decode(self.encoding)) 461 | 462 | def link(self, source, target): 463 | 'creates a hard link `target -> source` (e.g. ln source target)' 464 | 465 | return self.operations('link', target.decode(self.encoding), 466 | source.decode(self.encoding)) 467 | 468 | def chmod(self, path, mode): 469 | return self.operations('chmod', path.decode(self.encoding), mode) 470 | 471 | def chown(self, path, uid, gid): 472 | # Check if any of the arguments is a -1 that has overflowed 473 | if c_uid_t(uid + 1).value == 0: 474 | uid = -1 475 | if c_gid_t(gid + 1).value == 0: 476 | gid = -1 477 | 478 | return self.operations('chown', path.decode(self.encoding), uid, gid) 479 | 480 | def truncate(self, path, length): 481 | return self.operations('truncate', path.decode(self.encoding), length) 482 | 483 | def open(self, path, fip): 484 | fi = fip.contents 485 | if self.raw_fi: 486 | return self.operations('open', path.decode(self.encoding), fi) 487 | else: 488 | fi.fh = self.operations('open', path.decode(self.encoding), 489 | fi.flags) 490 | 491 | return 0 492 | 493 | def read(self, path, buf, size, offset, fip): 494 | if self.raw_fi: 495 | fh = fip.contents 496 | else: 497 | fh = fip.contents.fh 498 | 499 | ret = self.operations('read', path.decode(self.encoding), size, 500 | offset, fh) 501 | 502 | if not ret: return 0 503 | 504 | retsize = len(ret) 505 | assert retsize <= size, \ 506 | 'actual amount read %d greater than expected %d' % (retsize, size) 507 | 508 | data = create_string_buffer(ret, retsize) 509 | memmove(buf, data, retsize) 510 | return retsize 511 | 512 | def write(self, path, buf, size, offset, fip): 513 | data = string_at(buf, size) 514 | 515 | if self.raw_fi: 516 | fh = fip.contents 517 | else: 518 | fh = fip.contents.fh 519 | 520 | return self.operations('write', path.decode(self.encoding), data, 521 | offset, fh) 522 | 523 | def statfs(self, path, buf): 524 | stv = buf.contents 525 | attrs = self.operations('statfs', path.decode(self.encoding)) 526 | for key, val in attrs.items(): 527 | if hasattr(stv, key): 528 | setattr(stv, key, val) 529 | 530 | return 0 531 | 532 | def flush(self, path, fip): 533 | if self.raw_fi: 534 | fh = fip.contents 535 | else: 536 | fh = fip.contents.fh 537 | 538 | return self.operations('flush', path.decode(self.encoding), fh) 539 | 540 | def release(self, path, fip): 541 | if self.raw_fi: 542 | fh = fip.contents 543 | else: 544 | fh = fip.contents.fh 545 | 546 | return self.operations('release', path.decode(self.encoding), fh) 547 | 548 | def fsync(self, path, datasync, fip): 549 | if self.raw_fi: 550 | fh = fip.contents 551 | else: 552 | fh = fip.contents.fh 553 | 554 | return self.operations('fsync', path.decode(self.encoding), datasync, 555 | fh) 556 | 557 | def setxattr(self, path, name, value, size, options, *args): 558 | return self.operations('setxattr', path.decode(self.encoding), 559 | name.decode(self.encoding), 560 | string_at(value, size), options, *args) 561 | 562 | def getxattr(self, path, name, value, size, *args): 563 | ret = self.operations('getxattr', path.decode(self.encoding), 564 | name.decode(self.encoding), *args) 565 | 566 | retsize = len(ret) 567 | # allow size queries 568 | if not value: return retsize 569 | 570 | # do not truncate 571 | if retsize > size: return -ERANGE 572 | 573 | buf = create_string_buffer(ret, retsize) # Does not add trailing 0 574 | memmove(value, buf, retsize) 575 | 576 | return retsize 577 | 578 | def listxattr(self, path, namebuf, size): 579 | attrs = self.operations('listxattr', path.decode(self.encoding)) or '' 580 | ret = '\x00'.join(attrs).encode(self.encoding) + '\x00' 581 | 582 | retsize = len(ret) 583 | # allow size queries 584 | if not namebuf: return retsize 585 | 586 | # do not truncate 587 | if retsize > size: return -ERANGE 588 | 589 | buf = create_string_buffer(ret, retsize) 590 | memmove(namebuf, buf, retsize) 591 | 592 | return retsize 593 | 594 | def removexattr(self, path, name): 595 | return self.operations('removexattr', path.decode(self.encoding), 596 | name.decode(self.encoding)) 597 | 598 | def opendir(self, path, fip): 599 | # Ignore raw_fi 600 | fip.contents.fh = self.operations('opendir', 601 | path.decode(self.encoding)) 602 | 603 | return 0 604 | 605 | def readdir(self, path, buf, filler, offset, fip): 606 | # Ignore raw_fi 607 | for item in self.operations('readdir', path.decode(self.encoding), 608 | fip.contents.fh): 609 | 610 | if isinstance(item, basestring): 611 | name, st, offset = item, None, 0 612 | else: 613 | name, attrs, offset = item 614 | if attrs: 615 | st = c_stat() 616 | set_st_attrs(st, attrs) 617 | else: 618 | st = None 619 | 620 | if filler(buf, name.encode(self.encoding), st, offset) != 0: 621 | break 622 | 623 | return 0 624 | 625 | def releasedir(self, path, fip): 626 | # Ignore raw_fi 627 | return self.operations('releasedir', path.decode(self.encoding), 628 | fip.contents.fh) 629 | 630 | def fsyncdir(self, path, datasync, fip): 631 | # Ignore raw_fi 632 | return self.operations('fsyncdir', path.decode(self.encoding), 633 | datasync, fip.contents.fh) 634 | 635 | def init(self, conn): 636 | return self.operations('init', '/') 637 | 638 | def destroy(self, private_data): 639 | return self.operations('destroy', '/') 640 | 641 | def access(self, path, amode): 642 | return self.operations('access', path.decode(self.encoding), amode) 643 | 644 | def create(self, path, mode, fip): 645 | fi = fip.contents 646 | path = path.decode(self.encoding) 647 | 648 | if self.raw_fi: 649 | return self.operations('create', path, mode, fi) 650 | else: 651 | fi.fh = self.operations('create', path, mode) 652 | return 0 653 | 654 | def ftruncate(self, path, length, fip): 655 | if self.raw_fi: 656 | fh = fip.contents 657 | else: 658 | fh = fip.contents.fh 659 | 660 | return self.operations('truncate', path.decode(self.encoding), 661 | length, fh) 662 | 663 | def fgetattr(self, path, buf, fip): 664 | memset(buf, 0, sizeof(c_stat)) 665 | 666 | st = buf.contents 667 | if not fip: 668 | fh = fip 669 | elif self.raw_fi: 670 | fh = fip.contents 671 | else: 672 | fh = fip.contents.fh 673 | 674 | attrs = self.operations('getattr', path.decode(self.encoding), fh) 675 | set_st_attrs(st, attrs) 676 | return 0 677 | 678 | def lock(self, path, fip, cmd, lock): 679 | if self.raw_fi: 680 | fh = fip.contents 681 | else: 682 | fh = fip.contents.fh 683 | 684 | return self.operations('lock', path.decode(self.encoding), fh, cmd, 685 | lock) 686 | 687 | def utimens(self, path, buf): 688 | if buf: 689 | atime = time_of_timespec(buf.contents.actime) 690 | mtime = time_of_timespec(buf.contents.modtime) 691 | times = (atime, mtime) 692 | else: 693 | times = None 694 | 695 | return self.operations('utimens', path.decode(self.encoding), times) 696 | 697 | def bmap(self, path, blocksize, idx): 698 | return self.operations('bmap', path.decode(self.encoding), blocksize, 699 | idx) 700 | 701 | 702 | class Operations(object): 703 | ''' 704 | This class should be subclassed and passed as an argument to FUSE on 705 | initialization. All operations should raise a FuseOSError exception on 706 | error. 707 | 708 | When in doubt of what an operation should do, check the FUSE header file 709 | or the corresponding system call man page. 710 | ''' 711 | 712 | def __call__(self, op, *args): 713 | if not hasattr(self, op): 714 | raise FuseOSError(EFAULT) 715 | return getattr(self, op)(*args) 716 | 717 | def access(self, path, amode): 718 | return 0 719 | 720 | bmap = None 721 | 722 | def chmod(self, path, mode): 723 | raise FuseOSError(EROFS) 724 | 725 | def chown(self, path, uid, gid): 726 | raise FuseOSError(EROFS) 727 | 728 | def create(self, path, mode, fi=None): 729 | ''' 730 | When raw_fi is False (default case), fi is None and create should 731 | return a numerical file handle. 732 | 733 | When raw_fi is True the file handle should be set directly by create 734 | and return 0. 735 | ''' 736 | 737 | raise FuseOSError(EROFS) 738 | 739 | def destroy(self, path): 740 | 'Called on filesystem destruction. Path is always /' 741 | 742 | pass 743 | 744 | def flush(self, path, fh): 745 | return 0 746 | 747 | def fsync(self, path, datasync, fh): 748 | return 0 749 | 750 | def fsyncdir(self, path, datasync, fh): 751 | return 0 752 | 753 | def getattr(self, path, fh=None): 754 | ''' 755 | Returns a dictionary with keys identical to the stat C structure of 756 | stat(2). 757 | 758 | st_atime, st_mtime and st_ctime should be floats. 759 | 760 | NOTE: There is an incombatibility between Linux and Mac OS X 761 | concerning st_nlink of directories. Mac OS X counts all files inside 762 | the directory, while Linux counts only the subdirectories. 763 | ''' 764 | 765 | if path != '/': 766 | raise FuseOSError(ENOENT) 767 | return dict(st_mode=(S_IFDIR | 0755), st_nlink=2) 768 | 769 | def getxattr(self, path, name, position=0): 770 | raise FuseOSError(ENOTSUP) 771 | 772 | def init(self, path): 773 | ''' 774 | Called on filesystem initialization. (Path is always /) 775 | 776 | Use it instead of __init__ if you start threads on initialization. 777 | ''' 778 | 779 | pass 780 | 781 | def link(self, target, source): 782 | 'creates a hard link `target -> source` (e.g. ln source target)' 783 | 784 | raise FuseOSError(EROFS) 785 | 786 | def listxattr(self, path): 787 | return [] 788 | 789 | lock = None 790 | 791 | def mkdir(self, path, mode): 792 | raise FuseOSError(EROFS) 793 | 794 | def mknod(self, path, mode, dev): 795 | raise FuseOSError(EROFS) 796 | 797 | def open(self, path, flags): 798 | ''' 799 | When raw_fi is False (default case), open should return a numerical 800 | file handle. 801 | 802 | When raw_fi is True the signature of open becomes: 803 | open(self, path, fi) 804 | 805 | and the file handle should be set directly. 806 | ''' 807 | 808 | return 0 809 | 810 | def opendir(self, path): 811 | 'Returns a numerical file handle.' 812 | 813 | return 0 814 | 815 | def read(self, path, size, offset, fh): 816 | 'Returns a string containing the data requested.' 817 | 818 | raise FuseOSError(EIO) 819 | 820 | def readdir(self, path, fh): 821 | ''' 822 | Can return either a list of names, or a list of (name, attrs, offset) 823 | tuples. attrs is a dict as in getattr. 824 | ''' 825 | 826 | return ['.', '..'] 827 | 828 | def readlink(self, path): 829 | raise FuseOSError(ENOENT) 830 | 831 | def release(self, path, fh): 832 | return 0 833 | 834 | def releasedir(self, path, fh): 835 | return 0 836 | 837 | def removexattr(self, path, name): 838 | raise FuseOSError(ENOTSUP) 839 | 840 | def rename(self, old, new): 841 | raise FuseOSError(EROFS) 842 | 843 | def rmdir(self, path): 844 | raise FuseOSError(EROFS) 845 | 846 | def setxattr(self, path, name, value, options, position=0): 847 | raise FuseOSError(ENOTSUP) 848 | 849 | def statfs(self, path): 850 | ''' 851 | Returns a dictionary with keys identical to the statvfs C structure of 852 | statvfs(3). 853 | 854 | On Mac OS X f_bsize and f_frsize must be a power of 2 855 | (minimum 512). 856 | ''' 857 | 858 | return {} 859 | 860 | def symlink(self, target, source): 861 | 'creates a symlink `target -> source` (e.g. ln -s source target)' 862 | 863 | raise FuseOSError(EROFS) 864 | 865 | def truncate(self, path, length, fh=None): 866 | raise FuseOSError(EROFS) 867 | 868 | def unlink(self, path): 869 | raise FuseOSError(EROFS) 870 | 871 | def utimens(self, path, times=None): 872 | 'Times is a (atime, mtime) tuple. If None use current time.' 873 | 874 | return 0 875 | 876 | def write(self, path, data, offset, fh): 877 | raise FuseOSError(EROFS) 878 | 879 | 880 | class LoggingMixIn: 881 | log = logging.getLogger('fuse.log-mixin') 882 | 883 | def __call__(self, op, path, *args): 884 | self.log.debug('-> %s %s %s', op, path, repr(args)) 885 | ret = '[Unhandled Exception]' 886 | try: 887 | ret = getattr(self, op)(path, *args) 888 | return ret 889 | except OSError, e: 890 | ret = str(e) 891 | raise 892 | finally: 893 | self.log.debug('<- %s %s', op, repr(ret)) 894 | --------------------------------------------------------------------------------