Skip to content

Commit 0185620

Browse files
committed
[feature] Support libfuse 3.17+
1 parent 167c642 commit 0185620

File tree

1 file changed

+123
-68
lines changed

1 file changed

+123
-68
lines changed

mfusepy.py

Lines changed: 123 additions & 68 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_
@@ -751,85 +779,112 @@ class fuse_bufvec(ctypes.Structure):
751779
]
752780

753781

782+
# fuse_conn_info struct as defined and documented in fuse_common.h
783+
_fuse_conn_info_fields = [
784+
('proto_major', ctypes.c_uint),
785+
('proto_minor', ctypes.c_uint),
786+
]
754787
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),
788+
_fuse_conn_info_fields += [('async_read', _fuse_uint32)]
789+
_fuse_conn_info_fields += [('max_write', _fuse_uint32)]
790+
if fuse_version_major == 3:
791+
_fuse_conn_info_fields += [('max_read', _fuse_uint32)]
792+
_fuse_conn_info_fields += [
793+
('max_readahead', _fuse_uint32),
794+
('capable', _fuse_uint32), # Added in 2.8
795+
('want', _fuse_uint32), # Added in 2.8
796+
('max_background', _fuse_uint32), # Added in 2.9
797+
('congestion_threshold', _fuse_uint32), # Added in 2.9
798+
]
799+
if fuse_version_major == 2:
800+
_fuse_conn_info_fields += [('reserved', _fuse_uint32 * 23)]
801+
elif fuse_version_major == 3:
802+
_fuse_conn_info_fields += [('time_gran', _fuse_uint32)]
803+
if fuse_version_minor < 17:
804+
_fuse_conn_info_fields += [('reserved', _fuse_uint32 * 22)]
805+
else:
806+
_fuse_conn_info_fields += [
807+
('max_backing_stack_depth', ctypes.c_uint32),
808+
('no_interrupt', ctypes.c_uint32, 1),
809+
('padding', ctypes.c_uint32, 31),
810+
('capable_ext', ctypes.c_uint64),
811+
('want_ext', ctypes.c_uint64),
812+
('request_timeout', ctypes.c_uint16),
813+
('reserved', ctypes.c_uint16 * 31),
768814
]
769815

770-
elif fuse_version_major == 3:
771816

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),
785-
]
817+
# https://github.com/libfuse/libfuse/pull/1081/commits/24f5b129c4e1b03ebbd05ac0c7673f306facea1ak
818+
class fuse_conn_info(ctypes.Structure): # Added in 2.6 (ABI break of "init" from 2.5->2.6)
819+
_fields_ = _fuse_conn_info_fields
820+
786821

822+
if (fuse_version_major, fuse_version_minor) >= (3, 17):
823+
assert ctypes.sizeof(fuse_conn_info) == 128
787824

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.
825+
# FUSE 3-only struct for second init argument defined in fuse.h.
826+
# If a FUSE 2 method is loaded but 'init_with_config' overridden,
827+
# then this argument will only be zero-initialized and should be ignored.
828+
# 3.0.0 -> 3.10.5: no change
829+
# 3.10.5 -> 3.11.0: no_rofd_flush was added in the middle causing an ABI break
830+
# 3.11.0 -> 3.13.1: no change
831+
# 3.13.1 -> 3.14.1: parallel_direct_writes was added in the middle causing an ABI break
832+
# 3.14.1 -> 3.16.2: no change
833+
# 3.16.2 -> 3.17.0: Changed all types to uint32_t, added reserved bytes, reverted order to 3.10.
834+
# https://github.com/libfuse/libfuse/pull/1081
790835
_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),
836+
('set_gid', _fuse_int32),
837+
('gid', _fuse_uint32),
838+
('set_uid', _fuse_int32),
839+
('uid', _fuse_uint32),
840+
('set_mode', _fuse_int32),
841+
('umask', _fuse_uint32),
797842
('entry_timeout', ctypes.c_double),
798843
('negative_timeout', ctypes.c_double),
799844
('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),
845+
('intr', _fuse_int32),
846+
('intr_signal', _fuse_int32),
847+
('remember', _fuse_int32),
848+
('hard_remove', _fuse_int32),
849+
('use_ino', _fuse_int32),
850+
('readdir_ino', _fuse_int32),
851+
('direct_io', _fuse_int32),
852+
('kernel_cache', _fuse_int32),
853+
('auto_cache', _fuse_int32),
809854
]
810855
if fuse_version_major == 3:
811856
# Adding this member in the middle of the struct was an ABI-incompatible change!
812-
if fuse_version_minor >= 11:
857+
if fuse_version_minor >= 11 and fuse_version_minor < 17:
813858
_fuse_config_fields_ += [('no_rofd_flush', ctypes.c_int)]
814859

815860
_fuse_config_fields_ += [
816-
('ac_attr_timeout_set', ctypes.c_int),
861+
('ac_attr_timeout_set', _fuse_int32),
817862
('ac_attr_timeout', ctypes.c_double),
818-
('nullpath_ok', ctypes.c_int),
863+
('nullpath_ok', _fuse_int32),
819864
]
820865

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

827872
_fuse_config_fields_ += [
828-
('show_help', ctypes.c_int),
873+
('show_help', _fuse_int32),
829874
('modules', ctypes.c_char_p),
830-
('debug', ctypes.c_int),
875+
('debug', _fuse_int32),
831876
]
832877

878+
if fuse_version_minor >= 17:
879+
_fuse_config_fields_ += [
880+
('fmask', ctypes.c_uint32),
881+
('dmask', ctypes.c_uint32),
882+
('no_rofd_flush', ctypes.c_int32),
883+
('parallel_direct_writes', ctypes.c_int32),
884+
('flags', ctypes.c_int32),
885+
('reserved', ctypes.c_uint64 * 48),
886+
]
887+
833888

834889
class fuse_config(ctypes.Structure):
835890
_fields_ = _fuse_config_fields_

0 commit comments

Comments
 (0)