From ad2921311a044eaecba35c04792ad0248b785138 Mon Sep 17 00:00:00 2001 From: Mattia Mascarello Date: Tue, 9 May 2023 16:17:22 +0200 Subject: [PATCH] Windows support --- FSNode.py | 23 +- README.md | 9 +- main.py | 4 +- moodlefs.py | 10 +- refuse/__init__.py | 25 + refuse/_high_header.py | 482 ++++++++++++++ refuse/_inventory.py | 147 +++++ refuse/_refactor.py | 80 +++ refuse/high.py | 1407 ++++++++++++++++++++++++++++++++++++++++ refuse/low.py | 976 ++++++++++++++++++++++++++++ 10 files changed, 3144 insertions(+), 19 deletions(-) create mode 100644 refuse/__init__.py create mode 100644 refuse/_high_header.py create mode 100644 refuse/_inventory.py create mode 100644 refuse/_refactor.py create mode 100644 refuse/high.py create mode 100644 refuse/low.py 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 -![Demo](preview.gif) +![Demo](preview.gif) \ 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)