diff --git a/FSNode.py b/FSNode.py
index 5deb281..760197c 100644
--- a/FSNode.py
+++ b/FSNode.py
@@ -84,9 +84,7 @@ class FSNode:
st_ctime=time.time(),
st_mtime=time.time(),
st_atime=time.time(),
- st_nlink=len(self.children),
- st_uid=os.getuid(),
- st_gid=os.getgid()
+ st_nlink=len(self.children)
)
else:
return dict(
@@ -95,9 +93,7 @@ class FSNode:
st_mtime=time.time(),
st_atime=time.time(),
st_nlink=2,
- st_size=self.size,
- st_uid=os.getuid(),
- st_gid=os.getgid()
+ st_size=self.size
)
def find_child_by_name(self, name: str):
@@ -267,7 +263,6 @@ class FSNode:
f.children.append(readme)
return f
-
@staticmethod
def from_label(mo: Label, parent, m) -> None:
f = parent.find_child_by_name(slugify("README.md"))
@@ -283,12 +278,18 @@ class FSNode:
@staticmethod
def from_url(mo: Url, parent, m):
- f = FSNode(mo.name, parent, False)
- f.size = len(mo.url)
- f.linkTo = UrlLink(mo.url)
+ if os.name == 'nt':
+ f = FSNode(mo.name+".url", parent, False)
+ file_content = "[InternetShortcut]\nURL="+mo.url
+ f.size = len(file_content)
+ f.linkTo = UrlLink(file_content)
+ else:
+ f = FSNode(mo.name+".txt", parent, False)
+ f.size = len(mo.url)
+ f.linkTo = UrlLink(mo.url)
return f
def get_full_path(self) -> str:
if self.parent is None:
return "/"
- return self.parent.get_full_path() + self.name + "/"
+ return self.parent.get_full_path() + self.name + "/"
\ No newline at end of file
diff --git a/README.md b/README.md
index 6386886..30bdb3e 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,10 @@ This is a FUSE filesystem for Moodle. It allows you to mount your Moodle site as
It is a concept that has been around for a while, but I have not found any implementations that I could get to work, or that were not abandoned.
This is my attempt at creating one.
+## Windows binary
+
+You can easily grab a windows binary in the _realeases_ section, it behaves as the program, make sure to place the env file alongside it
+
## Installation
First, you need to create a `.env` file with the following variables:
@@ -26,6 +30,9 @@ python moodlefuse.py
This only works on Linux, as it uses FUSE. I have not tested it on Mac, but it could work there too.
+> Q: Why is _refuse_ in the project root?
+> It's alpha software with some errors, I managed to make it fit for this project for now
+
### Requirements
@@ -51,4 +58,4 @@ This only works on Linux, as it uses FUSE. I have not tested it on Mac, but it c
### Demo
-
+
\ No newline at end of file
diff --git a/main.py b/main.py
index 1e9704c..4b1bc46 100644
--- a/main.py
+++ b/main.py
@@ -1,6 +1,6 @@
import logging
-
-from fuse import FUSE
+import sys
+from refuse.high import FUSE
from moodle import Moodle
from moodlefs import MoodleFS
diff --git a/moodlefs.py b/moodlefs.py
index 40b41f5..d4b1dea 100644
--- a/moodlefs.py
+++ b/moodlefs.py
@@ -1,7 +1,7 @@
from errno import ENOENT, EACCES
-import fuse
-from fuse import LoggingMixIn, Operations, FuseOSError
+from refuse import high as fuse
+from refuse.high import LoggingMixIn, Operations, FuseOSError
from FSNode import FSNode
@@ -30,10 +30,10 @@ class MoodleFS(LoggingMixIn, Operations):
return s.to_stat_struct()
def getxattr(self, path, name, position=0):
- pass
+ raise fuse.FuseOSError(EACCES)
def listxattr(self, path):
- return []
+ raise fuse.FuseOSError(EACCES)
def mkdir(self, path, mode):
raise fuse.FuseOSError(EACCES)
@@ -79,4 +79,4 @@ class MoodleFS(LoggingMixIn, Operations):
pass
def write(self, path, data, offset, fh):
- raise fuse.FuseOSError(EACCES)
+ raise fuse.FuseOSError(EACCES)
\ No newline at end of file
diff --git a/refuse/__init__.py b/refuse/__init__.py
new file mode 100644
index 0000000..c99c7b5
--- /dev/null
+++ b/refuse/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+"""
+
+REFUSE
+Simple cross-plattform ctypes bindings for libfuse / FUSE for macOS / WinFsp
+https://github.com/pleiszenburg/refuse
+
+ src/refuse/__init__.py: Package root
+
+ Copyright (C) 2008-2020 refuse contributors
+
+
+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.
+
+
+"""
diff --git a/refuse/_high_header.py b/refuse/_high_header.py
new file mode 100644
index 0000000..baf49df
--- /dev/null
+++ b/refuse/_high_header.py
@@ -0,0 +1,482 @@
+# -*- coding: utf-8 -*-
+
+"""
+
+REFUSE
+Simple cross-plattform ctypes bindings for libfuse / FUSE for macOS / WinFsp
+https://github.com/pleiszenburg/refuse
+
+ src/refuse/_high_header.py: Temporary helper for code refactoring
+
+ THIS FILE IS TEMPORARY AND WILL BE REMOVED!
+
+ Copyright (C) 2008-2020 refuse contributors
+
+
+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.
+
+
+"""
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# IMPORT
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+import ctypes
+import sys
+
+from ._inventory import dump_globals, get_arch
+
+if __name__ == '__main__': # DEBUG
+ _system, _machine = sys.argv[1], sys.argv[2]
+else: # REGULAR
+ _system, _machine = get_arch()
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# HEADER: TYPES
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+if _system == 'Windows':
+ # NOTE:
+ #
+ # sizeof(long)==4 on Windows 32-bit and 64-bit
+ # sizeof(long)==4 on Cygwin 32-bit and ==8 on Cygwin 64-bit
+ #
+ # We have to fix up c_long and c_ulong so that it matches the
+ # Cygwin (and UNIX) sizes when run on Windows.
+ import sys
+ if sys.maxsize > 0xffffffff:
+ c_win_long = ctypes.c_int64
+ c_win_ulong = ctypes.c_uint64
+ else:
+ c_win_long = ctypes.c_int32
+ c_win_ulong = ctypes.c_uint32
+
+if _system == 'Windows' or _system == 'CYGWIN':
+ class c_timespec(ctypes.Structure):
+ _fields_ = [('tv_sec', c_win_long), ('tv_nsec', c_win_long)]
+elif _system == 'OpenBSD':
+ c_time_t = ctypes.c_int64
+ class c_timespec(ctypes.Structure):
+ _fields_ = [('tv_sec', c_time_t), ('tv_nsec', ctypes.c_long)]
+else:
+ class c_timespec(ctypes.Structure):
+ _fields_ = [('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long)]
+
+class c_utimbuf(ctypes.Structure):
+ _fields_ = [('actime', c_timespec), ('modtime', c_timespec)]
+
+class c_stat(ctypes.Structure):
+ pass # Platform dependent
+
+if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'):
+ 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
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int,
+ ctypes.c_uint32)
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t, ctypes.c_uint32)
+ if _system == 'Darwin':
+ c_stat._fields_ = [
+ ('st_dev', c_dev_t),
+ ('st_mode', c_mode_t),
+ ('st_nlink', ctypes.c_uint16),
+ ('st_ino', ctypes.c_uint64),
+ ('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_birthtimespec', c_timespec),
+ ('st_size', c_off_t),
+ ('st_blocks', ctypes.c_int64),
+ ('st_blksize', ctypes.c_int32),
+ ('st_flags', ctypes.c_int32),
+ ('st_gen', ctypes.c_int32),
+ ('st_lspare', ctypes.c_int32),
+ ('st_qspare', ctypes.c_int64)]
+ else: # MacFuse, FreeBSD
+ 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
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t)
+
+ 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)]
+ elif _machine == 'ppc64' or _machine == 'ppc64le':
+ 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),
+ ('__pad', ctypes.c_uint),
+ ('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 == 'aarch64':
+ c_stat._fields_ = [
+ ('st_dev', c_dev_t),
+ ('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),
+ ('__pad1', ctypes.c_ulong),
+ ('st_size', c_off_t),
+ ('st_blksize', ctypes.c_int),
+ ('__pad2', ctypes.c_int),
+ ('st_blocks', ctypes.c_long),
+ ('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)]
+elif _system == 'Windows' or _system == 'CYGWIN':
+ ENOTSUP = 129 if _system == 'Windows' else 134
+ c_dev_t = ctypes.c_uint
+ c_fsblkcnt_t = c_win_ulong
+ c_fsfilcnt_t = c_win_ulong
+ 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
+ setxattr_t = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+ getxattr_t = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t)
+ c_stat._fields_ = [
+ ('st_dev', c_dev_t),
+ ('st_ino', ctypes.c_ulonglong),
+ ('st_mode', c_mode_t),
+ ('st_nlink', ctypes.c_ushort),
+ ('st_uid', c_uid_t),
+ ('st_gid', c_gid_t),
+ ('st_rdev', c_dev_t),
+ ('st_size', c_off_t),
+ ('st_atimespec', c_timespec),
+ ('st_mtimespec', c_timespec),
+ ('st_ctimespec', c_timespec),
+ ('st_blksize', ctypes.c_int),
+ ('st_blocks', ctypes.c_longlong),
+ ('st_birthtimespec', c_timespec)]
+elif _system == 'OpenBSD':
+ ENOTSUP = 91
+ c_dev_t = ctypes.c_int32
+ c_uid_t = ctypes.c_uint32
+ c_gid_t = ctypes.c_uint32
+ c_mode_t = ctypes.c_uint32
+ c_off_t = ctypes.c_int64
+ c_pid_t = ctypes.c_int32
+ c_ino_t = ctypes.c_uint64
+ c_nlink_t = ctypes.c_uint32
+ c_blkcnt_t = ctypes.c_int64
+ c_blksize_t = ctypes.c_int32
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t)
+ c_fsblkcnt_t = ctypes.c_uint64
+ c_fsfilcnt_t = ctypes.c_uint64
+ c_stat._fields_ = [
+ ('st_mode', c_mode_t),
+ ('st_dev', c_dev_t),
+ ('st_ino', c_ino_t),
+ ('st_nlink', c_nlink_t),
+ ('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', c_blkcnt_t),
+ ('st_blksize', c_blksize_t),
+ ('st_flags', ctypes.c_uint32),
+ ('st_gen', ctypes.c_uint32),
+ ('st_birthtimespec', c_timespec),
+ ]
+else:
+ raise NotImplementedError('%s is not supported.' % _system)
+
+
+if _system == 'FreeBSD':
+ c_fsblkcnt_t = ctypes.c_uint64
+ c_fsfilcnt_t = ctypes.c_uint64
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t)
+
+ class c_statvfs(ctypes.Structure):
+ _fields_ = [
+ ('f_bavail', c_fsblkcnt_t),
+ ('f_bfree', c_fsblkcnt_t),
+ ('f_blocks', c_fsblkcnt_t),
+ ('f_favail', c_fsfilcnt_t),
+ ('f_ffree', c_fsfilcnt_t),
+ ('f_files', c_fsfilcnt_t),
+ ('f_bsize', ctypes.c_ulong),
+ ('f_flag', ctypes.c_ulong),
+ ('f_frsize', ctypes.c_ulong)]
+elif _system == 'Windows' or _system == 'CYGWIN':
+ class c_statvfs(ctypes.Structure):
+ _fields_ = [
+ ('f_bsize', c_win_ulong),
+ ('f_frsize', c_win_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),
+ ('f_fsid', c_win_ulong),
+ ('f_flag', c_win_ulong),
+ ('f_namemax', c_win_ulong)]
+else:
+ 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),
+ ('f_fsid', ctypes.c_ulong),
+ # ('unused', ctypes.c_int),
+ ('f_flag', ctypes.c_ulong),
+ ('f_namemax', ctypes.c_ulong)]
+
+if _system == 'Windows' or _system == 'CYGWIN':
+ class fuse_file_info(ctypes.Structure):
+ _fields_ = [
+ ('flags', ctypes.c_int),
+ ('fh_old', ctypes.c_int),
+ ('writepage', ctypes.c_int),
+ ('direct_io', ctypes.c_uint, 1),
+ ('keep_cache', ctypes.c_uint, 1),
+ ('flush', ctypes.c_uint, 1),
+ ('padding', ctypes.c_uint, 29),
+ ('fh', ctypes.c_uint64),
+ ('lock_owner', ctypes.c_uint64)]
+elif _system == "OpenBSD":
+ class fuse_file_info(ctypes.Structure):
+ _fields_ = [
+ ('flags', ctypes.c_int32),
+ ('fh_old', ctypes.c_uint32),
+ ('writepage', ctypes.c_int32),
+ ('direct_io', ctypes.c_uint32, 1),
+ ('keep_cache', ctypes.c_uint32, 1),
+ ('flush', ctypes.c_uint32, 1),
+ ('nonseekable', ctypes.c_uint32, 1),
+ ('padding', ctypes.c_uint32, 27),
+ ('flock_release', ctypes.c_uint32, 1),
+ ('fh', ctypes.c_uint64),
+ ('lock_owner', ctypes.c_uint64)]
+else:
+ 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)]
+
+if _system == "OpenBSD":
+ class fuse_context(ctypes.Structure):
+ _fields_ = [
+ ('fuse', ctypes.c_voidp),
+ ('uid', c_uid_t),
+ ('gid', c_gid_t),
+ ('pid', c_pid_t),
+ ('private_data', ctypes.c_voidp),
+ ('umask', c_mode_t),
+ ]
+else:
+ class fuse_context(ctypes.Structure):
+ _fields_ = [
+ ('fuse', ctypes.c_voidp),
+ ('uid', c_uid_t),
+ ('gid', c_gid_t),
+ ('pid', c_pid_t),
+ ('private_data', ctypes.c_voidp)]
+
+if _system == "OpenBSD":
+ bmap_ret_t = ctypes.c_uint64
+ extra_fields = []
+else:
+ bmap_ret_t = ctypes.c_ulonglong
+ extra_fields = [
+ ('flag_nullpath_ok', ctypes.c_uint, 1),
+ ('flag_nopath', ctypes.c_uint, 1),
+ ('flag_utime_omit_ok', ctypes.c_uint, 1),
+ ('flag_reserved', ctypes.c_uint, 29),
+ ('ioctl', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_void_p,
+ ctypes.POINTER(fuse_file_info), ctypes.c_uint, ctypes.c_void_p)),
+ ]
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# DEBUG
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+if __name__ == '__main__':
+ dump_globals(globals())
diff --git a/refuse/_inventory.py b/refuse/_inventory.py
new file mode 100644
index 0000000..f66258f
--- /dev/null
+++ b/refuse/_inventory.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+
+"""
+
+REFUSE
+Simple cross-plattform ctypes bindings for libfuse / FUSE for macOS / WinFsp
+https://github.com/pleiszenburg/refuse
+
+ src/refuse/_inventory.py: Temporary helper for code refactoring
+
+ THIS FILE IS TEMPORARY AND WILL BE REMOVED!
+
+ Copyright (C) 2008-2020 refuse contributors
+
+
+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.
+
+
+"""
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# IMPORT
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+import _ctypes
+from pprint import pprint as pp
+import platform # machine, system
+import subprocess
+import sys
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# LIBRARY HEADER "INVENTORY"
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+def get_arch():
+ """Get system name and generic, OS-independent machine name
+ """
+
+ system = platform.system()
+ machine = platform.machine()
+
+ if machine in ('i386', 'i486', 'i586', 'i686'):
+ machine = 'x86'
+
+ # On Windows, machine equals hardware - not os. We want os.
+ if system == 'Windows':
+ machine = 'x86_64' if sys.platform == 'win64' else 'x86'
+
+ return system, machine
+
+
+def get_archs():
+
+ system_dict = {
+ 'Windows': ['x86', 'x86_64'],
+ 'CYGWIN': ['x86', 'x86_64'],
+ 'OpenBSD': ['x86', 'x86_64', 'mips', 'ppc', 'ppc64', 'ppc64le', 'aarch64'],
+ 'FreeBSD': ['x86', 'x86_64'],
+ 'Darwin': ['x86', 'x86_64', 'ppc', 'ppc64'],
+ 'Darwin-MacFuse': ['x86', 'x86_64', 'ppc', 'ppc64'],
+ 'Linux': ['x86', 'x86_64', 'mips', 'ppc', 'ppc64', 'ppc64le', 'aarch64'],
+ }
+
+ arch_list = []
+ for system, machines in system_dict.items():
+ for machine in machines:
+ arch_list.append((system, machine))
+
+ for system, machine in arch_list:
+ yield system, machine
+
+
+def _analyze_item_(item):
+
+ item_type = type(item)
+ item_type_name = getattr(item_type, '__name__')
+
+ if item_type in (int, float, str, bytes, bool, list, dict, set, tuple): # basic python type
+ return item_type.__name__, item
+
+ if hasattr(item, '_fields_'): # ctypes struct
+ fields = [[field[0], _analyze_item_(field[1])] for field in item._fields_]
+ return ['ctypes struct', fields]
+
+ if item_type_name == 'PyCSimpleType':
+ return getattr(item, '__name__')
+
+ if item_type_name == 'PyCFuncPtrType':
+ try:
+ return [
+ 'ctypes func',
+ [_analyze_item_(field) for field in getattr(item, 'argtypes', [])],
+ _analyze_item_(item.restype)
+ ]
+ except:
+ return [
+ 'ctypes func', '?'
+ ]
+
+ return getattr(item, '__name__', '?'), type(item)
+
+
+def dump_globals(globals_dict):
+
+ header_dict = {
+ name: _analyze_item_(globals_dict[name])
+ for name in globals_dict.keys()
+ if not name.startswith('_') and name not in (
+ 'sys', # import
+ 'ctypes', # import
+ 'get_arch', # infrastructure
+ 'dump_globals', # debug
+ 'extra_fields' # TMP from OpenBSD port
+ )
+ }
+ pp(header_dict)
+
+
+def dump_header():
+
+ for system, machine in get_archs():
+
+ sys.stdout.write('+++ %s / %s +++\n' % (system, machine))
+ sys.stdout.flush()
+ proc = subprocess.Popen(
+ ['python3', '-m', 'refuse._high_header', system, machine],
+ stdout = subprocess.PIPE, stderr = subprocess.PIPE
+ )
+ data = proc.communicate()
+ sys.stderr.write(data[1].decode('utf-8'))
+ sys.stderr.flush()
+ sys.stdout.write(data[0].decode('utf-8'))
+ sys.stdout.flush()
+
+
+if __name__ == '__main__':
+ dump_header()
diff --git a/refuse/_refactor.py b/refuse/_refactor.py
new file mode 100644
index 0000000..2a5c0e4
--- /dev/null
+++ b/refuse/_refactor.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+
+"""
+
+REFUSE
+Simple cross-plattform ctypes bindings for libfuse / FUSE for macOS / WinFsp
+https://github.com/pleiszenburg/refuse
+
+ src/refuse/_refactor.py: Temporary helper for code refactoring
+
+ THIS FILE IS TEMPORARY AND WILL BE REMOVED!
+
+ Copyright (C) 2008-2020 refuse contributors
+
+
+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.
+
+
+"""
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# IMPORT
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+import os
+import ctypes
+from ctypes.util import find_library
+import sys
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# FIND LIBRARY
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+def get_libfuse(_system):
+
+ _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'))
+ elif _system == 'Windows':
+ try:
+ import _winreg as reg
+ except ImportError:
+ import winreg as reg
+ def Reg32GetValue(rootkey, keyname, valname):
+ key, val = None, None
+ try:
+ key = reg.OpenKey(rootkey, keyname, 0, reg.KEY_READ | reg.KEY_WOW64_32KEY)
+ val = str(reg.QueryValueEx(key, valname)[0])
+ except WindowsError:
+ pass
+ finally:
+ if key is not None:
+ reg.CloseKey(key)
+ return val
+ _libfuse_path = Reg32GetValue(reg.HKEY_LOCAL_MACHINE, r"SOFTWARE\WinFsp", r"InstallDir")
+ if _libfuse_path:
+ _libfuse_path += r"bin\winfsp-%s.dll" % ("x64" if sys.maxsize > 0xffffffff else "x86")
+ else:
+ _libfuse_path = find_library('fuse')
+
+ if not _libfuse_path:
+ raise EnvironmentError('Unable to find libfuse')
+ else:
+ _libfuse = ctypes.CDLL(_libfuse_path)
+
+ return _libfuse
diff --git a/refuse/high.py b/refuse/high.py
new file mode 100644
index 0000000..f089e80
--- /dev/null
+++ b/refuse/high.py
@@ -0,0 +1,1407 @@
+# -*- coding: utf-8 -*-
+
+"""
+
+REFUSE
+Simple cross-plattform ctypes bindings for libfuse / FUSE for macOS / WinFsp
+https://github.com/pleiszenburg/refuse
+
+ src/refuse/high.py: "High level" FUSE API
+
+ Copyright (C) 2008-2020 refuse contributors
+
+
+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.
+
+
+"""
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# IMPORT
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+import ctypes
+import errno
+import logging
+import os
+import warnings
+
+from platform import machine, system
+from signal import signal, SIGINT, SIG_DFL, SIGTERM
+from stat import S_IFDIR
+from traceback import print_exc
+
+from time import time
+try:
+ from time import time_ns
+except ImportError:
+ time_ns = lambda: int(time() * 1e9)
+
+from functools import partial
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# HEADER: "INIT"
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+log = logging.getLogger("fuse")
+
+# switch through operating systems and architectures
+_system = system()
+_machine = machine()
+
+# Simplify CYGWIN check
+if _system.startswith('CYGWIN'):
+ _system = 'CYGWIN'
+
+# HACK get reference on library
+from ._refactor import get_libfuse
+_libfuse = get_libfuse(_system)
+
+# Get MacFUSE status by looking at library
+if _system == 'Darwin' and hasattr(_libfuse, 'macfuse_version'):
+ _system = 'Darwin-MacFuse'
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# HEADER: TYPES
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+if _system == 'Windows':
+ # NOTE:
+ #
+ # sizeof(long)==4 on Windows 32-bit and 64-bit
+ # sizeof(long)==4 on Cygwin 32-bit and ==8 on Cygwin 64-bit
+ #
+ # We have to fix up c_long and c_ulong so that it matches the
+ # Cygwin (and UNIX) sizes when run on Windows.
+ import sys
+ if sys.maxsize > 0xffffffff:
+ c_win_long = ctypes.c_int64
+ c_win_ulong = ctypes.c_uint64
+ else:
+ c_win_long = ctypes.c_int32
+ c_win_ulong = ctypes.c_uint32
+
+if _system == 'Windows' or _system == 'CYGWIN':
+ class c_timespec(ctypes.Structure):
+ _fields_ = [('tv_sec', c_win_long), ('tv_nsec', c_win_long)]
+elif _system == 'OpenBSD':
+ c_time_t = ctypes.c_int64
+ class c_timespec(ctypes.Structure):
+ _fields_ = [('tv_sec', c_time_t), ('tv_nsec', ctypes.c_long)]
+else:
+ class c_timespec(ctypes.Structure):
+ _fields_ = [('tv_sec', ctypes.c_long), ('tv_nsec', ctypes.c_long)]
+
+class c_utimbuf(ctypes.Structure):
+ _fields_ = [('actime', c_timespec), ('modtime', c_timespec)]
+
+class c_stat(ctypes.Structure):
+ pass # Platform dependent
+
+if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'):
+ 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
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int,
+ ctypes.c_uint32)
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t, ctypes.c_uint32)
+ if _system == 'Darwin':
+ c_stat._fields_ = [
+ ('st_dev', c_dev_t),
+ ('st_mode', c_mode_t),
+ ('st_nlink', ctypes.c_uint16),
+ ('st_ino', ctypes.c_uint64),
+ ('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_birthtimespec', c_timespec),
+ ('st_size', c_off_t),
+ ('st_blocks', ctypes.c_int64),
+ ('st_blksize', ctypes.c_int32),
+ ('st_flags', ctypes.c_int32),
+ ('st_gen', ctypes.c_int32),
+ ('st_lspare', ctypes.c_int32),
+ ('st_qspare', ctypes.c_int64)]
+ else: # MacFuse, FreeBSD
+ 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
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t)
+
+ 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)]
+ elif _machine == 'ppc64' or _machine == 'ppc64le':
+ 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),
+ ('__pad', ctypes.c_uint),
+ ('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 == 'aarch64':
+ c_stat._fields_ = [
+ ('st_dev', c_dev_t),
+ ('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),
+ ('__pad1', ctypes.c_ulong),
+ ('st_size', c_off_t),
+ ('st_blksize', ctypes.c_int),
+ ('__pad2', ctypes.c_int),
+ ('st_blocks', ctypes.c_long),
+ ('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)]
+elif _system == 'Windows' or _system == 'CYGWIN':
+ ENOTSUP = 129 if _system == 'Windows' else 134
+ c_dev_t = ctypes.c_uint
+ c_fsblkcnt_t = c_win_ulong
+ c_fsfilcnt_t = c_win_ulong
+ 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
+ setxattr_t = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+ getxattr_t = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t)
+ c_stat._fields_ = [
+ ('st_dev', c_dev_t),
+ ('st_ino', ctypes.c_ulonglong),
+ ('st_mode', c_mode_t),
+ ('st_nlink', ctypes.c_ushort),
+ ('st_uid', c_uid_t),
+ ('st_gid', c_gid_t),
+ ('st_rdev', c_dev_t),
+ ('st_size', c_off_t),
+ ('st_atimespec', c_timespec),
+ ('st_mtimespec', c_timespec),
+ ('st_ctimespec', c_timespec),
+ ('st_blksize', ctypes.c_int),
+ ('st_blocks', ctypes.c_longlong),
+ ('st_birthtimespec', c_timespec)]
+elif _system == 'OpenBSD':
+ ENOTSUP = 91
+ c_dev_t = ctypes.c_int32
+ c_uid_t = ctypes.c_uint32
+ c_gid_t = ctypes.c_uint32
+ c_mode_t = ctypes.c_uint32
+ c_off_t = ctypes.c_int64
+ c_pid_t = ctypes.c_int32
+ c_ino_t = ctypes.c_uint64
+ c_nlink_t = ctypes.c_uint32
+ c_blkcnt_t = ctypes.c_int64
+ c_blksize_t = ctypes.c_int32
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t)
+ c_fsblkcnt_t = ctypes.c_uint64
+ c_fsfilcnt_t = ctypes.c_uint64
+ c_stat._fields_ = [
+ ('st_mode', c_mode_t),
+ ('st_dev', c_dev_t),
+ ('st_ino', c_ino_t),
+ ('st_nlink', c_nlink_t),
+ ('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', c_blkcnt_t),
+ ('st_blksize', c_blksize_t),
+ ('st_flags', ctypes.c_uint32),
+ ('st_gen', ctypes.c_uint32),
+ ('st_birthtimespec', c_timespec),
+ ]
+else:
+ raise NotImplementedError('%s is not supported.' % _system)
+
+
+if _system == 'FreeBSD':
+ c_fsblkcnt_t = ctypes.c_uint64
+ c_fsfilcnt_t = ctypes.c_uint64
+ setxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t, ctypes.c_int)
+
+ getxattr_t = ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p,
+ ctypes.POINTER(ctypes.c_byte), ctypes.c_size_t)
+
+ class c_statvfs(ctypes.Structure):
+ _fields_ = [
+ ('f_bavail', c_fsblkcnt_t),
+ ('f_bfree', c_fsblkcnt_t),
+ ('f_blocks', c_fsblkcnt_t),
+ ('f_favail', c_fsfilcnt_t),
+ ('f_ffree', c_fsfilcnt_t),
+ ('f_files', c_fsfilcnt_t),
+ ('f_bsize', ctypes.c_ulong),
+ ('f_flag', ctypes.c_ulong),
+ ('f_frsize', ctypes.c_ulong)]
+elif _system == 'Windows' or _system == 'CYGWIN':
+ class c_statvfs(ctypes.Structure):
+ _fields_ = [
+ ('f_bsize', c_win_ulong),
+ ('f_frsize', c_win_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),
+ ('f_fsid', c_win_ulong),
+ ('f_flag', c_win_ulong),
+ ('f_namemax', c_win_ulong)]
+else:
+ 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),
+ ('f_fsid', ctypes.c_ulong),
+ # ('unused', ctypes.c_int),
+ ('f_flag', ctypes.c_ulong),
+ ('f_namemax', ctypes.c_ulong)]
+
+if _system == 'Windows' or _system == 'CYGWIN':
+ class fuse_file_info(ctypes.Structure):
+ _fields_ = [
+ ('flags', ctypes.c_int),
+ ('fh_old', ctypes.c_int),
+ ('writepage', ctypes.c_int),
+ ('direct_io', ctypes.c_uint, 1),
+ ('keep_cache', ctypes.c_uint, 1),
+ ('flush', ctypes.c_uint, 1),
+ ('padding', ctypes.c_uint, 29),
+ ('fh', ctypes.c_uint64),
+ ('lock_owner', ctypes.c_uint64)]
+elif _system == "OpenBSD":
+ class fuse_file_info(ctypes.Structure):
+ _fields_ = [
+ ('flags', ctypes.c_int32),
+ ('fh_old', ctypes.c_uint32),
+ ('writepage', ctypes.c_int32),
+ ('direct_io', ctypes.c_uint32, 1),
+ ('keep_cache', ctypes.c_uint32, 1),
+ ('flush', ctypes.c_uint32, 1),
+ ('nonseekable', ctypes.c_uint32, 1),
+ ('padding', ctypes.c_uint32, 27),
+ ('flock_release', ctypes.c_uint32, 1),
+ ('fh', ctypes.c_uint64),
+ ('lock_owner', ctypes.c_uint64)]
+else:
+ 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)]
+
+if _system == "OpenBSD":
+ class fuse_context(ctypes.Structure):
+ _fields_ = [
+ ('fuse', ctypes.c_voidp),
+ ('uid', c_uid_t),
+ ('gid', c_gid_t),
+ ('pid', c_pid_t),
+ ('private_data', ctypes.c_voidp),
+ ('umask', c_mode_t),
+ ]
+else:
+ class fuse_context(ctypes.Structure):
+ _fields_ = [
+ ('fuse', ctypes.c_voidp),
+ ('uid', c_uid_t),
+ ('gid', c_gid_t),
+ ('pid', c_pid_t),
+ ('private_data', ctypes.c_voidp)]
+
+if _system == "OpenBSD":
+ bmap_ret_t = ctypes.c_uint64
+ extra_fields = []
+else:
+ bmap_ret_t = ctypes.c_ulonglong
+ extra_fields = [
+ ('flag_nullpath_ok', ctypes.c_uint, 1),
+ ('flag_nopath', ctypes.c_uint, 1),
+ ('flag_utime_omit_ok', ctypes.c_uint, 1),
+ ('flag_reserved', ctypes.c_uint, 29),
+ ('ioctl', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_void_p,
+ ctypes.POINTER(fuse_file_info), ctypes.c_uint, ctypes.c_void_p)),
+ ]
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# CLASS: FUSE operations
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+class fuse_operations(ctypes.Structure):
+ _fields_ = [
+ ('getattr', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_stat))),
+
+ ('readlink', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t)),
+
+ ('getdir', ctypes.c_voidp), # Deprecated, use readdir
+
+ ('mknod', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, c_mode_t, c_dev_t)),
+
+ ('mkdir', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, c_mode_t)),
+ ('unlink', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p)),
+ ('rmdir', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p)),
+
+ ('symlink', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)),
+
+ ('rename', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)),
+
+ ('link', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)),
+
+ ('chmod', ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_char_p, c_mode_t)),
+
+ ('chown', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, c_uid_t, c_gid_t)),
+
+ ('truncate', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, c_off_t)),
+
+ ('utime', ctypes.c_voidp), # Deprecated, use utimens
+ ('open', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))),
+
+ ('read', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t, c_off_t, ctypes.POINTER(fuse_file_info))),
+
+ ('write', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t, c_off_t, ctypes.POINTER(fuse_file_info))),
+
+ ('statfs', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_statvfs))),
+
+ ('flush', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))),
+
+ ('release', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))),
+
+ ('fsync', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_int,
+ ctypes.POINTER(fuse_file_info))),
+
+ ('setxattr', setxattr_t),
+ ('getxattr', getxattr_t),
+
+ ('listxattr', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(ctypes.c_byte),
+ ctypes.c_size_t)),
+
+ ('removexattr', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p)),
+
+ ('opendir', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))),
+
+ ('readdir', ctypes.CFUNCTYPE(
+ ctypes.c_int,
+ ctypes.c_char_p,
+ ctypes.c_voidp,
+ ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_voidp, ctypes.c_char_p,
+ ctypes.POINTER(c_stat), c_off_t),
+ c_off_t,
+ ctypes.POINTER(fuse_file_info))),
+
+ ('releasedir', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info))),
+
+ ('fsyncdir', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_int,
+ ctypes.POINTER(fuse_file_info))),
+
+ ('init', ctypes.CFUNCTYPE(ctypes.c_voidp, ctypes.c_voidp)),
+ ('destroy', ctypes.CFUNCTYPE(ctypes.c_voidp, ctypes.c_voidp)),
+
+ ('access', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_int)),
+
+ ('create', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, c_mode_t,
+ ctypes.POINTER(fuse_file_info))),
+
+ ('ftruncate', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, c_off_t,
+ ctypes.POINTER(fuse_file_info))),
+
+ ('fgetattr', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_stat),
+ ctypes.POINTER(fuse_file_info))),
+
+ ('lock', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(fuse_file_info),
+ ctypes.c_int, ctypes.c_voidp)),
+
+ ('utimens', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.POINTER(c_utimbuf))),
+
+ ('bmap', ctypes.CFUNCTYPE(
+ ctypes.c_int, ctypes.c_char_p, ctypes.c_size_t,
+ ctypes.POINTER(bmap_ret_t))),
+ ] + extra_fields
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# ROUTINES: bindings
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+_libfuse.fuse_get_context.restype = ctypes.POINTER(fuse_context)
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# ROUTINES: refuse
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+if _system == "OpenBSD":
+ def fuse_main_real(argc, argv, fuse_ops_v, sizeof_fuse_ops, ctx_p):
+ return _libfuse.fuse_main(argc, argv, fuse_ops_v, ctx_p)
+else:
+ fuse_main_real =_libfuse.fuse_main_real
+
+UTIME_OMIT = (1 << 30) - 2
+UTIME_NOW = (1 << 30) - 1
+
+def is_utime_now(ts):
+ return ts.tv_sec == 0 and ts.tv_nsec == UTIME_NOW
+
+def is_utime_omit(ts):
+ return ts.tv_sec == 0 and ts.tv_nsec == UTIME_OMIT
+
+def get_now(use_ns=False):
+ if use_ns:
+ return time_ns()
+ else:
+ return time()
+
+def time_of_timespec(ts, use_ns=False):
+ if use_ns:
+ return ts.tv_sec * 10 ** 9 + ts.tv_nsec
+ else:
+ return ts.tv_sec + ts.tv_nsec * 1E-9
+
+def set_st_attrs(st, attrs, use_ns=False):
+ for key, val in attrs.items():
+ if key in ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime'):
+ timespec = getattr(st, key + 'spec', None)
+ if timespec is None:
+ continue
+
+ if use_ns:
+ timespec.tv_sec, timespec.tv_nsec = divmod(int(val), 10 ** 9)
+ else:
+ timespec.tv_sec = int(val)
+ timespec.tv_nsec = int((val - timespec.tv_sec) * 1E9)
+ elif hasattr(st, key):
+ setattr(st, key, val)
+
+
+def fuse_get_context():
+ 'Returns a (uid, gid, pid) tuple'
+
+ ctxp = _libfuse.fuse_get_context()
+ ctx = ctxp.contents
+ return ctx.uid, ctx.gid, ctx.pid
+
+
+def fuse_exit():
+ '''
+ This will shutdown the FUSE mount and cause the call to FUSE(...) to
+ return, similar to sending SIGINT to the process.
+
+ Flags the native FUSE session as terminated and will cause any running FUSE
+ event loops to exit on the next opportunity. (see fuse.c::fuse_exit)
+ '''
+ # OpenBSD doesn't have fuse_exit
+ # instead fuse_loop() gracefully catches SIGTERM
+ if _system == "OpenBSD":
+ os.kill(os.getpid(), SIGTERM)
+ return
+
+ fuse_ptr = ctypes.c_void_p(_libfuse.fuse_get_context().contents.fuse)
+ _libfuse.fuse_exit(fuse_ptr)
+
+
+def get_fuse_version():
+ for method in [
+ lambda: _libfuse.fuse_pkgversion(),
+ lambda: _libfuse.fuse3_pkgversion(),
+ lambda: _libfuse.fuse_version(),
+ lambda: _libfuse.fuse3_version(),
+ lambda: _libfuse.macfuse_version(),
+ lambda: _libfuse.osxfuse_version(),
+ ]:
+ try:
+ val = method()
+ if isinstance(val, int) and val > 10:
+ return str(val / 10)
+ else:
+ return val
+ except AttributeError:
+ pass
+
+
+def get_fuse_libfile():
+ return _libfuse._name
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# CLASS: FuseOSError
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+class FuseOSError(OSError):
+ def __init__(self, errno):
+ super(FuseOSError, self).__init__(errno, os.strerror(errno))
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# CLASS: FUSE
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+class FUSE:
+ '''
+ This class is the lower level interface and should not be subclassed under
+ normal use. Its methods are called by fuse.
+
+ Assumes API version 2.6 or later.
+ '''
+
+ OPTIONS = (
+ ('foreground', '-f'),
+ ('debug', '-d'),
+ ('nothreads', '-s'),
+ )
+
+ def __init__(self, operations, mountpoint, raw_fi=False, encoding='utf-8',
+ **kwargs):
+
+ '''
+ Setting raw_fi to True will cause FUSE to pass the fuse_file_info
+ class as is to Operations, instead of just the fh field.
+
+ This gives you access to direct_io, keep_cache, etc.
+ '''
+
+ self.operations = operations
+ self.raw_fi = raw_fi
+ self.encoding = encoding
+ self.__critical_exception = None
+
+ self.use_ns = getattr(operations, 'use_ns', False)
+ 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 operations class or set your fusepy '
+ 'requirements to <4.',
+ DeprecationWarning)
+
+ args = ['fuse']
+
+ args.extend(flag for arg, flag in self.OPTIONS
+ if kwargs.pop(arg, False))
+
+ kwargs.setdefault('fsname', operations.__class__.__name__)
+ args.append('-o')
+ args.append(','.join(self._normalize_fuse_options(**kwargs)))
+ args.append(mountpoint)
+
+ args = [arg.encode(encoding) for arg in args]
+ argv = (ctypes.c_char_p * len(args))(*args)
+
+ fuse_ops = fuse_operations()
+ for ent in fuse_operations._fields_:
+ name, prototype = ent[:2]
+
+ check_name = name
+
+ # ftruncate()/fgetattr() are implemented in terms of their
+ # non-f-prefixed versions in the operations object
+ if check_name in ["ftruncate", "fgetattr"]:
+ check_name = check_name[1:]
+
+ val = getattr(operations, check_name, None)
+ if val is None:
+ continue
+
+ # Function pointer members are tested for using the
+ # getattr(operations, name) above but are dynamically
+ # invoked using self.operations(name)
+ if hasattr(prototype, 'argtypes'):
+ val = prototype(partial(self._wrapper, getattr(self, name)))
+
+ setattr(fuse_ops, name, val)
+
+ try:
+ old_handler = signal(SIGINT, SIG_DFL)
+ except ValueError:
+ old_handler = SIG_DFL
+
+ err = _libfuse.fuse_main_real(
+ len(args), argv, ctypes.pointer(fuse_ops),
+ ctypes.sizeof(fuse_ops),
+ None)
+
+ try:
+ signal(SIGINT, old_handler)
+ except ValueError:
+ pass
+
+ del self.operations # Invoke the destructor
+ if self.__critical_exception:
+ raise self.__critical_exception
+ if err:
+ raise RuntimeError(err)
+
+ @staticmethod
+ def _normalize_fuse_options(**kargs):
+ for key, value in kargs.items():
+ if isinstance(value, bool):
+ if value is True:
+ yield key
+ else:
+ yield '%s=%s' % (key, value)
+
+ def _wrapper(self, func, *args, **kwargs):
+ 'Decorator for the methods that follow'
+
+ try:
+ if func.__name__ == "init":
+ # init may not fail, as its return code is just stored as
+ # private_data field of struct fuse_context
+ return func(*args, **kwargs) or 0
+
+ else:
+ try:
+ return func(*args, **kwargs) or 0
+
+ except OSError as e:
+ if e.errno is not None and e.errno > 0:
+ log.debug(
+ "FUSE operation %s raised a %s, returning errno %s.",
+ func.__name__, type(e), e.errno, exc_info=True)
+ return -e.errno
+ else:
+ log.error(
+ "FUSE operation %s raised an OSError with an invalid "
+ "errno %s, returning errno.EINVAL.",
+ func.__name__, e.errno, exc_info=True)
+ return -errno.EINVAL
+
+ except Exception:
+ log.error("Uncaught exception from FUSE operation %s, "
+ "returning errno.EINVAL.",
+ func.__name__, exc_info=True)
+ return -errno.EINVAL
+
+ except BaseException as e:
+ self.__critical_exception = e
+ log.critical(
+ "Uncaught critical exception from FUSE operation %s, aborting.",
+ func.__name__, exc_info=True)
+ # the raised exception (even SystemExit) will be caught by FUSE
+ # potentially causing SIGSEGV, so tell system to stop/interrupt FUSE
+ fuse_exit()
+ return -errno.EFAULT
+
+ def _decode_optional_path(self, path):
+ # NB: this method is intended for fuse operations that
+ # allow the path argument to be NULL,
+ # *not* as a generic path decoding method
+ if path is None:
+ return None
+ return path.decode(self.encoding)
+
+ def getattr(self, path, buf):
+ return self.fgetattr(path, buf, None)
+
+ def readlink(self, path, buf, bufsize):
+ ret = self.operations('readlink', path.decode(self.encoding)) \
+ .encode(self.encoding)
+
+ # copies a string into the given buffer
+ # (null terminated and truncated if necessary)
+ data = ctypes.create_string_buffer(ret[:bufsize - 1])
+ ctypes.memmove(buf, data, len(data))
+ return 0
+
+ def mknod(self, path, mode, dev):
+ return self.operations('mknod', path.decode(self.encoding), mode, dev)
+
+ def mkdir(self, path, mode):
+ return self.operations('mkdir', path.decode(self.encoding), mode)
+
+ def unlink(self, path):
+ return self.operations('unlink', path.decode(self.encoding))
+
+ def rmdir(self, path):
+ return self.operations('rmdir', path.decode(self.encoding))
+
+ def symlink(self, source, target):
+ 'creates a symlink `target -> source` (e.g. ln -s source target)'
+
+ return self.operations('symlink', target.decode(self.encoding),
+ source.decode(self.encoding))
+
+ def rename(self, old, new):
+ return self.operations('rename', old.decode(self.encoding),
+ new.decode(self.encoding))
+
+ def link(self, source, target):
+ 'creates a hard link `target -> source` (e.g. ln source target)'
+
+ return self.operations('link', target.decode(self.encoding),
+ source.decode(self.encoding))
+
+ def chmod(self, path, mode):
+ return self.operations('chmod', path.decode(self.encoding), mode)
+
+ def chown(self, path, uid, gid):
+ # Check if any of the arguments is a -1 that has overflowed
+ if c_uid_t(uid + 1).value == 0:
+ uid = -1
+ if c_gid_t(gid + 1).value == 0:
+ gid = -1
+
+ return self.operations('chown', path.decode(self.encoding), uid, gid)
+
+ def truncate(self, path, length):
+ return self.operations('truncate', path.decode(self.encoding), length)
+
+ def open(self, path, fip):
+ fi = fip.contents
+ if self.raw_fi:
+ return self.operations('open', path.decode(self.encoding), fi)
+ else:
+ fi.fh = self.operations('open', path.decode(self.encoding),
+ fi.flags)
+
+ return 0
+
+ def read(self, path, buf, size, offset, fip):
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ ret = self.operations('read', self._decode_optional_path(path), size,
+ offset, fh)
+
+ if not ret:
+ return 0
+
+ retsize = len(ret)
+ assert retsize <= size, \
+ 'actual amount read %d greater than expected %d' % (retsize, size)
+
+ ctypes.memmove(buf, ret, retsize)
+ return retsize
+
+ def write(self, path, buf, size, offset, fip):
+ data = ctypes.string_at(buf, size)
+
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ return self.operations('write', self._decode_optional_path(path), data,
+ offset, fh)
+
+ def statfs(self, path, buf):
+ stv = buf.contents
+ attrs = self.operations('statfs', path.decode(self.encoding))
+ for key, val in attrs.items():
+ if hasattr(stv, key):
+ setattr(stv, key, val)
+
+ return 0
+
+ def flush(self, path, fip):
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ return self.operations('flush', self._decode_optional_path(path), fh)
+
+ def release(self, path, fip):
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ return self.operations('release', self._decode_optional_path(path), fh)
+
+ def fsync(self, path, datasync, fip):
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ return self.operations('fsync', self._decode_optional_path(path), datasync,
+ fh)
+
+ def setxattr(self, path, name, value, size, options, *args):
+ return self.operations('setxattr', path.decode(self.encoding),
+ name.decode(self.encoding),
+ ctypes.string_at(value, size), options, *args)
+
+ def getxattr(self, path, name, value, size, *args):
+ ret = self.operations('getxattr', path.decode(self.encoding),
+ name.decode(self.encoding), *args)
+
+ retsize = len(ret)
+ # allow size queries
+ if not value:
+ return retsize
+
+ # do not truncate
+ if retsize > size:
+ return -errno.ERANGE
+
+ # Does not add trailing 0
+ buf = ctypes.create_string_buffer(ret, retsize)
+ ctypes.memmove(value, buf, retsize)
+
+ return retsize
+
+ def listxattr(self, path, namebuf, size):
+ attrs = self.operations('listxattr', path.decode(self.encoding)) or ''
+ ret = '\x00'.join(attrs).encode(self.encoding)
+ if len(ret) > 0:
+ ret += '\x00'.encode(self.encoding)
+
+ retsize = len(ret)
+ # allow size queries
+ if not namebuf:
+ return retsize
+
+ # do not truncate
+ if retsize > size:
+ return -errno.ERANGE
+
+ buf = ctypes.create_string_buffer(ret, retsize)
+ ctypes.memmove(namebuf, buf, retsize)
+
+ return retsize
+
+ def removexattr(self, path, name):
+ return self.operations('removexattr', path.decode(self.encoding),
+ name.decode(self.encoding))
+
+ def opendir(self, path, fip):
+ # Ignore raw_fi
+ fip.contents.fh = self.operations('opendir',
+ path.decode(self.encoding))
+
+ return 0
+
+ def readdir(self, path, buf, filler, offset, fip):
+ # Ignore raw_fi
+ for item in self.operations('readdir', self._decode_optional_path(path),
+ fip.contents.fh):
+
+ if isinstance(item, str):
+ name, st, offset = item, None, 0
+ else:
+ name, attrs, offset = item
+ if attrs:
+ st = c_stat()
+ set_st_attrs(st, attrs, use_ns=self.use_ns)
+ else:
+ st = None
+
+ if filler(buf, name.encode(self.encoding), st, offset) != 0:
+ break
+
+ return 0
+
+ def releasedir(self, path, fip):
+ # Ignore raw_fi
+ return self.operations('releasedir', self._decode_optional_path(path),
+ fip.contents.fh)
+
+ def fsyncdir(self, path, datasync, fip):
+ # Ignore raw_fi
+ return self.operations('fsyncdir', self._decode_optional_path(path),
+ datasync, fip.contents.fh)
+
+ def init(self, conn):
+ return self.operations('init', '/')
+
+ def destroy(self, private_data):
+ return self.operations('destroy', '/')
+
+ def access(self, path, amode):
+ return self.operations('access', path.decode(self.encoding), amode)
+
+ def create(self, path, mode, fip):
+ fi = fip.contents
+ path = path.decode(self.encoding)
+
+ if self.raw_fi:
+ return self.operations('create', path, mode, fi)
+ else:
+ fi.fh = self.operations('create', path, mode)
+ return 0
+
+ def ftruncate(self, path, length, fip):
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ return self.operations('truncate', self._decode_optional_path(path),
+ length, fh)
+
+ def fgetattr(self, path, buf, fip):
+ ctypes.memset(buf, 0, ctypes.sizeof(c_stat))
+
+ st = buf.contents
+ if not fip:
+ fh = fip
+ elif self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ attrs = self.operations('getattr', self._decode_optional_path(path), fh)
+ set_st_attrs(st, attrs, use_ns=self.use_ns)
+ return 0
+
+ def lock(self, path, fip, cmd, lock):
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ return self.operations('lock', self._decode_optional_path(path), fh, cmd,
+ lock)
+
+ def utimens(self, path, buf):
+ if buf:
+ atime = time_of_timespec(buf.contents.actime, use_ns=self.use_ns)
+ mtime = time_of_timespec(buf.contents.modtime, use_ns=self.use_ns)
+ if self.use_ns:
+ now = get_now(self.use_ns)
+ if is_utime_now(buf.contents.actime):
+ atime = now
+ elif is_utime_omit(buf.contents.actime):
+ atime = None
+ if is_utime_now(buf.contents.modtime):
+ mtime = now
+ elif is_utime_omit(buf.contents.modtime):
+ mtime = None
+ else:
+ pass # TODO ... this is the old, bad behavior
+ times = (atime, mtime)
+ else:
+ times = None
+
+ return self.operations('utimens', path.decode(self.encoding), times)
+
+ def bmap(self, path, blocksize, idx):
+ return self.operations('bmap', path.decode(self.encoding), blocksize,
+ idx)
+
+ def ioctl(self, path, cmd, arg, fip, flags, data):
+ if self.raw_fi:
+ fh = fip.contents
+ else:
+ fh = fip.contents.fh
+
+ return self.operations('ioctl', path.decode(self.encoding),
+ cmd, arg, fh, flags, data)
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# CLASS: Operations
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+class Operations:
+ '''
+ This class should be subclassed and passed as an argument to FUSE on
+ initialization. All operations should raise a FuseOSError exception on
+ error.
+
+ When in doubt of what an operation should do, check the FUSE header file
+ or the corresponding system call man page.
+ '''
+
+ def __call__(self, op, *args):
+ if not hasattr(self, op):
+ raise FuseOSError(errno.EFAULT)
+ return getattr(self, op)(*args)
+
+ def access(self, path, amode):
+ return 0
+
+ bmap = None
+
+ def chmod(self, path, mode):
+ raise FuseOSError(errno.EROFS)
+
+ def chown(self, path, uid, gid):
+ raise FuseOSError(errno.EROFS)
+
+ def create(self, path, mode, fi=None):
+ '''
+ When raw_fi is False (default case), fi is None and create should
+ return a numerical file handle.
+
+ When raw_fi is True the file handle should be set directly by create
+ and return 0.
+ '''
+
+ raise FuseOSError(errno.EROFS)
+
+ def destroy(self, path):
+ 'Called on filesystem destruction. Path is always /'
+
+ pass
+
+ def flush(self, path, fh):
+ return 0
+
+ def fsync(self, path, datasync, fh):
+ return 0
+
+ def fsyncdir(self, path, datasync, fh):
+ return 0
+
+ def getattr(self, path, fh=None):
+ '''
+ Returns a dictionary with keys identical to the stat C structure of
+ stat(2).
+
+ st_atime, st_mtime and st_ctime should be floats.
+
+ NOTE: There is an incompatibility between Linux and Mac OS X
+ concerning st_nlink of directories. Mac OS X counts all files inside
+ the directory, while Linux counts only the subdirectories.
+ '''
+
+ if path != '/':
+ raise FuseOSError(errno.ENOENT)
+ return dict(st_mode=(S_IFDIR | 0o755), st_nlink=2)
+
+ def getxattr(self, path, name, position=0):
+ raise FuseOSError(ENOTSUP)
+
+ def init(self, path):
+ '''
+ Called on filesystem initialization. (Path is always /)
+
+ Use it instead of __init__ if you start threads on initialization.
+ '''
+
+ pass
+
+ def ioctl(self, path, cmd, arg, fip, flags, data):
+ raise FuseOSError(errno.ENOTTY)
+
+ def link(self, target, source):
+ 'creates a hard link `target -> source` (e.g. ln source target)'
+
+ raise FuseOSError(errno.EROFS)
+
+ def listxattr(self, path):
+ return []
+
+ lock = None
+
+ def mkdir(self, path, mode):
+ raise FuseOSError(errno.EROFS)
+
+ def mknod(self, path, mode, dev):
+ raise FuseOSError(errno.EROFS)
+
+ def open(self, path, flags):
+ '''
+ When raw_fi is False (default case), open should return a numerical
+ file handle.
+
+ When raw_fi is True the signature of open becomes:
+ open(self, path, fi)
+
+ and the file handle should be set directly.
+ '''
+
+ return 0
+
+ def opendir(self, path):
+ 'Returns a numerical file handle.'
+
+ return 0
+
+ def read(self, path, size, offset, fh):
+ 'Returns a string containing the data requested.'
+
+ raise FuseOSError(errno.EIO)
+
+ def readdir(self, path, fh):
+ '''
+ Can return either a list of names, or a list of (name, attrs, offset)
+ tuples. attrs is a dict as in getattr.
+ '''
+
+ return ['.', '..']
+
+ def readlink(self, path):
+ raise FuseOSError(errno.ENOENT)
+
+ def release(self, path, fh):
+ return 0
+
+ def releasedir(self, path, fh):
+ return 0
+
+ def removexattr(self, path, name):
+ raise FuseOSError(ENOTSUP)
+
+ def rename(self, old, new):
+ raise FuseOSError(errno.EROFS)
+
+ def rmdir(self, path):
+ raise FuseOSError(errno.EROFS)
+
+ def setxattr(self, path, name, value, options, position=0):
+ raise FuseOSError(ENOTSUP)
+
+ def statfs(self, path):
+ '''
+ Returns a dictionary with keys identical to the statvfs C structure of
+ statvfs(3).
+
+ On Mac OS X f_bsize and f_frsize must be a power of 2
+ (minimum 512).
+ '''
+
+ return {}
+
+ def symlink(self, target, source):
+ 'creates a symlink `target -> source` (e.g. ln -s source target)'
+
+ raise FuseOSError(errno.EROFS)
+
+ def truncate(self, path, length, fh=None):
+ raise FuseOSError(errno.EROFS)
+
+ def unlink(self, path):
+ raise FuseOSError(errno.EROFS)
+
+ def utimens(self, path, times=None):
+ 'Times is a (atime, mtime) tuple. If None use current time.'
+
+ return 0
+
+ def write(self, path, data, offset, fh):
+ raise FuseOSError(errno.EROFS)
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# CLASS: LoggingMixIn
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+class LoggingMixIn:
+ log = logging.getLogger('fuse.log-mixin')
+
+ def __call__(self, op, path, *args):
+ self.log.debug('-> %s %s %s', op, path, repr(args))
+ ret = '[Unhandled Exception]'
+ try:
+ ret = getattr(self, op)(path, *args)
+ return ret
+ except OSError as e:
+ ret = str(e)
+ raise
+ finally:
+ self.log.debug('<- %s %s', op, repr(ret))
diff --git a/refuse/low.py b/refuse/low.py
new file mode 100644
index 0000000..25509fb
--- /dev/null
+++ b/refuse/low.py
@@ -0,0 +1,976 @@
+# -*- 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
+
+
+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.
+
+
+"""
+
+
+# +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+# 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)