977 lines
30 KiB
Python
977 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
|
|
REFUSE
|
|
Simple cross-plattform ctypes bindings for libfuse / FUSE for macOS / WinFsp
|
|
https://github.com/pleiszenburg/refuse
|
|
|
|
src/refuse/low.py: "Low level" FUSE API
|
|
|
|
Copyright (C) 2008-2020 refuse contributors
|
|
|
|
<LICENSE_BLOCK>
|
|
The contents of this file are subject to the Internet Systems Consortium (ISC)
|
|
license ("ISC license" or "License"). You may not use this file except in
|
|
compliance with the License. You may obtain a copy of the License at
|
|
https://opensource.org/licenses/ISC
|
|
https://github.com/pleiszenburg/refuse/blob/master/LICENSE
|
|
|
|
Software distributed under the License is distributed on an "AS IS" basis,
|
|
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
|
|
specific language governing rights and limitations under the License.
|
|
</LICENSE_BLOCK>
|
|
|
|
"""
|
|
|
|
|
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
# IMPORT
|
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
import ctypes
|
|
import errno
|
|
import os
|
|
import warnings
|
|
|
|
from ctypes.util import find_library
|
|
from platform import machine, system
|
|
from signal import signal, SIGINT, SIG_DFL
|
|
from stat import S_IFDIR
|
|
|
|
|
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
# HEADER: TODO organize ...
|
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
_system = system()
|
|
_machine = machine()
|
|
|
|
_libfuse_path = os.environ.get('FUSE_LIBRARY_PATH')
|
|
if not _libfuse_path:
|
|
if _system == 'Darwin':
|
|
# libfuse dependency
|
|
_libiconv = ctypes.CDLL(find_library('iconv'), ctypes.RTLD_GLOBAL)
|
|
|
|
_libfuse_path = (find_library('fuse4x') or find_library('osxfuse') or
|
|
find_library('fuse'))
|
|
else:
|
|
_libfuse_path = find_library('fuse')
|
|
|
|
if not _libfuse_path:
|
|
raise EnvironmentError('Unable to find libfuse')
|
|
else:
|
|
_libfuse = ctypes.CDLL(_libfuse_path)
|
|
|
|
|
|
class LibFUSE(ctypes.CDLL):
|
|
def __init__(self):
|
|
if _system == 'Darwin':
|
|
self.libiconv = _libiconv
|
|
super(LibFUSE, self).__init__(_libfuse_path)
|
|
|
|
self.fuse_mount.argtypes = (
|
|
ctypes.c_char_p, ctypes.POINTER(fuse_args))
|
|
|
|
self.fuse_mount.restype = ctypes.c_void_p
|
|
|
|
self.fuse_lowlevel_new.argtypes = (
|
|
ctypes.POINTER(fuse_args), ctypes.POINTER(fuse_lowlevel_ops),
|
|
ctypes.c_size_t, ctypes.c_void_p)
|
|
|
|
self.fuse_lowlevel_new.restype = ctypes.c_void_p
|
|
self.fuse_set_signal_handlers.argtypes = (ctypes.c_void_p,)
|
|
self.fuse_session_add_chan.argtypes = (
|
|
ctypes.c_void_p, ctypes.c_void_p)
|
|
self.fuse_session_loop.argtypes = (ctypes.c_void_p,)
|
|
self.fuse_remove_signal_handlers.argtypes = (ctypes.c_void_p,)
|
|
self.fuse_session_remove_chan.argtypes = (ctypes.c_void_p,)
|
|
self.fuse_session_destroy.argtypes = (ctypes.c_void_p,)
|
|
self.fuse_unmount.argtypes = (ctypes.c_char_p, ctypes.c_void_p)
|
|
|
|
self.fuse_req_ctx.restype = ctypes.POINTER(fuse_ctx)
|
|
self.fuse_req_ctx.argtypes = (fuse_req_t,)
|
|
|
|
self.fuse_reply_err.argtypes = (fuse_req_t, ctypes.c_int)
|
|
self.fuse_reply_attr.argtypes = (
|
|
fuse_req_t, ctypes.c_void_p, ctypes.c_double)
|
|
self.fuse_reply_entry.argtypes = (fuse_req_t, ctypes.c_void_p)
|
|
self.fuse_reply_open.argtypes = (fuse_req_t, ctypes.c_void_p)
|
|
self.fuse_reply_buf.argtypes = (
|
|
fuse_req_t, ctypes.c_char_p, ctypes.c_size_t)
|
|
self.fuse_reply_none.argtypes = (fuse_req_t,)
|
|
self.fuse_reply_write.argtypes = (fuse_req_t, ctypes.c_size_t)
|
|
self.fuse_reply_readlink.argtypes = (
|
|
fuse_req_t, ctypes.c_char_p)
|
|
|
|
self.fuse_add_direntry.argtypes = (
|
|
ctypes.c_void_p, ctypes.c_char_p, ctypes.c_size_t,
|
|
ctypes.c_char_p, c_stat_p, c_off_t)
|
|
|
|
class fuse_args(ctypes.Structure):
|
|
_fields_ = [
|
|
('argc', ctypes.c_int),
|
|
('argv', ctypes.POINTER(ctypes.c_char_p)),
|
|
('allocated', ctypes.c_int),
|
|
]
|
|
|
|
class c_timespec(ctypes.Structure):
|
|
_fields_ = [
|
|
('tv_sec', ctypes.c_long),
|
|
('tv_nsec', ctypes.c_long),
|
|
]
|
|
|
|
class c_stat(ctypes.Structure):
|
|
pass # Platform dependent
|
|
|
|
if _system == 'Darwin':
|
|
ENOTSUP = 45
|
|
|
|
c_dev_t = ctypes.c_int32
|
|
c_fsblkcnt_t = ctypes.c_ulong
|
|
c_fsfilcnt_t = ctypes.c_ulong
|
|
c_gid_t = ctypes.c_uint32
|
|
c_mode_t = ctypes.c_uint16
|
|
c_off_t = ctypes.c_int64
|
|
c_pid_t = ctypes.c_int32
|
|
c_uid_t = ctypes.c_uint32
|
|
c_stat._fields_ = [
|
|
('st_dev', c_dev_t),
|
|
('st_ino', ctypes.c_uint32),
|
|
('st_mode', c_mode_t),
|
|
('st_nlink', ctypes.c_uint16),
|
|
('st_uid', c_uid_t),
|
|
('st_gid', c_gid_t),
|
|
('st_rdev', c_dev_t),
|
|
('st_atimespec', c_timespec),
|
|
('st_mtimespec', c_timespec),
|
|
('st_ctimespec', c_timespec),
|
|
('st_size', c_off_t),
|
|
('st_blocks', ctypes.c_int64),
|
|
('st_blksize', ctypes.c_int32)]
|
|
elif _system == 'Linux':
|
|
ENOTSUP = 95
|
|
|
|
c_dev_t = ctypes.c_ulonglong
|
|
c_fsblkcnt_t = ctypes.c_ulonglong
|
|
c_fsfilcnt_t = ctypes.c_ulonglong
|
|
c_gid_t = ctypes.c_uint
|
|
c_mode_t = ctypes.c_uint
|
|
c_off_t = ctypes.c_longlong
|
|
c_pid_t = ctypes.c_int
|
|
c_uid_t = ctypes.c_uint
|
|
|
|
if _machine == 'x86_64':
|
|
c_stat._fields_ = [
|
|
('st_dev', c_dev_t),
|
|
('st_ino', ctypes.c_ulong),
|
|
('st_nlink', ctypes.c_ulong),
|
|
('st_mode', c_mode_t),
|
|
('st_uid', c_uid_t),
|
|
('st_gid', c_gid_t),
|
|
('__pad0', ctypes.c_int),
|
|
('st_rdev', c_dev_t),
|
|
('st_size', c_off_t),
|
|
('st_blksize', ctypes.c_long),
|
|
('st_blocks', ctypes.c_long),
|
|
('st_atimespec', c_timespec),
|
|
('st_mtimespec', c_timespec),
|
|
('st_ctimespec', c_timespec)]
|
|
elif _machine == 'mips':
|
|
c_stat._fields_ = [
|
|
('st_dev', c_dev_t),
|
|
('__pad1_1', ctypes.c_ulong),
|
|
('__pad1_2', ctypes.c_ulong),
|
|
('__pad1_3', ctypes.c_ulong),
|
|
('st_ino', ctypes.c_ulong),
|
|
('st_mode', c_mode_t),
|
|
('st_nlink', ctypes.c_ulong),
|
|
('st_uid', c_uid_t),
|
|
('st_gid', c_gid_t),
|
|
('st_rdev', c_dev_t),
|
|
('__pad2_1', ctypes.c_ulong),
|
|
('__pad2_2', ctypes.c_ulong),
|
|
('st_size', c_off_t),
|
|
('__pad3', ctypes.c_ulong),
|
|
('st_atimespec', c_timespec),
|
|
('__pad4', ctypes.c_ulong),
|
|
('st_mtimespec', c_timespec),
|
|
('__pad5', ctypes.c_ulong),
|
|
('st_ctimespec', c_timespec),
|
|
('__pad6', ctypes.c_ulong),
|
|
('st_blksize', ctypes.c_long),
|
|
('st_blocks', ctypes.c_long),
|
|
('__pad7_1', ctypes.c_ulong),
|
|
('__pad7_2', ctypes.c_ulong),
|
|
('__pad7_3', ctypes.c_ulong),
|
|
('__pad7_4', ctypes.c_ulong),
|
|
('__pad7_5', ctypes.c_ulong),
|
|
('__pad7_6', ctypes.c_ulong),
|
|
('__pad7_7', ctypes.c_ulong),
|
|
('__pad7_8', ctypes.c_ulong),
|
|
('__pad7_9', ctypes.c_ulong),
|
|
('__pad7_10', ctypes.c_ulong),
|
|
('__pad7_11', ctypes.c_ulong),
|
|
('__pad7_12', ctypes.c_ulong),
|
|
('__pad7_13', ctypes.c_ulong),
|
|
('__pad7_14', ctypes.c_ulong)]
|
|
elif _machine == 'ppc':
|
|
c_stat._fields_ = [
|
|
('st_dev', c_dev_t),
|
|
('st_ino', ctypes.c_ulonglong),
|
|
('st_mode', c_mode_t),
|
|
('st_nlink', ctypes.c_uint),
|
|
('st_uid', c_uid_t),
|
|
('st_gid', c_gid_t),
|
|
('st_rdev', c_dev_t),
|
|
('__pad2', ctypes.c_ushort),
|
|
('st_size', c_off_t),
|
|
('st_blksize', ctypes.c_long),
|
|
('st_blocks', ctypes.c_longlong),
|
|
('st_atimespec', c_timespec),
|
|
('st_mtimespec', c_timespec),
|
|
('st_ctimespec', c_timespec)]
|
|
else:
|
|
# i686, use as fallback for everything else
|
|
c_stat._fields_ = [
|
|
('st_dev', c_dev_t),
|
|
('__pad1', ctypes.c_ushort),
|
|
('__st_ino', ctypes.c_ulong),
|
|
('st_mode', c_mode_t),
|
|
('st_nlink', ctypes.c_uint),
|
|
('st_uid', c_uid_t),
|
|
('st_gid', c_gid_t),
|
|
('st_rdev', c_dev_t),
|
|
('__pad2', ctypes.c_ushort),
|
|
('st_size', c_off_t),
|
|
('st_blksize', ctypes.c_long),
|
|
('st_blocks', ctypes.c_longlong),
|
|
('st_atimespec', c_timespec),
|
|
('st_mtimespec', c_timespec),
|
|
('st_ctimespec', c_timespec),
|
|
('st_ino', ctypes.c_ulonglong)]
|
|
else:
|
|
raise NotImplementedError('%s is not supported.' % _system)
|
|
|
|
class c_statvfs(ctypes.Structure):
|
|
_fields_ = [
|
|
('f_bsize', ctypes.c_ulong),
|
|
('f_frsize', ctypes.c_ulong),
|
|
('f_blocks', c_fsblkcnt_t),
|
|
('f_bfree', c_fsblkcnt_t),
|
|
('f_bavail', c_fsblkcnt_t),
|
|
('f_files', c_fsfilcnt_t),
|
|
('f_ffree', c_fsfilcnt_t),
|
|
('f_favail', c_fsfilcnt_t)]
|
|
|
|
class fuse_file_info(ctypes.Structure):
|
|
_fields_ = [
|
|
('flags', ctypes.c_int),
|
|
('fh_old', ctypes.c_ulong),
|
|
('writepage', ctypes.c_int),
|
|
('direct_io', ctypes.c_uint, 1),
|
|
('keep_cache', ctypes.c_uint, 1),
|
|
('flush', ctypes.c_uint, 1),
|
|
('nonseekable', ctypes.c_uint, 1),
|
|
('flock_release', ctypes.c_uint, 1),
|
|
('padding', ctypes.c_uint, 27),
|
|
('fh', ctypes.c_uint64),
|
|
('lock_owner', ctypes.c_uint64)]
|
|
|
|
class fuse_ctx(ctypes.Structure):
|
|
_fields_ = [
|
|
('uid', c_uid_t),
|
|
('gid', c_gid_t),
|
|
('pid', c_pid_t),
|
|
]
|
|
|
|
class fuse_forget_data(ctypes.Structure):
|
|
_fields_ = [
|
|
('ino', ctypes.c_uint64), # fuse_ino_t on libfuse3
|
|
('nlookup', ctypes.c_uint64),
|
|
]
|
|
|
|
fuse_ino_t = ctypes.c_ulong
|
|
fuse_req_t = ctypes.c_void_p
|
|
c_stat_p = ctypes.POINTER(c_stat)
|
|
c_bytes_p = ctypes.POINTER(ctypes.c_byte)
|
|
fuse_file_info_p = ctypes.POINTER(fuse_file_info)
|
|
fuse_forget_data_p = ctypes.POINTER(fuse_forget_data)
|
|
|
|
FUSE_SET_ATTR = ('st_mode', 'st_uid', 'st_gid', 'st_size', 'st_atime', 'st_mtime')
|
|
|
|
class fuse_entry_param(ctypes.Structure):
|
|
_fields_ = [
|
|
('ino', fuse_ino_t),
|
|
('generation', ctypes.c_ulong),
|
|
('attr', c_stat),
|
|
('attr_timeout', ctypes.c_double),
|
|
('entry_timeout', ctypes.c_double),
|
|
]
|
|
|
|
class fuse_lowlevel_ops(ctypes.Structure):
|
|
_fields_ = [
|
|
('init', ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)),
|
|
('destroy', ctypes.CFUNCTYPE(None, ctypes.c_void_p)),
|
|
|
|
('lookup', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p)),
|
|
|
|
('forget', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_ulong)), # nlookup is uint64_t in libfuse3
|
|
|
|
('getattr', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
|
|
|
|
('setattr', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, c_stat_p, ctypes.c_int,
|
|
fuse_file_info_p)),
|
|
|
|
('readlink', ctypes.CFUNCTYPE(None, fuse_req_t, fuse_ino_t)),
|
|
|
|
('mknod', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p, c_mode_t,
|
|
c_dev_t)),
|
|
|
|
('mkdir', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p, c_mode_t)),
|
|
|
|
('unlink', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p)),
|
|
|
|
('rmdir', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p)),
|
|
|
|
('symlink', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, ctypes.c_char_p, fuse_ino_t, ctypes.c_char_p)),
|
|
|
|
('rename', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p, fuse_ino_t,
|
|
ctypes.c_char_p)), # There is an extra argument `unsigned int flags` in libfuse3
|
|
|
|
('link', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_ino_t, ctypes.c_char_p)),
|
|
|
|
('open', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
|
|
|
|
('read', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_size_t, c_off_t,
|
|
fuse_file_info_p)),
|
|
|
|
('write', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, c_bytes_p, ctypes.c_size_t,
|
|
c_off_t, fuse_file_info_p)),
|
|
|
|
('flush', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
|
|
|
|
('release', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
|
|
|
|
('fsync', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_int, fuse_file_info_p)),
|
|
|
|
('opendir', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
|
|
|
|
('readdir', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_size_t, c_off_t,
|
|
fuse_file_info_p)),
|
|
|
|
('releasedir', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p)),
|
|
|
|
('fsyncdir', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_int, fuse_file_info_p)),
|
|
|
|
('statfs', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t)),
|
|
|
|
('setxattr', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p, c_bytes_p, ctypes.c_size_t, ctypes.c_int)),
|
|
|
|
('getxattr', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p, ctypes.c_size_t)),
|
|
|
|
('listxattr', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_size_t)),
|
|
|
|
('removexattr', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p)),
|
|
|
|
('access', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_int)),
|
|
|
|
('create', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_char_p, c_mode_t, fuse_file_info_p)),
|
|
|
|
('getlk', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p, ctypes.c_void_p)),
|
|
|
|
('setlk', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p, ctypes.c_void_p, ctypes.c_int)),
|
|
|
|
('bmap', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_size_t, ctypes.c_uint64)),
|
|
|
|
('ioctl', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_int, ctypes.c_void_p, fuse_file_info_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_size_t, ctypes.c_size_t)),
|
|
|
|
('poll', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p, ctypes.c_void_p)),
|
|
|
|
('write_buf', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_void_p, c_off_t, fuse_file_info_p)),
|
|
|
|
('retrieve_reply', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, ctypes.c_void_p, fuse_ino_t, c_off_t, ctypes.c_void_p)),
|
|
|
|
('forget_multi', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, ctypes.c_size_t, fuse_forget_data_p)),
|
|
|
|
('flock', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, fuse_file_info_p, ctypes.c_int)),
|
|
|
|
('fallocate', ctypes.CFUNCTYPE(
|
|
None, fuse_req_t, fuse_ino_t, ctypes.c_int, c_off_t, c_off_t, fuse_file_info_p)),
|
|
|
|
# readdirplus only exists in libfuse3
|
|
#('readdirplus', ctypes.CFUNCTYPE(
|
|
#None, fuse_req_t, fuse_ino_t, ctypes.c_size_t, c_off_t, fuse_file_info_p)),
|
|
]
|
|
|
|
|
|
def struct_to_dict(p):
|
|
try:
|
|
x = p.contents
|
|
return dict((key, getattr(x, key)) for key, type in x._fields_)
|
|
except ValueError:
|
|
return {}
|
|
|
|
def stat_to_dict(p, use_ns=False):
|
|
try:
|
|
d = {}
|
|
x = p.contents
|
|
for key, type in x._fields_:
|
|
if key in ('st_atimespec', 'st_mtimespec', 'st_ctimespec'):
|
|
ts = getattr(x, key)
|
|
key = key[:-4] # Lose the "spec"
|
|
|
|
if use_ns:
|
|
d[key] = ts.tv_sec * 10 ** 9 + ts.tv_nsec
|
|
else:
|
|
d[key] = ts.tv_sec + ts.tv_nsec / 1E9
|
|
else:
|
|
d[key] = getattr(x, key)
|
|
return d
|
|
except ValueError:
|
|
return {}
|
|
|
|
def dict_to_stat(d, use_ns=False):
|
|
for key in ('st_atime', 'st_mtime', 'st_ctime'):
|
|
if key in d:
|
|
val = d[key]
|
|
|
|
if use_ns:
|
|
sec, ns = divmod(int(val), 10 ** 9)
|
|
else:
|
|
sec = int(val)
|
|
nsec = int((val - sec) * 1E9)
|
|
|
|
d[key + 'spec'] = c_timespec(sec, nsec)
|
|
return c_stat(**d)
|
|
|
|
def setattr_mask_to_list(mask):
|
|
return [FUSE_SET_ATTR[i] for i in range(len(FUSE_SET_ATTR)) if mask & (1 << i)]
|
|
|
|
|
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
# CLASS: FUSELL
|
|
# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
|
|
class FUSELL:
|
|
use_ns = False
|
|
|
|
def __init__(self, mountpoint, encoding='utf-8'):
|
|
if not self.use_ns:
|
|
warnings.warn(
|
|
'Time as floating point seconds for utimens is deprecated!\n'
|
|
'To enable time as nanoseconds set the property "use_ns" to '
|
|
'True in your FUSELL class or set your fusepy '
|
|
'requirements to <4.',
|
|
DeprecationWarning)
|
|
|
|
self.libfuse = LibFUSE()
|
|
self.encoding = encoding
|
|
|
|
fuse_ops = fuse_lowlevel_ops()
|
|
|
|
for name, prototype in fuse_lowlevel_ops._fields_:
|
|
method = getattr(self, 'fuse_' + name, None) or getattr(self, name, None)
|
|
if method:
|
|
setattr(fuse_ops, name, prototype(method))
|
|
|
|
args = ['fuse']
|
|
argv = fuse_args(len(args), (ctypes.c_char_p * len(args))(*[arg.encode(self.encoding) for arg in args]), 0)
|
|
|
|
# TODO: handle initialization errors
|
|
|
|
chan = self.libfuse.fuse_mount(mountpoint.encode(encoding), argv)
|
|
assert chan
|
|
|
|
session = self.libfuse.fuse_lowlevel_new(
|
|
argv, ctypes.byref(fuse_ops), ctypes.sizeof(fuse_ops), None)
|
|
assert session
|
|
|
|
try:
|
|
old_handler = signal(SIGINT, SIG_DFL)
|
|
except ValueError:
|
|
old_handler = SIG_DFL
|
|
|
|
err = self.libfuse.fuse_set_signal_handlers(session)
|
|
assert err == 0
|
|
|
|
self.libfuse.fuse_session_add_chan(session, chan)
|
|
|
|
err = self.libfuse.fuse_session_loop(session)
|
|
assert err == 0
|
|
|
|
err = self.libfuse.fuse_remove_signal_handlers(session)
|
|
assert err == 0
|
|
|
|
try:
|
|
signal(SIGINT, old_handler)
|
|
except ValueError:
|
|
pass
|
|
|
|
self.libfuse.fuse_session_remove_chan(chan)
|
|
self.libfuse.fuse_session_destroy(session)
|
|
self.libfuse.fuse_unmount(mountpoint.encode(encoding), chan)
|
|
|
|
def reply_err(self, req, err):
|
|
return self.libfuse.fuse_reply_err(req, err)
|
|
|
|
def reply_none(self, req):
|
|
self.libfuse.fuse_reply_none(req)
|
|
|
|
def reply_entry(self, req, entry):
|
|
entry['attr'] = c_stat(**entry['attr'])
|
|
e = fuse_entry_param(**entry)
|
|
self.libfuse.fuse_reply_entry(req, ctypes.byref(e))
|
|
|
|
def reply_create(self, req, *args):
|
|
pass # XXX
|
|
|
|
def reply_attr(self, req, attr, attr_timeout):
|
|
st = dict_to_stat(attr, use_ns=self.use_ns)
|
|
return self.libfuse.fuse_reply_attr(
|
|
req, ctypes.byref(st), ctypes.c_double(attr_timeout))
|
|
|
|
def reply_readlink(self, req, link):
|
|
return self.libfuse.fuse_reply_readlink(
|
|
req, link.encode(self.encoding))
|
|
|
|
def reply_open(self, req, d):
|
|
fi = fuse_file_info(**d)
|
|
return self.libfuse.fuse_reply_open(req, ctypes.byref(fi))
|
|
|
|
def reply_write(self, req, count):
|
|
return self.libfuse.fuse_reply_write(req, count)
|
|
|
|
def reply_buf(self, req, buf):
|
|
return self.libfuse.fuse_reply_buf(req, buf, len(buf))
|
|
|
|
def reply_readdir(self, req, size, off, entries):
|
|
bufsize = 0
|
|
sized_entries = []
|
|
for name, attr in entries:
|
|
name = name.encode(self.encoding)
|
|
entsize = self.libfuse.fuse_add_direntry(req, None, 0, name, None, 0)
|
|
sized_entries.append((name, attr, entsize))
|
|
bufsize += entsize
|
|
|
|
next = 0
|
|
buf = ctypes.create_string_buffer(bufsize)
|
|
for name, attr, entsize in sized_entries:
|
|
entbuf = ctypes.cast(
|
|
ctypes.addressof(buf) + next, ctypes.c_char_p)
|
|
st = c_stat(**attr)
|
|
next += entsize
|
|
self.libfuse.fuse_add_direntry(
|
|
req, entbuf, entsize, name, ctypes.byref(st), next)
|
|
|
|
if off < bufsize:
|
|
buf = ctypes.cast(
|
|
ctypes.addressof(buf) + off, ctypes.c_char_p) if off else buf
|
|
return self.libfuse.fuse_reply_buf(req, buf, min(bufsize - off, size))
|
|
else:
|
|
return self.libfuse.fuse_reply_buf(req, None, 0)
|
|
|
|
|
|
# If you override the following methods you should reply directly
|
|
# with the self.libfuse.fuse_reply_* methods.
|
|
|
|
def fuse_lookup(self, req, parent, name):
|
|
self.lookup(req, parent, name.decode(self.encoding))
|
|
|
|
def fuse_getattr(self, req, ino, fi):
|
|
self.getattr(req, ino, struct_to_dict(fi))
|
|
|
|
def fuse_setattr(self, req, ino, attr, to_set, fi):
|
|
attr_dict = stat_to_dict(attr, use_ns=self.use_ns)
|
|
to_set_list = setattr_mask_to_list(to_set)
|
|
fi_dict = struct_to_dict(fi)
|
|
self.setattr(req, ino, attr_dict, to_set_list, fi_dict)
|
|
|
|
def fuse_mknod(self, req, parent, name, mode, rdev):
|
|
self.mknod(req, parent, name.decode(self.encoding), mode, rdev)
|
|
|
|
def fuse_mkdir(self, req, parent, name, mode):
|
|
self.mkdir(req, parent, name.decode(self.encoding), mode)
|
|
|
|
def fuse_unlink(self, req, parent, name):
|
|
self.unlink(req, parent, name.decode(self.encoding))
|
|
|
|
def fuse_rmdir(self, req, parent, name):
|
|
self.rmdir(req, parent, name.decode(self.encoding))
|
|
|
|
def fuse_symlink(self, req, link, parent, name):
|
|
self.symlink(req, link.decode(self.encoding), parent, name.decode(self.encoding))
|
|
|
|
def fuse_rename(self, req, parent, name, newparent, newname):
|
|
self.rename(req, parent, name.decode(self.encoding), newparent, newname.decode(self.encoding))
|
|
|
|
def fuse_link(self, req, ino, newparent, newname):
|
|
self.link(req, ino, newparent, newname.decode(self.encoding))
|
|
|
|
def fuse_open(self, req, ino, fi):
|
|
self.open(req, ino, struct_to_dict(fi))
|
|
|
|
def fuse_read(self, req, ino, size, off, fi):
|
|
self.read(req, ino, size, off, fi)
|
|
|
|
def fuse_write(self, req, ino, buf, size, off, fi):
|
|
buf_str = ctypes.string_at(buf, size)
|
|
fi_dict = struct_to_dict(fi)
|
|
self.write(req, ino, buf_str, off, fi_dict)
|
|
|
|
def fuse_flush(self, req, ino, fi):
|
|
self.flush(req, ino, struct_to_dict(fi))
|
|
|
|
def fuse_release(self, req, ino, fi):
|
|
self.release(req, ino, struct_to_dict(fi))
|
|
|
|
def fuse_fsync(self, req, ino, datasync, fi):
|
|
self.fsyncdir(req, ino, datasync, struct_to_dict(fi))
|
|
|
|
def fuse_opendir(self, req, ino, fi):
|
|
self.opendir(req, ino, struct_to_dict(fi))
|
|
|
|
def fuse_readdir(self, req, ino, size, off, fi):
|
|
self.readdir(req, ino, size, off, struct_to_dict(fi))
|
|
|
|
def fuse_releasedir(self, req, ino, fi):
|
|
self.releasedir(req, ino, struct_to_dict(fi))
|
|
|
|
def fuse_fsyncdir(self, req, ino, datasync, fi):
|
|
self.fsyncdir(req, ino, datasync, struct_to_dict(fi))
|
|
|
|
def fuse_setxattr(self, req, ino, name, value, size, flags):
|
|
self.setxattr(req, ino, name.decode(self.encoding), ctypes.string_at(value, size), flags)
|
|
|
|
def fuse_getxattr(self, req, ino, name, size):
|
|
self.getxattr(req, ino, name.decode(self.encoding), size)
|
|
|
|
def fuse_removexattr(self, req, ino, name):
|
|
self.removexattr(req, ino, name.decode(self.encoding))
|
|
|
|
def fuse_create(self, req, parent, name, mode, fi):
|
|
self.create(req, parent, name.decode(self.encoding), mode, struct_to_dict(fi))
|
|
|
|
# Utility methods
|
|
|
|
def req_ctx(self, req):
|
|
ctx = self.libfuse.fuse_req_ctx(req)
|
|
return struct_to_dict(ctx)
|
|
|
|
|
|
# Methods to be overridden in subclasses.
|
|
# Reply with the self.reply_* methods.
|
|
|
|
def init(self, userdata, conn):
|
|
"""Initialize filesystem
|
|
|
|
There's no reply to this method
|
|
"""
|
|
pass
|
|
|
|
def destroy(self, userdata):
|
|
"""Clean up filesystem
|
|
|
|
There's no reply to this method
|
|
"""
|
|
pass
|
|
|
|
def lookup(self, req, parent, name):
|
|
"""Look up a directory entry by name and get its attributes.
|
|
|
|
Valid replies:
|
|
reply_entry
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOENT)
|
|
|
|
def forget(self, req, ino, nlookup):
|
|
"""Forget about an inode
|
|
|
|
Valid replies:
|
|
reply_none
|
|
"""
|
|
self.reply_none(req)
|
|
|
|
def getattr(self, req, ino, fi):
|
|
"""Get file attributes
|
|
|
|
Valid replies:
|
|
reply_attr
|
|
reply_err
|
|
"""
|
|
if ino == 1:
|
|
attr = {'st_ino': 1, 'st_mode': S_IFDIR | 0o755, 'st_nlink': 2}
|
|
self.reply_attr(req, attr, 1.0)
|
|
else:
|
|
self.reply_err(req, errno.ENOENT)
|
|
|
|
def setattr(self, req, ino, attr, to_set, fi):
|
|
"""Set file attributes
|
|
|
|
Valid replies:
|
|
reply_attr
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def readlink(self, req, ino):
|
|
"""Read symbolic link
|
|
|
|
Valid replies:
|
|
reply_readlink
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOENT)
|
|
|
|
def mknod(self, req, parent, name, mode, rdev):
|
|
"""Create file node
|
|
|
|
Valid replies:
|
|
reply_entry
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def mkdir(self, req, parent, name, mode):
|
|
"""Create a directory
|
|
|
|
Valid replies:
|
|
reply_entry
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def unlink(self, req, parent, name):
|
|
"""Remove a file
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def rmdir(self, req, parent, name):
|
|
"""Remove a directory
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def symlink(self, req, link, parent, name):
|
|
"""Create a symbolic link
|
|
|
|
Valid replies:
|
|
reply_entry
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def rename(self, req, parent, name, newparent, newname):
|
|
"""Rename a file
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def link(self, req, ino, newparent, newname):
|
|
"""Create a hard link
|
|
|
|
Valid replies:
|
|
reply_entry
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def open(self, req, ino, fi):
|
|
"""Open a file
|
|
|
|
Valid replies:
|
|
reply_open
|
|
reply_err
|
|
"""
|
|
self.reply_open(req, fi)
|
|
|
|
def read(self, req, ino, size, off, fi):
|
|
"""Read data
|
|
|
|
Valid replies:
|
|
reply_buf
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EIO)
|
|
|
|
def write(self, req, ino, buf, off, fi):
|
|
"""Write data
|
|
|
|
Valid replies:
|
|
reply_write
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.EROFS)
|
|
|
|
def flush(self, req, ino, fi):
|
|
"""Flush method
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, 0)
|
|
|
|
def release(self, req, ino, fi):
|
|
"""Release an open file
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, 0)
|
|
|
|
def fsync(self, req, ino, datasync, fi):
|
|
"""Synchronize file contents
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, 0)
|
|
|
|
def opendir(self, req, ino, fi):
|
|
"""Open a directory
|
|
|
|
Valid replies:
|
|
reply_open
|
|
reply_err
|
|
"""
|
|
self.reply_open(req, fi)
|
|
|
|
def readdir(self, req, ino, size, off, fi):
|
|
"""Read directory
|
|
|
|
Valid replies:
|
|
reply_readdir
|
|
reply_err
|
|
"""
|
|
if ino == 1:
|
|
attr = {'st_ino': 1, 'st_mode': S_IFDIR}
|
|
entries = [('.', attr), ('..', attr)]
|
|
self.reply_readdir(req, size, off, entries)
|
|
else:
|
|
self.reply_err(req, errno.ENOENT)
|
|
|
|
def releasedir(self, req, ino, fi):
|
|
"""Release an open directory
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, 0)
|
|
|
|
def fsyncdir(self, req, ino, datasync, fi):
|
|
"""Synchronize directory contents
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, 0)
|
|
|
|
def statfs(self, req, ino):
|
|
""" Get file system statistics
|
|
|
|
Valid replies:
|
|
reply_statfs
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOSYS)
|
|
|
|
def setxattr(self, req, ino, name, value, flags):
|
|
""" Set an extended attribute
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOSYS)
|
|
|
|
def getxattr(self, req, ino, name, size):
|
|
""" Set an extended attribute
|
|
|
|
Valid replies:
|
|
reply_buf
|
|
reply_data
|
|
reply_xattr
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOSYS)
|
|
|
|
def listxattr(self, req, ino, size):
|
|
"""List extended attribute names
|
|
|
|
Valid replies:
|
|
reply_buf
|
|
reply_data
|
|
reply_xattr
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOSYS)
|
|
|
|
def removexattr(self, req, ino, name):
|
|
"""Remove an extended attribute
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOSYS)
|
|
|
|
def access(self, req, ino, mask):
|
|
"""Check file access permissions
|
|
|
|
Valid replies:
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOSYS)
|
|
|
|
def create(self, req, parent, name, mode, fi):
|
|
"""Create and open a file
|
|
|
|
Valid replies:
|
|
reply_create
|
|
reply_err
|
|
"""
|
|
self.reply_err(req, errno.ENOSYS)
|