1 # Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com>
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.
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.
15 from __future__ import division
18 from ctypes.util import find_library
20 from functools import partial
21 from os import strerror
22 from platform import machine, system
23 from stat import S_IFDIR
24 from traceback import print_exc
27 class c_timespec(Structure):
28 _fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)]
30 class c_utimbuf(Structure):
31 _fields_ = [('actime', c_timespec), ('modtime', c_timespec)]
33 class c_stat(Structure):
34 pass # Platform dependent
37 if _system in ('Darwin', 'FreeBSD'):
38 _libiconv = CDLL(find_library("iconv"), RTLD_GLOBAL) # libfuse dependency
41 c_fsblkcnt_t = c_ulong
42 c_fsfilcnt_t = c_ulong
48 setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
49 c_size_t, c_int, c_uint32)
50 getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
55 ('st_mode', c_mode_t),
56 ('st_nlink', c_uint16),
60 ('st_atimespec', c_timespec),
61 ('st_mtimespec', c_timespec),
62 ('st_ctimespec', c_timespec),
64 ('st_blocks', c_int64),
65 ('st_blksize', c_int32)]
66 elif _system == 'Linux':
69 c_fsblkcnt_t = c_ulonglong
70 c_fsfilcnt_t = c_ulonglong
76 setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t, c_int)
77 getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t)
80 if _machine == 'x86_64':
84 ('st_nlink', c_ulong),
85 ('st_mode', c_mode_t),
91 ('st_blksize', c_long),
92 ('st_blocks', c_long),
93 ('st_atimespec', c_timespec),
94 ('st_mtimespec', c_timespec),
95 ('st_ctimespec', c_timespec)]
96 elif _machine == 'ppc':
99 ('st_ino', c_ulonglong),
100 ('st_mode', c_mode_t),
101 ('st_nlink', c_uint),
104 ('st_rdev', c_dev_t),
105 ('__pad2', c_ushort),
106 ('st_size', c_off_t),
107 ('st_blksize', c_long),
108 ('st_blocks', c_longlong),
109 ('st_atimespec', c_timespec),
110 ('st_mtimespec', c_timespec),
111 ('st_ctimespec', c_timespec)]
113 # i686, use as fallback for everything else
116 ('__pad1', c_ushort),
117 ('__st_ino', c_ulong),
118 ('st_mode', c_mode_t),
119 ('st_nlink', c_uint),
122 ('st_rdev', c_dev_t),
123 ('__pad2', c_ushort),
124 ('st_size', c_off_t),
125 ('st_blksize', c_long),
126 ('st_blocks', c_longlong),
127 ('st_atimespec', c_timespec),
128 ('st_mtimespec', c_timespec),
129 ('st_ctimespec', c_timespec),
130 ('st_ino', c_ulonglong)]
132 raise NotImplementedError('%s is not supported.' % _system)
135 class c_statvfs(Structure):
137 ('f_bsize', c_ulong),
138 ('f_frsize', c_ulong),
139 ('f_blocks', c_fsblkcnt_t),
140 ('f_bfree', c_fsblkcnt_t),
141 ('f_bavail', c_fsblkcnt_t),
142 ('f_files', c_fsfilcnt_t),
143 ('f_ffree', c_fsfilcnt_t),
144 ('f_favail', c_fsfilcnt_t)]
146 if _system == 'FreeBSD':
147 c_fsblkcnt_t = c_uint64
148 c_fsfilcnt_t = c_uint64
149 setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t, c_int)
150 getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte), c_size_t)
151 class c_statvfs(Structure):
153 ('f_bavail', c_fsblkcnt_t),
154 ('f_bfree', c_fsblkcnt_t),
155 ('f_blocks', c_fsblkcnt_t),
156 ('f_favail', c_fsfilcnt_t),
157 ('f_ffree', c_fsfilcnt_t),
158 ('f_files', c_fsfilcnt_t),
159 ('f_bsize', c_ulong),
161 ('f_frsize', c_ulong)]
163 class fuse_file_info(Structure):
167 ('writepage', c_int),
168 ('direct_io', c_uint, 1),
169 ('keep_cache', c_uint, 1),
170 ('flush', c_uint, 1),
171 ('padding', c_uint, 29),
173 ('lock_owner', c_uint64)]
175 class fuse_context(Structure):
181 ('private_data', c_voidp)]
183 class fuse_operations(Structure):
185 ('getattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat))),
186 ('readlink', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
187 ('getdir', c_voidp), # Deprecated, use readdir
188 ('mknod', CFUNCTYPE(c_int, c_char_p, c_mode_t, c_dev_t)),
189 ('mkdir', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
190 ('unlink', CFUNCTYPE(c_int, c_char_p)),
191 ('rmdir', CFUNCTYPE(c_int, c_char_p)),
192 ('symlink', CFUNCTYPE(c_int, c_char_p, c_char_p)),
193 ('rename', CFUNCTYPE(c_int, c_char_p, c_char_p)),
194 ('link', CFUNCTYPE(c_int, c_char_p, c_char_p)),
195 ('chmod', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
196 ('chown', CFUNCTYPE(c_int, c_char_p, c_uid_t, c_gid_t)),
197 ('truncate', CFUNCTYPE(c_int, c_char_p, c_off_t)),
198 ('utime', c_voidp), # Deprecated, use utimens
199 ('open', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
200 ('read', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, c_off_t,
201 POINTER(fuse_file_info))),
202 ('write', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t, c_off_t,
203 POINTER(fuse_file_info))),
204 ('statfs', CFUNCTYPE(c_int, c_char_p, POINTER(c_statvfs))),
205 ('flush', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
206 ('release', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
207 ('fsync', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))),
208 ('setxattr', setxattr_t),
209 ('getxattr', getxattr_t),
210 ('listxattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
211 ('removexattr', CFUNCTYPE(c_int, c_char_p, c_char_p)),
212 ('opendir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
213 ('readdir', CFUNCTYPE(c_int, c_char_p, c_voidp, CFUNCTYPE(c_int, c_voidp,
214 c_char_p, POINTER(c_stat), c_off_t), c_off_t, POINTER(fuse_file_info))),
215 ('releasedir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
216 ('fsyncdir', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))),
217 ('init', CFUNCTYPE(c_voidp, c_voidp)),
218 ('destroy', CFUNCTYPE(c_voidp, c_voidp)),
219 ('access', CFUNCTYPE(c_int, c_char_p, c_int)),
220 ('create', CFUNCTYPE(c_int, c_char_p, c_mode_t, POINTER(fuse_file_info))),
221 ('ftruncate', CFUNCTYPE(c_int, c_char_p, c_off_t, POINTER(fuse_file_info))),
222 ('fgetattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat),
223 POINTER(fuse_file_info))),
224 ('lock', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info), c_int, c_voidp)),
225 ('utimens', CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))),
226 ('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong)))]
229 def time_of_timespec(ts):
230 return ts.tv_sec + ts.tv_nsec / 10 ** 9
232 def set_st_attrs(st, attrs):
233 for key, val in attrs.items():
234 if key in ('st_atime', 'st_mtime', 'st_ctime'):
235 timespec = getattr(st, key + 'spec')
236 timespec.tv_sec = int(val)
237 timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9)
238 elif hasattr(st, key):
239 setattr(st, key, val)
242 _libfuse_path = find_library('fuse')
243 if not _libfuse_path:
244 raise EnvironmentError('Unable to find libfuse')
245 _libfuse = CDLL(_libfuse_path)
246 _libfuse.fuse_get_context.restype = POINTER(fuse_context)
249 def fuse_get_context():
250 """Returns a (uid, gid, pid) tuple"""
251 ctxp = _libfuse.fuse_get_context()
253 return ctx.uid, ctx.gid, ctx.pid
256 class FuseOSError(OSError):
257 def __init__(self, errno):
258 super(FuseOSError, self).__init__(errno, strerror(errno))
262 """This class is the lower level interface and should not be subclassed
263 under normal use. Its methods are called by fuse.
264 Assumes API version 2.6 or later."""
266 def __init__(self, operations, mountpoint, raw_fi=False, **kwargs):
267 """Setting raw_fi to True will cause FUSE to pass the fuse_file_info
268 class as is to Operations, instead of just the fh field.
269 This gives you access to direct_io, keep_cache, etc."""
271 self.operations = operations
274 if kwargs.pop('foreground', False):
276 if kwargs.pop('debug', False):
278 if kwargs.pop('nothreads', False):
280 kwargs.setdefault('fsname', operations.__class__.__name__)
282 args.append(','.join(key if val == True else '%s=%s' % (key, val)
283 for key, val in kwargs.items()))
284 args.append(mountpoint)
285 argv = (c_char_p * len(args))(*args)
287 fuse_ops = fuse_operations()
288 for name, prototype in fuse_operations._fields_:
289 if prototype != c_voidp and getattr(operations, name, None):
290 op = partial(self._wrapper_, getattr(self, name))
291 setattr(fuse_ops, name, prototype(op))
292 err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops),
293 sizeof(fuse_ops), None)
294 del self.operations # Invoke the destructor
296 raise RuntimeError(err)
298 def _wrapper_(self, func, *args, **kwargs):
299 """Decorator for the methods that follow"""
301 return func(*args, **kwargs) or 0
303 return -(e.errno or EFAULT)
308 def getattr(self, path, buf):
309 return self.fgetattr(path, buf, None)
311 def readlink(self, path, buf, bufsize):
312 ret = self.operations('readlink', path)
313 data = create_string_buffer(ret[:bufsize - 1])
314 memmove(buf, data, len(data))
317 def mknod(self, path, mode, dev):
318 return self.operations('mknod', path, mode, dev)
320 def mkdir(self, path, mode):
321 return self.operations('mkdir', path, mode)
323 def unlink(self, path):
324 return self.operations('unlink', path)
326 def rmdir(self, path):
327 return self.operations('rmdir', path)
329 def symlink(self, source, target):
330 return self.operations('symlink', target, source)
332 def rename(self, old, new):
333 return self.operations('rename', old, new)
335 def link(self, source, target):
336 return self.operations('link', target, source)
338 def chmod(self, path, mode):
339 return self.operations('chmod', path, mode)
341 def chown(self, path, uid, gid):
342 # Check if any of the arguments is a -1 that has overflowed
343 if c_uid_t(uid + 1).value == 0:
345 if c_gid_t(gid + 1).value == 0:
347 return self.operations('chown', path, uid, gid)
349 def truncate(self, path, length):
350 return self.operations('truncate', path, length)
352 def open(self, path, fip):
355 return self.operations('open', path, fi)
357 fi.fh = self.operations('open', path, fi.flags)
360 def read(self, path, buf, size, offset, fip):
361 fh = fip.contents if self.raw_fi else fip.contents.fh
362 ret = self.operations('read', path, size, offset, fh)
365 data = create_string_buffer(ret[:size], size)
366 memmove(buf, data, size)
369 def write(self, path, buf, size, offset, fip):
370 data = string_at(buf, size)
371 fh = fip.contents if self.raw_fi else fip.contents.fh
372 return self.operations('write', path, data, offset, fh)
374 def statfs(self, path, buf):
376 attrs = self.operations('statfs', path)
377 for key, val in attrs.items():
378 if hasattr(stv, key):
379 setattr(stv, key, val)
382 def flush(self, path, fip):
383 fh = fip.contents if self.raw_fi else fip.contents.fh
384 return self.operations('flush', path, fh)
386 def release(self, path, fip):
387 fh = fip.contents if self.raw_fi else fip.contents.fh
388 return self.operations('release', path, fh)
390 def fsync(self, path, datasync, fip):
391 fh = fip.contents if self.raw_fi else fip.contents.fh
392 return self.operations('fsync', path, datasync, fh)
394 def setxattr(self, path, name, value, size, options, *args):
395 data = string_at(value, size)
396 return self.operations('setxattr', path, name, data, options, *args)
398 def getxattr(self, path, name, value, size, *args):
399 ret = self.operations('getxattr', path, name, *args)
401 buf = create_string_buffer(ret, retsize) # Does not add trailing 0
405 memmove(value, buf, retsize)
408 def listxattr(self, path, namebuf, size):
409 ret = self.operations('listxattr', path)
410 buf = create_string_buffer('\x00'.join(ret)) if ret else ''
415 memmove(namebuf, buf, bufsize)
418 def removexattr(self, path, name):
419 return self.operations('removexattr', path, name)
421 def opendir(self, path, fip):
423 fip.contents.fh = self.operations('opendir', path)
426 def readdir(self, path, buf, filler, offset, fip):
428 for item in self.operations('readdir', path, fip.contents.fh):
429 if isinstance(item, (str, unicode)):
430 name, st, offset = item, None, 0
432 name, attrs, offset = item
435 set_st_attrs(st, attrs)
438 if filler(buf, name, st, offset) != 0:
442 def releasedir(self, path, fip):
444 return self.operations('releasedir', path, fip.contents.fh)
446 def fsyncdir(self, path, datasync, fip):
448 return self.operations('fsyncdir', path, datasync, fip.contents.fh)
450 def init(self, conn):
451 return self.operations('init', '/')
453 def destroy(self, private_data):
454 return self.operations('destroy', '/')
456 def access(self, path, amode):
457 return self.operations('access', path, amode)
459 def create(self, path, mode, fip):
462 return self.operations('create', path, mode, fi)
464 fi.fh = self.operations('create', path, mode)
467 def ftruncate(self, path, length, fip):
468 fh = fip.contents if self.raw_fi else fip.contents.fh
469 return self.operations('truncate', path, length, fh)
471 def fgetattr(self, path, buf, fip):
472 memset(buf, 0, sizeof(c_stat))
474 fh = fip and (fip.contents if self.raw_fi else fip.contents.fh)
475 attrs = self.operations('getattr', path, fh)
476 set_st_attrs(st, attrs)
479 def lock(self, path, fip, cmd, lock):
480 fh = fip.contents if self.raw_fi else fip.contents.fh
481 return self.operations('lock', path, fh, cmd, lock)
483 def utimens(self, path, buf):
485 atime = time_of_timespec(buf.contents.actime)
486 mtime = time_of_timespec(buf.contents.modtime)
487 times = (atime, mtime)
490 return self.operations('utimens', path, times)
492 def bmap(self, path, blocksize, idx):
493 return self.operations('bmap', path, blocksize, idx)
496 class Operations(object):
497 """This class should be subclassed and passed as an argument to FUSE on
498 initialization. All operations should raise a FuseOSError exception
501 When in doubt of what an operation should do, check the FUSE header
502 file or the corresponding system call man page."""
504 def __call__(self, op, *args):
505 if not hasattr(self, op):
506 raise FuseOSError(EFAULT)
507 return getattr(self, op)(*args)
509 def access(self, path, amode):
514 def chmod(self, path, mode):
515 raise FuseOSError(EROFS)
517 def chown(self, path, uid, gid):
518 raise FuseOSError(EROFS)
520 def create(self, path, mode, fi=None):
521 """When raw_fi is False (default case), fi is None and create should
522 return a numerical file handle.
523 When raw_fi is True the file handle should be set directly by create
525 raise FuseOSError(EROFS)
527 def destroy(self, path):
528 """Called on filesystem destruction. Path is always /"""
531 def flush(self, path, fh):
534 def fsync(self, path, datasync, fh):
537 def fsyncdir(self, path, datasync, fh):
540 def getattr(self, path, fh=None):
541 """Returns a dictionary with keys identical to the stat C structure
543 st_atime, st_mtime and st_ctime should be floats.
544 NOTE: There is an incombatibility between Linux and Mac OS X concerning
545 st_nlink of directories. Mac OS X counts all files inside the directory,
546 while Linux counts only the subdirectories."""
549 raise FuseOSError(ENOENT)
550 return dict(st_mode=(S_IFDIR | 0755), st_nlink=2)
552 def getxattr(self, path, name, position=0):
553 raise FuseOSError(ENOTSUP)
555 def init(self, path):
556 """Called on filesystem initialization. Path is always /
557 Use it instead of __init__ if you start threads on initialization."""
560 def link(self, target, source):
561 raise FuseOSError(EROFS)
563 def listxattr(self, path):
568 def mkdir(self, path, mode):
569 raise FuseOSError(EROFS)
571 def mknod(self, path, mode, dev):
572 raise FuseOSError(EROFS)
574 def open(self, path, flags):
575 """When raw_fi is False (default case), open should return a numerical
577 When raw_fi is True the signature of open becomes:
579 and the file handle should be set directly."""
582 def opendir(self, path):
583 """Returns a numerical file handle."""
586 def read(self, path, size, offset, fh):
587 """Returns a string containing the data requested."""
588 raise FuseOSError(EIO)
590 def readdir(self, path, fh):
591 """Can return either a list of names, or a list of (name, attrs, offset)
592 tuples. attrs is a dict as in getattr."""
595 def readlink(self, path):
596 raise FuseOSError(ENOENT)
598 def release(self, path, fh):
601 def releasedir(self, path, fh):
604 def removexattr(self, path, name):
605 raise FuseOSError(ENOTSUP)
607 def rename(self, old, new):
608 raise FuseOSError(EROFS)
610 def rmdir(self, path):
611 raise FuseOSError(EROFS)
613 def setxattr(self, path, name, value, options, position=0):
614 raise FuseOSError(ENOTSUP)
616 def statfs(self, path):
617 """Returns a dictionary with keys identical to the statvfs C structure
619 On Mac OS X f_bsize and f_frsize must be a power of 2 (minimum 512)."""
622 def symlink(self, target, source):
623 raise FuseOSError(EROFS)
625 def truncate(self, path, length, fh=None):
626 raise FuseOSError(EROFS)
628 def unlink(self, path):
629 raise FuseOSError(EROFS)
631 def utimens(self, path, times=None):
632 """Times is a (atime, mtime) tuple. If None use current time."""
635 def write(self, path, data, offset, fh):
636 raise FuseOSError(EROFS)
640 def __call__(self, op, path, *args):
641 print '->', op, path, repr(args)
642 ret = '[Unhandled Exception]'
644 ret = getattr(self, op)(path, *args)
650 print '<-', op, repr(ret)