@@ -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
658665if 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 ]
672679elif 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
707735class 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+ ]
754787if 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]
810855if 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
834889class fuse_config (ctypes .Structure ):
835890 _fields_ = _fuse_config_fields_
0 commit comments