Skip to content

Commit 310cfcf

Browse files
committed
[feature] Support libfuse 3.17+
1 parent ac44621 commit 310cfcf

File tree

1 file changed

+122
-71
lines changed

1 file changed

+122
-71
lines changed

mfusepy.py

Lines changed: 122 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,6 @@ def get_fuse_version(libfuse):
186186
raise AttributeError(
187187
f"Found library {_libfuse_path} has wrong major version: {fuse_version_major}. " "Expected FUSE 2!"
188188
)
189-
if fuse_version_major == 3 and fuse_version_minor > 16:
190-
raise AttributeError(
191-
f"Found library {_libfuse_path} is too new ({fuse_version_major}.{fuse_version_minor}) "
192-
"and will not be used because FUSE 3 has no track record of ABI compatibility."
193-
)
194189

195190

196191
# Check FUSE major version changes by cloning https://github.com/libfuse/libfuse.git
@@ -655,6 +650,18 @@ class c_flock_t(ctypes.Structure):
655650
# so now there are 33 bits in total, which probably lead to some unwanted
656651
# padding in libfuse 3.0.2. This has been made explicit since libfuse 3.7.0:
657652
# https://github.com/libfuse/libfuse/commit/1d8e8ca94a3faa635afd1a3bd8d7d26472063a3f
653+
# - 3.0.0 -> 3.4.2: no change (flags and padding did add up to 33 bits from the beginning)
654+
# - 3.4.2 -> 3.5.0: cache_readdir added correctly
655+
# - 3.5.0 -> 3.6.2: no change
656+
# - 3.6.2 -> 3.7.0: padding 2 was explicitly added but did exist because of alignment
657+
# - 3.7.0 -> 3.10.5: no change
658+
# - 3.10.5 -> 3.11.0: noflush added correctly
659+
# - 3.11.0 -> 3.13.1: no change
660+
# - 3.13.1 -> 3.14.1: parallel_direct_writes was added in the middle.
661+
# Padding was correctly decreased by 1.
662+
# - 3.14.1 -> 3.16.2: no change
663+
_fuse_int32 = ctypes.c_int32 if (fuse_version_major, fuse_version_minor) >= (3, 17) else ctypes.c_int
664+
_fuse_uint32 = ctypes.c_uint32 if (fuse_version_major, fuse_version_minor) >= (3, 17) else ctypes.c_uint
658665
if fuse_version_major == 2:
659666
_fuse_file_info_fields_ = [
660667
('flags', ctypes.c_int),
@@ -670,39 +677,60 @@ class c_flock_t(ctypes.Structure):
670677
('lock_owner', ctypes.c_uint64),
671678
]
672679
elif fuse_version_major == 3:
673-
_fuse_file_info_fields_ = [('flags', ctypes.c_int)]
680+
_fuse_file_info_fields_ = [('flags', _fuse_int32)]
681+
682+
# Bit flag types were changed from unsigned int to uint32_t in libfuse 3.17,
683+
# but as far as I understand this change does not matter because it is only 1 bit.
674684

675685
_fuse_file_info_fields_bitfield = [
676-
('writepage', ctypes.c_uint, 1),
677-
('direct_io', ctypes.c_uint, 1),
678-
('keep_cache', ctypes.c_uint, 1),
686+
('writepage', _fuse_uint32, 1),
687+
('direct_io', _fuse_uint32, 1),
688+
('keep_cache', _fuse_uint32, 1),
679689
]
680690
# Introduced in 3.15.0 and its placement is an API-incompatible change / bug!
681691
# https://github.com/libfuse/libfuse/issues/1029
682-
if fuse_version_minor >= 15:
692+
if fuse_version_minor >= 15 and fuse_version_minor < 17:
683693
_fuse_file_info_fields_bitfield += [
684-
('parallel_direct_writes', ctypes.c_uint, 1),
694+
('parallel_direct_writes', _fuse_uint32, 1),
685695
]
686696
_fuse_file_info_fields_bitfield += [
687-
('flush', ctypes.c_uint, 1),
688-
('nonseekable', ctypes.c_uint, 1),
689-
('flock_release', ctypes.c_uint, 1),
697+
('flush', _fuse_uint32, 1),
698+
('nonseekable', _fuse_uint32, 1),
699+
('flock_release', _fuse_uint32, 1),
690700
]
691-
# Further ABI-breaks.
692701
if fuse_version_minor >= 5:
693702
_fuse_file_info_fields_bitfield += [('cache_readdir', ctypes.c_uint, 1)]
694703
if fuse_version_minor >= 11:
695704
_fuse_file_info_fields_bitfield += [('noflush', ctypes.c_uint, 1)]
705+
if fuse_version_minor >= 17:
706+
_fuse_file_info_fields_bitfield += [
707+
('parallel_direct_writes', _fuse_uint32, 1),
708+
]
709+
710+
_fuse_file_info_flag_count = sum(x[2] for x in _fuse_file_info_fields_bitfield)
711+
assert _fuse_file_info_flag_count < ctypes.sizeof(_fuse_uint32) * 8
696712

697713
_fuse_file_info_fields_ += _fuse_file_info_fields_bitfield
698714
_fuse_file_info_fields_ += [
699-
('padding', ctypes.c_uint, 32 - sum(x[2] for x in _fuse_file_info_fields_bitfield)),
700-
('padding2', ctypes.c_uint, 32),
715+
('padding', _fuse_uint32, ctypes.sizeof(_fuse_uint32) * 8 - _fuse_file_info_flag_count),
716+
('padding2', _fuse_uint32),
717+
]
718+
# https://github.com/libfuse/libfuse/pull/1038#discussion_r1775112524
719+
# https://github.com/libfuse/libfuse/pull/1081
720+
# This padding did always exist because fh was aligned to an offset modulo 8 B,
721+
# but libfuse 3.17 made this explicit and I'm not fully sure how ctypes behaves.
722+
if fuse_version_minor >= 17:
723+
_fuse_file_info_fields_ += [('padding3', _fuse_uint32)]
724+
725+
_fuse_file_info_fields_ += [
701726
('fh', ctypes.c_uint64),
702727
('lock_owner', ctypes.c_uint64),
703728
('poll_events', ctypes.c_uint64),
704729
]
705730

731+
if ctypes.sizeof(ctypes.c_int) == ctypes.sizeof(ctypes.c_uint32) and fuse_version_minor <= 17:
732+
assert ctypes.sizeof(fuse_ops) == 40
733+
706734

707735
class fuse_file_info(ctypes.Structure):
708736
_fields_ = _fuse_file_info_fields_
@@ -750,86 +778,109 @@ class fuse_bufvec(ctypes.Structure):
750778
('buf', ctypes.POINTER(fuse_buf)),
751779
]
752780

753-
781+
# fuse_conn_info struct as defined and documented in fuse_common.h
782+
_fuse_conn_info_fields = [
783+
('proto_major', ctypes.c_uint),
784+
('proto_minor', ctypes.c_uint),
785+
]
754786
if fuse_version_major == 2:
755-
756-
class fuse_conn_info(ctypes.Structure): # Added in 2.6 (ABI break of "init" from 2.5->2.6)
757-
_fields_ = [
758-
('proto_major', ctypes.c_uint),
759-
('proto_minor', ctypes.c_uint),
760-
('async_read', ctypes.c_uint),
761-
('max_write', ctypes.c_uint),
762-
('max_readahead', ctypes.c_uint),
763-
('capable', ctypes.c_uint), # Added in 2.8
764-
('want', ctypes.c_uint), # Added in 2.8
765-
('max_background', ctypes.c_uint), # Added in 2.9
766-
('congestion_threshold', ctypes.c_uint), # Added in 2.9
767-
('reserved', ctypes.c_uint * 23),
768-
]
769-
787+
_fuse_conn_info_fields += [('async_read', _fuse_uint32)]
788+
_fuse_conn_info_fields += [('max_write', _fuse_uint32)]
789+
if fuse_version_major == 3:
790+
_fuse_conn_info_fields += [('max_read', _fuse_uint32)]
791+
_fuse_conn_info_fields += [
792+
('max_readahead', _fuse_uint32),
793+
('capable', _fuse_uint32), # Added in 2.8
794+
('want', _fuse_uint32), # Added in 2.8
795+
('max_background', _fuse_uint32), # Added in 2.9
796+
('congestion_threshold', _fuse_uint32), # Added in 2.9
797+
]
798+
if fuse_version_major == 2:
799+
_fuse_conn_info_fields += [('reserved', _fuse_uint32 * 23)]
770800
elif fuse_version_major == 3:
771-
772-
class fuse_conn_info(ctypes.Structure):
773-
_fields_ = [
774-
('proto_major', ctypes.c_uint),
775-
('proto_minor', ctypes.c_uint),
776-
('max_write', ctypes.c_uint),
777-
('max_read', ctypes.c_uint),
778-
('max_readahead', ctypes.c_uint),
779-
('capable', ctypes.c_uint),
780-
('want', ctypes.c_uint),
781-
('max_background', ctypes.c_uint),
782-
('congestion_threshold', ctypes.c_uint),
783-
('time_gran', ctypes.c_uint),
784-
('reserved', ctypes.c_uint * 22),
801+
_fuse_conn_info_fields += [('time_gran', _fuse_uint32)]
802+
if fuse_version_minor < 17:
803+
_fuse_conn_info_fields += [('reserved', _fuse_uint32 * 22)]
804+
else:
805+
_fuse_conn_info_fields += [
806+
('max_backing_stack_depth', ctypes.c_uint32),
807+
('no_interrupt', ctypes.c_uint32, 1),
808+
('padding', ctypes.c_uint32, 31),
809+
('capable_ext', ctypes.c_uint64),
810+
('want_ext', ctypes.c_uint64),
811+
('request_timeout', ctypes.c_uint16),
812+
('reserved', ctypes.c_uint16 * 31),
785813
]
786814

787-
788-
# FUSE 3-only struct for second init argument. If a FUSE 2 method is loaded but 'init_with_config'
789-
# overridden, then this argument will only be zero-initialized and should be ignored.
815+
# https://github.com/libfuse/libfuse/pull/1081/commits/24f5b129c4e1b03ebbd05ac0c7673f306facea1ak
816+
class fuse_conn_info(ctypes.Structure): # Added in 2.6 (ABI break of "init" from 2.5->2.6)
817+
_fields_ = _fuse_conn_info_fields
818+
if (fuse_version_major, fuse_version_minor) >= (3,17):
819+
assert ctypes.sizeof(fuse_conn_info) == 128
820+
821+
# FUSE 3-only struct for second init argument defined in fuse.h.
822+
# If a FUSE 2 method is loaded but 'init_with_config' overridden,
823+
# then this argument will only be zero-initialized and should be ignored.
824+
# 3.0.0 -> 3.10.5: no change
825+
# 3.10.5 -> 3.11.0: no_rofd_flush was added in the middle causing an ABI break
826+
# 3.11.0 -> 3.13.1: no change
827+
# 3.13.1 -> 3.14.1: parallel_direct_writes was added in the middle causing an ABI break
828+
# 3.14.1 -> 3.16.2: no change
829+
# 3.16.2 -> 3.17.0: Changed all types to uint32_t, added reserved bytes, reverted order to 3.10.
830+
# https://github.com/libfuse/libfuse/pull/1081
790831
_fuse_config_fields_ = [
791-
('set_gid', ctypes.c_int),
792-
('gid', ctypes.c_uint),
793-
('set_uid', ctypes.c_int),
794-
('uid', ctypes.c_uint),
795-
('set_mode', ctypes.c_int),
796-
('umask', ctypes.c_uint),
832+
('set_gid', _fuse_int32),
833+
('gid', _fuse_uint32),
834+
('set_uid', _fuse_int32),
835+
('uid', _fuse_uint32),
836+
('set_mode', _fuse_int32),
837+
('umask', _fuse_uint32),
797838
('entry_timeout', ctypes.c_double),
798839
('negative_timeout', ctypes.c_double),
799840
('attr_timeout', ctypes.c_double),
800-
('intr', ctypes.c_int),
801-
('intr_signal', ctypes.c_int),
802-
('remember', ctypes.c_int),
803-
('hard_remove', ctypes.c_int),
804-
('use_ino', ctypes.c_int),
805-
('readdir_ino', ctypes.c_int),
806-
('direct_io', ctypes.c_int),
807-
('kernel_cache', ctypes.c_int),
808-
('auto_cache', ctypes.c_int),
841+
('intr', _fuse_int32),
842+
('intr_signal', _fuse_int32),
843+
('remember', _fuse_int32),
844+
('hard_remove', _fuse_int32),
845+
('use_ino', _fuse_int32),
846+
('readdir_ino', _fuse_int32),
847+
('direct_io', _fuse_int32),
848+
('kernel_cache', _fuse_int32),
849+
('auto_cache', _fuse_int32),
809850
]
810851
if fuse_version_major == 3:
811852
# Adding this member in the middle of the struct was an ABI-incompatible change!
812-
if fuse_version_minor >= 11:
853+
if fuse_version_minor >= 11 and fuse_version_minor < 17:
813854
_fuse_config_fields_ += [('no_rofd_flush', ctypes.c_int)]
814855

815856
_fuse_config_fields_ += [
816-
('ac_attr_timeout_set', ctypes.c_int),
857+
('ac_attr_timeout_set', _fuse_int32),
817858
('ac_attr_timeout', ctypes.c_double),
818-
('nullpath_ok', ctypes.c_int),
859+
('nullpath_ok', _fuse_int32),
819860
]
820861

821862
# Another ABI break as discussed here: https://lists.debian.org/debian-devel/2024/03/msg00278.html
822863
# The break was in 3.14.1 NOT in 3.14.0, but I cannot query the bugfix version.
823864
# I'd hope that all 3.14.0 installations have been replaced by updates to 3.14.1.
824-
if fuse_version_minor >= 14:
865+
if fuse_version_minor >= 14 and fuse_version_minor < 17:
825866
_fuse_config_fields_ += [('parallel_direct_writes', ctypes.c_int)]
826867

827868
_fuse_config_fields_ += [
828-
('show_help', ctypes.c_int),
869+
('show_help', _fuse_int32),
829870
('modules', ctypes.c_char_p),
830-
('debug', ctypes.c_int),
871+
('debug', _fuse_int32),
831872
]
832873

874+
if fuse_version_minor >= 17:
875+
_fuse_config_fields_ += [
876+
('fmask', ctypes.c_uint32),
877+
('dmask', ctypes.c_uint32),
878+
('no_rofd_flush', ctypes.c_int32),
879+
('parallel_direct_writes', ctypes.c_int32),
880+
('flags', ctypes.c_int32),
881+
('reserved', ctypes.c_uint64 * 48),
882+
]
883+
833884

834885
class fuse_config(ctypes.Structure):
835886
_fields_ = _fuse_config_fields_

0 commit comments

Comments
 (0)