about summary refs log tree commit diff
path: root/support
diff options
context:
space:
mode:
Diffstat (limited to 'support')
-rw-r--r--support/Makefile23
-rw-r--r--support/bundled/README5
-rw-r--r--support/bundled/linux/COPYING20
-rw-r--r--support/bundled/linux/LICENSES/exceptions/Linux-syscall-note24
-rw-r--r--support/bundled/linux/LICENSES/preferred/GPL-2.0359
-rw-r--r--support/bundled/linux/include/uapi/linux/fuse.h1189
-rw-r--r--support/check.h5
-rw-r--r--support/file_contents.h63
-rw-r--r--support/fuse.h217
-rw-r--r--support/process_state.h7
-rw-r--r--support/readdir.h85
-rw-r--r--support/support_check_stat_fd.c (renamed from support/xlstat.c)11
-rw-r--r--support/support_check_stat_path.c (renamed from support/support-xfstat.c)9
-rw-r--r--support/support_compare_file_bytes.c42
-rw-r--r--support/support_compare_file_string.c28
-rw-r--r--support/support_format_addrinfo.c1
-rw-r--r--support/support_fuse.c706
-rw-r--r--support/support_open_and_compare_file_bytes.c (renamed from support/support-xstat-time64.c)25
-rw-r--r--support/support_open_and_compare_file_string.c32
-rw-r--r--support/support_process_state.c6
-rw-r--r--support/support_readdir.c318
-rw-r--r--support/support_readdir_check.c30
-rw-r--r--support/support_readdir_r_check.c (renamed from support/xlstat-time64.c)25
-rw-r--r--support/support_test_compare_failure.c11
-rw-r--r--support/test-driver.c1
-rw-r--r--support/timespec-add.c1
-rw-r--r--support/timespec-sub.c1
-rw-r--r--support/tst-support-process_state.c19
-rw-r--r--support/tst-support_fuse.c349
-rw-r--r--support/tst-support_readdir.c73
-rw-r--r--support/tst-xdirent.c76
-rw-r--r--support/xclosedir.c (renamed from support/support-xstat.c)14
-rw-r--r--support/xdirent.h86
-rw-r--r--support/xfdopendir.c30
-rw-r--r--support/xopendir.c30
-rw-r--r--support/xstatx.c (renamed from support/support-xfstat-time64.c)18
-rw-r--r--support/xunistd.h32
37 files changed, 3887 insertions, 84 deletions
diff --git a/support/Makefile b/support/Makefile
index aa57207bdc..84e2419775 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -42,15 +42,15 @@ libsupport-routines = \
   resolv_test \
   set_fortify_handler \
   support-open-dev-null-range \
-  support-xfstat \
-  support-xfstat-time64 \
-  support-xstat \
-  support-xstat-time64 \
   support_become_root \
   support_can_chroot \
   support_capture_subprocess \
   support_capture_subprocess_check \
+  support_check_stat_fd \
+  support_check_stat_path \
   support_chroot \
+  support_compare_file_bytes \
+  support_compare_file_string \
   support_copy_file \
   support_copy_file_range \
   support_create_timer \
@@ -64,9 +64,12 @@ libsupport-routines = \
   support_format_herrno \
   support_format_hostent \
   support_format_netent \
+  support_fuse \
   support_isolate_in_subprocess \
   support_mutex_pi_monotonic \
   support_need_proc \
+  support_open_and_compare_file_bytes \
+  support_open_and_compare_file_string \
   support_openpty \
   support_path_support_time64 \
   support_paths \
@@ -75,6 +78,9 @@ libsupport-routines = \
   support_quote_blob \
   support_quote_blob_wide \
   support_quote_string \
+  support_readdir \
+  support_readdir_check \
+  support_readdir_r_check \
   support_record_failure \
   support_run_diff \
   support_select_modifies_timeout \
@@ -117,6 +123,7 @@ libsupport-routines = \
   xclock_settime_time64 \
   xclone \
   xclose \
+  xclosedir \
   xconnect \
   xcopy_file_range \
   xdlfcn \
@@ -124,6 +131,7 @@ libsupport-routines = \
   xdup2 \
   xfchmod \
   xfclose \
+  xfdopendir \
   xfgets \
   xfopen \
   xfork \
@@ -135,8 +143,6 @@ libsupport-routines = \
   xgetsockname \
   xlisten \
   xlseek \
-  xlstat \
-  xlstat-time64 \
   xmalloc \
   xmemstream \
   xmkdir \
@@ -147,6 +153,7 @@ libsupport-routines = \
   xmunmap \
   xnewlocale \
   xopen \
+  xopendir \
   xpipe \
   xpoll \
   xposix_memalign \
@@ -210,6 +217,7 @@ libsupport-routines = \
   xsignal \
   xsigstack \
   xsocket \
+  xstatx \
   xstrdup \
   xstrndup \
   xsymlink \
@@ -321,15 +329,18 @@ tests = \
   tst-support_capture_subprocess \
   tst-support_descriptors \
   tst-support_format_dns_packet \
+  tst-support_fuse \
   tst-support_quote_blob \
   tst-support_quote_blob_wide \
   tst-support_quote_string \
+  tst-support_readdir \
   tst-support_record_failure \
   tst-test_compare \
   tst-test_compare_blob \
   tst-test_compare_string \
   tst-test_compare_string_wide \
   tst-timespec \
+  tst-xdirent \
   tst-xreadlink \
   tst-xsigstack \
   # tests
diff --git a/support/bundled/README b/support/bundled/README
new file mode 100644
index 0000000000..e861b3d40a
--- /dev/null
+++ b/support/bundled/README
@@ -0,0 +1,5 @@
+This subtree contains bundled files included verbatim from other
+sources.  They are used for building the support/ infrastructure.
+
+linux/
+	Select files from the Linux 6.10 source tree.
diff --git a/support/bundled/linux/COPYING b/support/bundled/linux/COPYING
new file mode 100644
index 0000000000..a635a38ef9
--- /dev/null
+++ b/support/bundled/linux/COPYING
@@ -0,0 +1,20 @@
+The Linux Kernel is provided under:
+
+	SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+
+Being under the terms of the GNU General Public License version 2 only,
+according with:
+
+	LICENSES/preferred/GPL-2.0
+
+With an explicit syscall exception, as stated at:
+
+	LICENSES/exceptions/Linux-syscall-note
+
+In addition, other licenses may also apply. Please see:
+
+	Documentation/process/license-rules.rst
+
+for more details.
+
+All contributions to the Linux Kernel are subject to this COPYING file.
diff --git a/support/bundled/linux/LICENSES/exceptions/Linux-syscall-note b/support/bundled/linux/LICENSES/exceptions/Linux-syscall-note
new file mode 100644
index 0000000000..adbe756a05
--- /dev/null
+++ b/support/bundled/linux/LICENSES/exceptions/Linux-syscall-note
@@ -0,0 +1,24 @@
+SPDX-Exception-Identifier: Linux-syscall-note
+SPDX-URL: https://spdx.org/licenses/Linux-syscall-note.html
+SPDX-Licenses: GPL-2.0, GPL-2.0+, GPL-1.0+, LGPL-2.0, LGPL-2.0+, LGPL-2.1, LGPL-2.1+, GPL-2.0-only, GPL-2.0-or-later
+Usage-Guide:
+  This exception is used together with one of the above SPDX-Licenses
+  to mark user space API (uapi) header files so they can be included
+  into non GPL compliant user space application code.
+  To use this exception add it with the keyword WITH to one of the
+  identifiers in the SPDX-Licenses tag:
+    SPDX-License-Identifier: <SPDX-License> WITH Linux-syscall-note
+License-Text:
+
+   NOTE! This copyright does *not* cover user programs that use kernel
+ services by normal system calls - this is merely considered normal use
+ of the kernel, and does *not* fall under the heading of "derived work".
+ Also note that the GPL below is copyrighted by the Free Software
+ Foundation, but the instance of code that it refers to (the Linux
+ kernel) is copyrighted by me and others who actually wrote it.
+
+ Also note that the only valid version of the GPL as far as the kernel
+ is concerned is _this_ particular version of the license (ie v2, not
+ v2.2 or v3.x or whatever), unless explicitly otherwise stated.
+
+			Linus Torvalds
diff --git a/support/bundled/linux/LICENSES/preferred/GPL-2.0 b/support/bundled/linux/LICENSES/preferred/GPL-2.0
new file mode 100644
index 0000000000..ff0812fd89
--- /dev/null
+++ b/support/bundled/linux/LICENSES/preferred/GPL-2.0
@@ -0,0 +1,359 @@
+Valid-License-Identifier: GPL-2.0
+Valid-License-Identifier: GPL-2.0-only
+Valid-License-Identifier: GPL-2.0+
+Valid-License-Identifier: GPL-2.0-or-later
+SPDX-URL: https://spdx.org/licenses/GPL-2.0.html
+Usage-Guide:
+  To use this license in source code, put one of the following SPDX
+  tag/value pairs into a comment according to the placement
+  guidelines in the licensing rules documentation.
+  For 'GNU General Public License (GPL) version 2 only' use:
+    SPDX-License-Identifier: GPL-2.0
+  or
+    SPDX-License-Identifier: GPL-2.0-only
+  For 'GNU General Public License (GPL) version 2 or any later version' use:
+    SPDX-License-Identifier: GPL-2.0+
+  or
+    SPDX-License-Identifier: GPL-2.0-or-later
+License-Text:
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/support/bundled/linux/include/uapi/linux/fuse.h b/support/bundled/linux/include/uapi/linux/fuse.h
new file mode 100644
index 0000000000..d08b99d60f
--- /dev/null
+++ b/support/bundled/linux/include/uapi/linux/fuse.h
@@ -0,0 +1,1189 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-2-Clause) */
+/*
+    This file defines the kernel interface of FUSE
+    Copyright (C) 2001-2008  Miklos Szeredi <miklos@szeredi.hu>
+
+    This program can be distributed under the terms of the GNU GPL.
+    See the file COPYING.
+
+    This -- and only this -- header file may also be distributed under
+    the terms of the BSD Licence as follows:
+
+    Copyright (C) 2001-2007 Miklos Szeredi. All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+    ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+*/
+
+/*
+ * This file defines the kernel interface of FUSE
+ *
+ * Protocol changelog:
+ *
+ * 7.1:
+ *  - add the following messages:
+ *      FUSE_SETATTR, FUSE_SYMLINK, FUSE_MKNOD, FUSE_MKDIR, FUSE_UNLINK,
+ *      FUSE_RMDIR, FUSE_RENAME, FUSE_LINK, FUSE_OPEN, FUSE_READ, FUSE_WRITE,
+ *      FUSE_RELEASE, FUSE_FSYNC, FUSE_FLUSH, FUSE_SETXATTR, FUSE_GETXATTR,
+ *      FUSE_LISTXATTR, FUSE_REMOVEXATTR, FUSE_OPENDIR, FUSE_READDIR,
+ *      FUSE_RELEASEDIR
+ *  - add padding to messages to accommodate 32-bit servers on 64-bit kernels
+ *
+ * 7.2:
+ *  - add FOPEN_DIRECT_IO and FOPEN_KEEP_CACHE flags
+ *  - add FUSE_FSYNCDIR message
+ *
+ * 7.3:
+ *  - add FUSE_ACCESS message
+ *  - add FUSE_CREATE message
+ *  - add filehandle to fuse_setattr_in
+ *
+ * 7.4:
+ *  - add frsize to fuse_kstatfs
+ *  - clean up request size limit checking
+ *
+ * 7.5:
+ *  - add flags and max_write to fuse_init_out
+ *
+ * 7.6:
+ *  - add max_readahead to fuse_init_in and fuse_init_out
+ *
+ * 7.7:
+ *  - add FUSE_INTERRUPT message
+ *  - add POSIX file lock support
+ *
+ * 7.8:
+ *  - add lock_owner and flags fields to fuse_release_in
+ *  - add FUSE_BMAP message
+ *  - add FUSE_DESTROY message
+ *
+ * 7.9:
+ *  - new fuse_getattr_in input argument of GETATTR
+ *  - add lk_flags in fuse_lk_in
+ *  - add lock_owner field to fuse_setattr_in, fuse_read_in and fuse_write_in
+ *  - add blksize field to fuse_attr
+ *  - add file flags field to fuse_read_in and fuse_write_in
+ *  - Add ATIME_NOW and MTIME_NOW flags to fuse_setattr_in
+ *
+ * 7.10
+ *  - add nonseekable open flag
+ *
+ * 7.11
+ *  - add IOCTL message
+ *  - add unsolicited notification support
+ *  - add POLL message and NOTIFY_POLL notification
+ *
+ * 7.12
+ *  - add umask flag to input argument of create, mknod and mkdir
+ *  - add notification messages for invalidation of inodes and
+ *    directory entries
+ *
+ * 7.13
+ *  - make max number of background requests and congestion threshold
+ *    tunables
+ *
+ * 7.14
+ *  - add splice support to fuse device
+ *
+ * 7.15
+ *  - add store notify
+ *  - add retrieve notify
+ *
+ * 7.16
+ *  - add BATCH_FORGET request
+ *  - FUSE_IOCTL_UNRESTRICTED shall now return with array of 'struct
+ *    fuse_ioctl_iovec' instead of ambiguous 'struct iovec'
+ *  - add FUSE_IOCTL_32BIT flag
+ *
+ * 7.17
+ *  - add FUSE_FLOCK_LOCKS and FUSE_RELEASE_FLOCK_UNLOCK
+ *
+ * 7.18
+ *  - add FUSE_IOCTL_DIR flag
+ *  - add FUSE_NOTIFY_DELETE
+ *
+ * 7.19
+ *  - add FUSE_FALLOCATE
+ *
+ * 7.20
+ *  - add FUSE_AUTO_INVAL_DATA
+ *
+ * 7.21
+ *  - add FUSE_READDIRPLUS
+ *  - send the requested events in POLL request
+ *
+ * 7.22
+ *  - add FUSE_ASYNC_DIO
+ *
+ * 7.23
+ *  - add FUSE_WRITEBACK_CACHE
+ *  - add time_gran to fuse_init_out
+ *  - add reserved space to fuse_init_out
+ *  - add FATTR_CTIME
+ *  - add ctime and ctimensec to fuse_setattr_in
+ *  - add FUSE_RENAME2 request
+ *  - add FUSE_NO_OPEN_SUPPORT flag
+ *
+ *  7.24
+ *  - add FUSE_LSEEK for SEEK_HOLE and SEEK_DATA support
+ *
+ *  7.25
+ *  - add FUSE_PARALLEL_DIROPS
+ *
+ *  7.26
+ *  - add FUSE_HANDLE_KILLPRIV
+ *  - add FUSE_POSIX_ACL
+ *
+ *  7.27
+ *  - add FUSE_ABORT_ERROR
+ *
+ *  7.28
+ *  - add FUSE_COPY_FILE_RANGE
+ *  - add FOPEN_CACHE_DIR
+ *  - add FUSE_MAX_PAGES, add max_pages to init_out
+ *  - add FUSE_CACHE_SYMLINKS
+ *
+ *  7.29
+ *  - add FUSE_NO_OPENDIR_SUPPORT flag
+ *
+ *  7.30
+ *  - add FUSE_EXPLICIT_INVAL_DATA
+ *  - add FUSE_IOCTL_COMPAT_X32
+ *
+ *  7.31
+ *  - add FUSE_WRITE_KILL_PRIV flag
+ *  - add FUSE_SETUPMAPPING and FUSE_REMOVEMAPPING
+ *  - add map_alignment to fuse_init_out, add FUSE_MAP_ALIGNMENT flag
+ *
+ *  7.32
+ *  - add flags to fuse_attr, add FUSE_ATTR_SUBMOUNT, add FUSE_SUBMOUNTS
+ *
+ *  7.33
+ *  - add FUSE_HANDLE_KILLPRIV_V2, FUSE_WRITE_KILL_SUIDGID, FATTR_KILL_SUIDGID
+ *  - add FUSE_OPEN_KILL_SUIDGID
+ *  - extend fuse_setxattr_in, add FUSE_SETXATTR_EXT
+ *  - add FUSE_SETXATTR_ACL_KILL_SGID
+ *
+ *  7.34
+ *  - add FUSE_SYNCFS
+ *
+ *  7.35
+ *  - add FOPEN_NOFLUSH
+ *
+ *  7.36
+ *  - extend fuse_init_in with reserved fields, add FUSE_INIT_EXT init flag
+ *  - add flags2 to fuse_init_in and fuse_init_out
+ *  - add FUSE_SECURITY_CTX init flag
+ *  - add security context to create, mkdir, symlink, and mknod requests
+ *  - add FUSE_HAS_INODE_DAX, FUSE_ATTR_DAX
+ *
+ *  7.37
+ *  - add FUSE_TMPFILE
+ *
+ *  7.38
+ *  - add FUSE_EXPIRE_ONLY flag to fuse_notify_inval_entry
+ *  - add FOPEN_PARALLEL_DIRECT_WRITES
+ *  - add total_extlen to fuse_in_header
+ *  - add FUSE_MAX_NR_SECCTX
+ *  - add extension header
+ *  - add FUSE_EXT_GROUPS
+ *  - add FUSE_CREATE_SUPP_GROUP
+ *  - add FUSE_HAS_EXPIRE_ONLY
+ *
+ *  7.39
+ *  - add FUSE_DIRECT_IO_ALLOW_MMAP
+ *  - add FUSE_STATX and related structures
+ *
+ *  7.40
+ *  - add max_stack_depth to fuse_init_out, add FUSE_PASSTHROUGH init flag
+ *  - add backing_id to fuse_open_out, add FOPEN_PASSTHROUGH open flag
+ *  - add FUSE_NO_EXPORT_SUPPORT init flag
+ *  - add FUSE_NOTIFY_RESEND, add FUSE_HAS_RESEND init flag
+ */
+
+#ifndef _LINUX_FUSE_H
+#define _LINUX_FUSE_H
+
+#ifdef __KERNEL__
+#include <linux/types.h>
+#else
+#include <stdint.h>
+#endif
+
+/*
+ * Version negotiation:
+ *
+ * Both the kernel and userspace send the version they support in the
+ * INIT request and reply respectively.
+ *
+ * If the major versions match then both shall use the smallest
+ * of the two minor versions for communication.
+ *
+ * If the kernel supports a larger major version, then userspace shall
+ * reply with the major version it supports, ignore the rest of the
+ * INIT message and expect a new INIT message from the kernel with a
+ * matching major version.
+ *
+ * If the library supports a larger major version, then it shall fall
+ * back to the major protocol version sent by the kernel for
+ * communication and reply with that major version (and an arbitrary
+ * supported minor version).
+ */
+
+/** Version number of this interface */
+#define FUSE_KERNEL_VERSION 7
+
+/** Minor version number of this interface */
+#define FUSE_KERNEL_MINOR_VERSION 40
+
+/** The node ID of the root inode */
+#define FUSE_ROOT_ID 1
+
+/* Make sure all structures are padded to 64bit boundary, so 32bit
+   userspace works under 64bit kernels */
+
+struct fuse_attr {
+	uint64_t	ino;
+	uint64_t	size;
+	uint64_t	blocks;
+	uint64_t	atime;
+	uint64_t	mtime;
+	uint64_t	ctime;
+	uint32_t	atimensec;
+	uint32_t	mtimensec;
+	uint32_t	ctimensec;
+	uint32_t	mode;
+	uint32_t	nlink;
+	uint32_t	uid;
+	uint32_t	gid;
+	uint32_t	rdev;
+	uint32_t	blksize;
+	uint32_t	flags;
+};
+
+/*
+ * The following structures are bit-for-bit compatible with the statx(2) ABI in
+ * Linux.
+ */
+struct fuse_sx_time {
+	int64_t		tv_sec;
+	uint32_t	tv_nsec;
+	int32_t		__reserved;
+};
+
+struct fuse_statx {
+	uint32_t	mask;
+	uint32_t	blksize;
+	uint64_t	attributes;
+	uint32_t	nlink;
+	uint32_t	uid;
+	uint32_t	gid;
+	uint16_t	mode;
+	uint16_t	__spare0[1];
+	uint64_t	ino;
+	uint64_t	size;
+	uint64_t	blocks;
+	uint64_t	attributes_mask;
+	struct fuse_sx_time	atime;
+	struct fuse_sx_time	btime;
+	struct fuse_sx_time	ctime;
+	struct fuse_sx_time	mtime;
+	uint32_t	rdev_major;
+	uint32_t	rdev_minor;
+	uint32_t	dev_major;
+	uint32_t	dev_minor;
+	uint64_t	__spare2[14];
+};
+
+struct fuse_kstatfs {
+	uint64_t	blocks;
+	uint64_t	bfree;
+	uint64_t	bavail;
+	uint64_t	files;
+	uint64_t	ffree;
+	uint32_t	bsize;
+	uint32_t	namelen;
+	uint32_t	frsize;
+	uint32_t	padding;
+	uint32_t	spare[6];
+};
+
+struct fuse_file_lock {
+	uint64_t	start;
+	uint64_t	end;
+	uint32_t	type;
+	uint32_t	pid; /* tgid */
+};
+
+/**
+ * Bitmasks for fuse_setattr_in.valid
+ */
+#define FATTR_MODE	(1 << 0)
+#define FATTR_UID	(1 << 1)
+#define FATTR_GID	(1 << 2)
+#define FATTR_SIZE	(1 << 3)
+#define FATTR_ATIME	(1 << 4)
+#define FATTR_MTIME	(1 << 5)
+#define FATTR_FH	(1 << 6)
+#define FATTR_ATIME_NOW	(1 << 7)
+#define FATTR_MTIME_NOW	(1 << 8)
+#define FATTR_LOCKOWNER	(1 << 9)
+#define FATTR_CTIME	(1 << 10)
+#define FATTR_KILL_SUIDGID	(1 << 11)
+
+/**
+ * Flags returned by the OPEN request
+ *
+ * FOPEN_DIRECT_IO: bypass page cache for this open file
+ * FOPEN_KEEP_CACHE: don't invalidate the data cache on open
+ * FOPEN_NONSEEKABLE: the file is not seekable
+ * FOPEN_CACHE_DIR: allow caching this directory
+ * FOPEN_STREAM: the file is stream-like (no file position at all)
+ * FOPEN_NOFLUSH: don't flush data cache on close (unless FUSE_WRITEBACK_CACHE)
+ * FOPEN_PARALLEL_DIRECT_WRITES: Allow concurrent direct writes on the same inode
+ * FOPEN_PASSTHROUGH: passthrough read/write io for this open file
+ */
+#define FOPEN_DIRECT_IO		(1 << 0)
+#define FOPEN_KEEP_CACHE	(1 << 1)
+#define FOPEN_NONSEEKABLE	(1 << 2)
+#define FOPEN_CACHE_DIR		(1 << 3)
+#define FOPEN_STREAM		(1 << 4)
+#define FOPEN_NOFLUSH		(1 << 5)
+#define FOPEN_PARALLEL_DIRECT_WRITES	(1 << 6)
+#define FOPEN_PASSTHROUGH	(1 << 7)
+
+/**
+ * INIT request/reply flags
+ *
+ * FUSE_ASYNC_READ: asynchronous read requests
+ * FUSE_POSIX_LOCKS: remote locking for POSIX file locks
+ * FUSE_FILE_OPS: kernel sends file handle for fstat, etc... (not yet supported)
+ * FUSE_ATOMIC_O_TRUNC: handles the O_TRUNC open flag in the filesystem
+ * FUSE_EXPORT_SUPPORT: filesystem handles lookups of "." and ".."
+ * FUSE_BIG_WRITES: filesystem can handle write size larger than 4kB
+ * FUSE_DONT_MASK: don't apply umask to file mode on create operations
+ * FUSE_SPLICE_WRITE: kernel supports splice write on the device
+ * FUSE_SPLICE_MOVE: kernel supports splice move on the device
+ * FUSE_SPLICE_READ: kernel supports splice read on the device
+ * FUSE_FLOCK_LOCKS: remote locking for BSD style file locks
+ * FUSE_HAS_IOCTL_DIR: kernel supports ioctl on directories
+ * FUSE_AUTO_INVAL_DATA: automatically invalidate cached pages
+ * FUSE_DO_READDIRPLUS: do READDIRPLUS (READDIR+LOOKUP in one)
+ * FUSE_READDIRPLUS_AUTO: adaptive readdirplus
+ * FUSE_ASYNC_DIO: asynchronous direct I/O submission
+ * FUSE_WRITEBACK_CACHE: use writeback cache for buffered writes
+ * FUSE_NO_OPEN_SUPPORT: kernel supports zero-message opens
+ * FUSE_PARALLEL_DIROPS: allow parallel lookups and readdir
+ * FUSE_HANDLE_KILLPRIV: fs handles killing suid/sgid/cap on write/chown/trunc
+ * FUSE_POSIX_ACL: filesystem supports posix acls
+ * FUSE_ABORT_ERROR: reading the device after abort returns ECONNABORTED
+ * FUSE_MAX_PAGES: init_out.max_pages contains the max number of req pages
+ * FUSE_CACHE_SYMLINKS: cache READLINK responses
+ * FUSE_NO_OPENDIR_SUPPORT: kernel supports zero-message opendir
+ * FUSE_EXPLICIT_INVAL_DATA: only invalidate cached pages on explicit request
+ * FUSE_MAP_ALIGNMENT: init_out.map_alignment contains log2(byte alignment) for
+ *		       foffset and moffset fields in struct
+ *		       fuse_setupmapping_out and fuse_removemapping_one.
+ * FUSE_SUBMOUNTS: kernel supports auto-mounting directory submounts
+ * FUSE_HANDLE_KILLPRIV_V2: fs kills suid/sgid/cap on write/chown/trunc.
+ *			Upon write/truncate suid/sgid is only killed if caller
+ *			does not have CAP_FSETID. Additionally upon
+ *			write/truncate sgid is killed only if file has group
+ *			execute permission. (Same as Linux VFS behavior).
+ * FUSE_SETXATTR_EXT:	Server supports extended struct fuse_setxattr_in
+ * FUSE_INIT_EXT: extended fuse_init_in request
+ * FUSE_INIT_RESERVED: reserved, do not use
+ * FUSE_SECURITY_CTX:	add security context to create, mkdir, symlink, and
+ *			mknod
+ * FUSE_HAS_INODE_DAX:  use per inode DAX
+ * FUSE_CREATE_SUPP_GROUP: add supplementary group info to create, mkdir,
+ *			symlink and mknod (single group that matches parent)
+ * FUSE_HAS_EXPIRE_ONLY: kernel supports expiry-only entry invalidation
+ * FUSE_DIRECT_IO_ALLOW_MMAP: allow shared mmap in FOPEN_DIRECT_IO mode.
+ * FUSE_NO_EXPORT_SUPPORT: explicitly disable export support
+ * FUSE_HAS_RESEND: kernel supports resending pending requests, and the high bit
+ *		    of the request ID indicates resend requests
+ */
+#define FUSE_ASYNC_READ		(1 << 0)
+#define FUSE_POSIX_LOCKS	(1 << 1)
+#define FUSE_FILE_OPS		(1 << 2)
+#define FUSE_ATOMIC_O_TRUNC	(1 << 3)
+#define FUSE_EXPORT_SUPPORT	(1 << 4)
+#define FUSE_BIG_WRITES		(1 << 5)
+#define FUSE_DONT_MASK		(1 << 6)
+#define FUSE_SPLICE_WRITE	(1 << 7)
+#define FUSE_SPLICE_MOVE	(1 << 8)
+#define FUSE_SPLICE_READ	(1 << 9)
+#define FUSE_FLOCK_LOCKS	(1 << 10)
+#define FUSE_HAS_IOCTL_DIR	(1 << 11)
+#define FUSE_AUTO_INVAL_DATA	(1 << 12)
+#define FUSE_DO_READDIRPLUS	(1 << 13)
+#define FUSE_READDIRPLUS_AUTO	(1 << 14)
+#define FUSE_ASYNC_DIO		(1 << 15)
+#define FUSE_WRITEBACK_CACHE	(1 << 16)
+#define FUSE_NO_OPEN_SUPPORT	(1 << 17)
+#define FUSE_PARALLEL_DIROPS    (1 << 18)
+#define FUSE_HANDLE_KILLPRIV	(1 << 19)
+#define FUSE_POSIX_ACL		(1 << 20)
+#define FUSE_ABORT_ERROR	(1 << 21)
+#define FUSE_MAX_PAGES		(1 << 22)
+#define FUSE_CACHE_SYMLINKS	(1 << 23)
+#define FUSE_NO_OPENDIR_SUPPORT (1 << 24)
+#define FUSE_EXPLICIT_INVAL_DATA (1 << 25)
+#define FUSE_MAP_ALIGNMENT	(1 << 26)
+#define FUSE_SUBMOUNTS		(1 << 27)
+#define FUSE_HANDLE_KILLPRIV_V2	(1 << 28)
+#define FUSE_SETXATTR_EXT	(1 << 29)
+#define FUSE_INIT_EXT		(1 << 30)
+#define FUSE_INIT_RESERVED	(1 << 31)
+/* bits 32..63 get shifted down 32 bits into the flags2 field */
+#define FUSE_SECURITY_CTX	(1ULL << 32)
+#define FUSE_HAS_INODE_DAX	(1ULL << 33)
+#define FUSE_CREATE_SUPP_GROUP	(1ULL << 34)
+#define FUSE_HAS_EXPIRE_ONLY	(1ULL << 35)
+#define FUSE_DIRECT_IO_ALLOW_MMAP (1ULL << 36)
+#define FUSE_PASSTHROUGH	(1ULL << 37)
+#define FUSE_NO_EXPORT_SUPPORT	(1ULL << 38)
+#define FUSE_HAS_RESEND		(1ULL << 39)
+
+/* Obsolete alias for FUSE_DIRECT_IO_ALLOW_MMAP */
+#define FUSE_DIRECT_IO_RELAX	FUSE_DIRECT_IO_ALLOW_MMAP
+
+/**
+ * CUSE INIT request/reply flags
+ *
+ * CUSE_UNRESTRICTED_IOCTL:  use unrestricted ioctl
+ */
+#define CUSE_UNRESTRICTED_IOCTL	(1 << 0)
+
+/**
+ * Release flags
+ */
+#define FUSE_RELEASE_FLUSH	(1 << 0)
+#define FUSE_RELEASE_FLOCK_UNLOCK	(1 << 1)
+
+/**
+ * Getattr flags
+ */
+#define FUSE_GETATTR_FH		(1 << 0)
+
+/**
+ * Lock flags
+ */
+#define FUSE_LK_FLOCK		(1 << 0)
+
+/**
+ * WRITE flags
+ *
+ * FUSE_WRITE_CACHE: delayed write from page cache, file handle is guessed
+ * FUSE_WRITE_LOCKOWNER: lock_owner field is valid
+ * FUSE_WRITE_KILL_SUIDGID: kill suid and sgid bits
+ */
+#define FUSE_WRITE_CACHE	(1 << 0)
+#define FUSE_WRITE_LOCKOWNER	(1 << 1)
+#define FUSE_WRITE_KILL_SUIDGID (1 << 2)
+
+/* Obsolete alias; this flag implies killing suid/sgid only. */
+#define FUSE_WRITE_KILL_PRIV	FUSE_WRITE_KILL_SUIDGID
+
+/**
+ * Read flags
+ */
+#define FUSE_READ_LOCKOWNER	(1 << 1)
+
+/**
+ * Ioctl flags
+ *
+ * FUSE_IOCTL_COMPAT: 32bit compat ioctl on 64bit machine
+ * FUSE_IOCTL_UNRESTRICTED: not restricted to well-formed ioctls, retry allowed
+ * FUSE_IOCTL_RETRY: retry with new iovecs
+ * FUSE_IOCTL_32BIT: 32bit ioctl
+ * FUSE_IOCTL_DIR: is a directory
+ * FUSE_IOCTL_COMPAT_X32: x32 compat ioctl on 64bit machine (64bit time_t)
+ *
+ * FUSE_IOCTL_MAX_IOV: maximum of in_iovecs + out_iovecs
+ */
+#define FUSE_IOCTL_COMPAT	(1 << 0)
+#define FUSE_IOCTL_UNRESTRICTED	(1 << 1)
+#define FUSE_IOCTL_RETRY	(1 << 2)
+#define FUSE_IOCTL_32BIT	(1 << 3)
+#define FUSE_IOCTL_DIR		(1 << 4)
+#define FUSE_IOCTL_COMPAT_X32	(1 << 5)
+
+#define FUSE_IOCTL_MAX_IOV	256
+
+/**
+ * Poll flags
+ *
+ * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify
+ */
+#define FUSE_POLL_SCHEDULE_NOTIFY (1 << 0)
+
+/**
+ * Fsync flags
+ *
+ * FUSE_FSYNC_FDATASYNC: Sync data only, not metadata
+ */
+#define FUSE_FSYNC_FDATASYNC	(1 << 0)
+
+/**
+ * fuse_attr flags
+ *
+ * FUSE_ATTR_SUBMOUNT: Object is a submount root
+ * FUSE_ATTR_DAX: Enable DAX for this file in per inode DAX mode
+ */
+#define FUSE_ATTR_SUBMOUNT      (1 << 0)
+#define FUSE_ATTR_DAX		(1 << 1)
+
+/**
+ * Open flags
+ * FUSE_OPEN_KILL_SUIDGID: Kill suid and sgid if executable
+ */
+#define FUSE_OPEN_KILL_SUIDGID	(1 << 0)
+
+/**
+ * setxattr flags
+ * FUSE_SETXATTR_ACL_KILL_SGID: Clear SGID when system.posix_acl_access is set
+ */
+#define FUSE_SETXATTR_ACL_KILL_SGID	(1 << 0)
+
+/**
+ * notify_inval_entry flags
+ * FUSE_EXPIRE_ONLY
+ */
+#define FUSE_EXPIRE_ONLY		(1 << 0)
+
+/**
+ * extension type
+ * FUSE_MAX_NR_SECCTX: maximum value of &fuse_secctx_header.nr_secctx
+ * FUSE_EXT_GROUPS: &fuse_supp_groups extension
+ */
+enum fuse_ext_type {
+	/* Types 0..31 are reserved for fuse_secctx_header */
+	FUSE_MAX_NR_SECCTX	= 31,
+	FUSE_EXT_GROUPS		= 32,
+};
+
+enum fuse_opcode {
+	FUSE_LOOKUP		= 1,
+	FUSE_FORGET		= 2,  /* no reply */
+	FUSE_GETATTR		= 3,
+	FUSE_SETATTR		= 4,
+	FUSE_READLINK		= 5,
+	FUSE_SYMLINK		= 6,
+	FUSE_MKNOD		= 8,
+	FUSE_MKDIR		= 9,
+	FUSE_UNLINK		= 10,
+	FUSE_RMDIR		= 11,
+	FUSE_RENAME		= 12,
+	FUSE_LINK		= 13,
+	FUSE_OPEN		= 14,
+	FUSE_READ		= 15,
+	FUSE_WRITE		= 16,
+	FUSE_STATFS		= 17,
+	FUSE_RELEASE		= 18,
+	FUSE_FSYNC		= 20,
+	FUSE_SETXATTR		= 21,
+	FUSE_GETXATTR		= 22,
+	FUSE_LISTXATTR		= 23,
+	FUSE_REMOVEXATTR	= 24,
+	FUSE_FLUSH		= 25,
+	FUSE_INIT		= 26,
+	FUSE_OPENDIR		= 27,
+	FUSE_READDIR		= 28,
+	FUSE_RELEASEDIR		= 29,
+	FUSE_FSYNCDIR		= 30,
+	FUSE_GETLK		= 31,
+	FUSE_SETLK		= 32,
+	FUSE_SETLKW		= 33,
+	FUSE_ACCESS		= 34,
+	FUSE_CREATE		= 35,
+	FUSE_INTERRUPT		= 36,
+	FUSE_BMAP		= 37,
+	FUSE_DESTROY		= 38,
+	FUSE_IOCTL		= 39,
+	FUSE_POLL		= 40,
+	FUSE_NOTIFY_REPLY	= 41,
+	FUSE_BATCH_FORGET	= 42,
+	FUSE_FALLOCATE		= 43,
+	FUSE_READDIRPLUS	= 44,
+	FUSE_RENAME2		= 45,
+	FUSE_LSEEK		= 46,
+	FUSE_COPY_FILE_RANGE	= 47,
+	FUSE_SETUPMAPPING	= 48,
+	FUSE_REMOVEMAPPING	= 49,
+	FUSE_SYNCFS		= 50,
+	FUSE_TMPFILE		= 51,
+	FUSE_STATX		= 52,
+
+	/* CUSE specific operations */
+	CUSE_INIT		= 4096,
+
+	/* Reserved opcodes: helpful to detect structure endian-ness */
+	CUSE_INIT_BSWAP_RESERVED	= 1048576,	/* CUSE_INIT << 8 */
+	FUSE_INIT_BSWAP_RESERVED	= 436207616,	/* FUSE_INIT << 24 */
+};
+
+enum fuse_notify_code {
+	FUSE_NOTIFY_POLL   = 1,
+	FUSE_NOTIFY_INVAL_INODE = 2,
+	FUSE_NOTIFY_INVAL_ENTRY = 3,
+	FUSE_NOTIFY_STORE = 4,
+	FUSE_NOTIFY_RETRIEVE = 5,
+	FUSE_NOTIFY_DELETE = 6,
+	FUSE_NOTIFY_RESEND = 7,
+	FUSE_NOTIFY_CODE_MAX,
+};
+
+/* The read buffer is required to be at least 8k, but may be much larger */
+#define FUSE_MIN_READ_BUFFER 8192
+
+#define FUSE_COMPAT_ENTRY_OUT_SIZE 120
+
+struct fuse_entry_out {
+	uint64_t	nodeid;		/* Inode ID */
+	uint64_t	generation;	/* Inode generation: nodeid:gen must
+					   be unique for the fs's lifetime */
+	uint64_t	entry_valid;	/* Cache timeout for the name */
+	uint64_t	attr_valid;	/* Cache timeout for the attributes */
+	uint32_t	entry_valid_nsec;
+	uint32_t	attr_valid_nsec;
+	struct fuse_attr attr;
+};
+
+struct fuse_forget_in {
+	uint64_t	nlookup;
+};
+
+struct fuse_forget_one {
+	uint64_t	nodeid;
+	uint64_t	nlookup;
+};
+
+struct fuse_batch_forget_in {
+	uint32_t	count;
+	uint32_t	dummy;
+};
+
+struct fuse_getattr_in {
+	uint32_t	getattr_flags;
+	uint32_t	dummy;
+	uint64_t	fh;
+};
+
+#define FUSE_COMPAT_ATTR_OUT_SIZE 96
+
+struct fuse_attr_out {
+	uint64_t	attr_valid;	/* Cache timeout for the attributes */
+	uint32_t	attr_valid_nsec;
+	uint32_t	dummy;
+	struct fuse_attr attr;
+};
+
+struct fuse_statx_in {
+	uint32_t	getattr_flags;
+	uint32_t	reserved;
+	uint64_t	fh;
+	uint32_t	sx_flags;
+	uint32_t	sx_mask;
+};
+
+struct fuse_statx_out {
+	uint64_t	attr_valid;	/* Cache timeout for the attributes */
+	uint32_t	attr_valid_nsec;
+	uint32_t	flags;
+	uint64_t	spare[2];
+	struct fuse_statx stat;
+};
+
+#define FUSE_COMPAT_MKNOD_IN_SIZE 8
+
+struct fuse_mknod_in {
+	uint32_t	mode;
+	uint32_t	rdev;
+	uint32_t	umask;
+	uint32_t	padding;
+};
+
+struct fuse_mkdir_in {
+	uint32_t	mode;
+	uint32_t	umask;
+};
+
+struct fuse_rename_in {
+	uint64_t	newdir;
+};
+
+struct fuse_rename2_in {
+	uint64_t	newdir;
+	uint32_t	flags;
+	uint32_t	padding;
+};
+
+struct fuse_link_in {
+	uint64_t	oldnodeid;
+};
+
+struct fuse_setattr_in {
+	uint32_t	valid;
+	uint32_t	padding;
+	uint64_t	fh;
+	uint64_t	size;
+	uint64_t	lock_owner;
+	uint64_t	atime;
+	uint64_t	mtime;
+	uint64_t	ctime;
+	uint32_t	atimensec;
+	uint32_t	mtimensec;
+	uint32_t	ctimensec;
+	uint32_t	mode;
+	uint32_t	unused4;
+	uint32_t	uid;
+	uint32_t	gid;
+	uint32_t	unused5;
+};
+
+struct fuse_open_in {
+	uint32_t	flags;
+	uint32_t	open_flags;	/* FUSE_OPEN_... */
+};
+
+struct fuse_create_in {
+	uint32_t	flags;
+	uint32_t	mode;
+	uint32_t	umask;
+	uint32_t	open_flags;	/* FUSE_OPEN_... */
+};
+
+struct fuse_open_out {
+	uint64_t	fh;
+	uint32_t	open_flags;
+	int32_t		backing_id;
+};
+
+struct fuse_release_in {
+	uint64_t	fh;
+	uint32_t	flags;
+	uint32_t	release_flags;
+	uint64_t	lock_owner;
+};
+
+struct fuse_flush_in {
+	uint64_t	fh;
+	uint32_t	unused;
+	uint32_t	padding;
+	uint64_t	lock_owner;
+};
+
+struct fuse_read_in {
+	uint64_t	fh;
+	uint64_t	offset;
+	uint32_t	size;
+	uint32_t	read_flags;
+	uint64_t	lock_owner;
+	uint32_t	flags;
+	uint32_t	padding;
+};
+
+#define FUSE_COMPAT_WRITE_IN_SIZE 24
+
+struct fuse_write_in {
+	uint64_t	fh;
+	uint64_t	offset;
+	uint32_t	size;
+	uint32_t	write_flags;
+	uint64_t	lock_owner;
+	uint32_t	flags;
+	uint32_t	padding;
+};
+
+struct fuse_write_out {
+	uint32_t	size;
+	uint32_t	padding;
+};
+
+#define FUSE_COMPAT_STATFS_SIZE 48
+
+struct fuse_statfs_out {
+	struct fuse_kstatfs st;
+};
+
+struct fuse_fsync_in {
+	uint64_t	fh;
+	uint32_t	fsync_flags;
+	uint32_t	padding;
+};
+
+#define FUSE_COMPAT_SETXATTR_IN_SIZE 8
+
+struct fuse_setxattr_in {
+	uint32_t	size;
+	uint32_t	flags;
+	uint32_t	setxattr_flags;
+	uint32_t	padding;
+};
+
+struct fuse_getxattr_in {
+	uint32_t	size;
+	uint32_t	padding;
+};
+
+struct fuse_getxattr_out {
+	uint32_t	size;
+	uint32_t	padding;
+};
+
+struct fuse_lk_in {
+	uint64_t	fh;
+	uint64_t	owner;
+	struct fuse_file_lock lk;
+	uint32_t	lk_flags;
+	uint32_t	padding;
+};
+
+struct fuse_lk_out {
+	struct fuse_file_lock lk;
+};
+
+struct fuse_access_in {
+	uint32_t	mask;
+	uint32_t	padding;
+};
+
+struct fuse_init_in {
+	uint32_t	major;
+	uint32_t	minor;
+	uint32_t	max_readahead;
+	uint32_t	flags;
+	uint32_t	flags2;
+	uint32_t	unused[11];
+};
+
+#define FUSE_COMPAT_INIT_OUT_SIZE 8
+#define FUSE_COMPAT_22_INIT_OUT_SIZE 24
+
+struct fuse_init_out {
+	uint32_t	major;
+	uint32_t	minor;
+	uint32_t	max_readahead;
+	uint32_t	flags;
+	uint16_t	max_background;
+	uint16_t	congestion_threshold;
+	uint32_t	max_write;
+	uint32_t	time_gran;
+	uint16_t	max_pages;
+	uint16_t	map_alignment;
+	uint32_t	flags2;
+	uint32_t	max_stack_depth;
+	uint32_t	unused[6];
+};
+
+#define CUSE_INIT_INFO_MAX 4096
+
+struct cuse_init_in {
+	uint32_t	major;
+	uint32_t	minor;
+	uint32_t	unused;
+	uint32_t	flags;
+};
+
+struct cuse_init_out {
+	uint32_t	major;
+	uint32_t	minor;
+	uint32_t	unused;
+	uint32_t	flags;
+	uint32_t	max_read;
+	uint32_t	max_write;
+	uint32_t	dev_major;		/* chardev major */
+	uint32_t	dev_minor;		/* chardev minor */
+	uint32_t	spare[10];
+};
+
+struct fuse_interrupt_in {
+	uint64_t	unique;
+};
+
+struct fuse_bmap_in {
+	uint64_t	block;
+	uint32_t	blocksize;
+	uint32_t	padding;
+};
+
+struct fuse_bmap_out {
+	uint64_t	block;
+};
+
+struct fuse_ioctl_in {
+	uint64_t	fh;
+	uint32_t	flags;
+	uint32_t	cmd;
+	uint64_t	arg;
+	uint32_t	in_size;
+	uint32_t	out_size;
+};
+
+struct fuse_ioctl_iovec {
+	uint64_t	base;
+	uint64_t	len;
+};
+
+struct fuse_ioctl_out {
+	int32_t		result;
+	uint32_t	flags;
+	uint32_t	in_iovs;
+	uint32_t	out_iovs;
+};
+
+struct fuse_poll_in {
+	uint64_t	fh;
+	uint64_t	kh;
+	uint32_t	flags;
+	uint32_t	events;
+};
+
+struct fuse_poll_out {
+	uint32_t	revents;
+	uint32_t	padding;
+};
+
+struct fuse_notify_poll_wakeup_out {
+	uint64_t	kh;
+};
+
+struct fuse_fallocate_in {
+	uint64_t	fh;
+	uint64_t	offset;
+	uint64_t	length;
+	uint32_t	mode;
+	uint32_t	padding;
+};
+
+/**
+ * FUSE request unique ID flag
+ *
+ * Indicates whether this is a resend request. The receiver should handle this
+ * request accordingly.
+ */
+#define FUSE_UNIQUE_RESEND (1ULL << 63)
+
+struct fuse_in_header {
+	uint32_t	len;
+	uint32_t	opcode;
+	uint64_t	unique;
+	uint64_t	nodeid;
+	uint32_t	uid;
+	uint32_t	gid;
+	uint32_t	pid;
+	uint16_t	total_extlen; /* length of extensions in 8byte units */
+	uint16_t	padding;
+};
+
+struct fuse_out_header {
+	uint32_t	len;
+	int32_t		error;
+	uint64_t	unique;
+};
+
+struct fuse_dirent {
+	uint64_t	ino;
+	uint64_t	off;
+	uint32_t	namelen;
+	uint32_t	type;
+	char name[];
+};
+
+/* Align variable length records to 64bit boundary */
+#define FUSE_REC_ALIGN(x) \
+	(((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+
+#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+#define FUSE_DIRENT_ALIGN(x) FUSE_REC_ALIGN(x)
+#define FUSE_DIRENT_SIZE(d) \
+	FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET + (d)->namelen)
+
+struct fuse_direntplus {
+	struct fuse_entry_out entry_out;
+	struct fuse_dirent dirent;
+};
+
+#define FUSE_NAME_OFFSET_DIRENTPLUS \
+	offsetof(struct fuse_direntplus, dirent.name)
+#define FUSE_DIRENTPLUS_SIZE(d) \
+	FUSE_DIRENT_ALIGN(FUSE_NAME_OFFSET_DIRENTPLUS + (d)->dirent.namelen)
+
+struct fuse_notify_inval_inode_out {
+	uint64_t	ino;
+	int64_t		off;
+	int64_t		len;
+};
+
+struct fuse_notify_inval_entry_out {
+	uint64_t	parent;
+	uint32_t	namelen;
+	uint32_t	flags;
+};
+
+struct fuse_notify_delete_out {
+	uint64_t	parent;
+	uint64_t	child;
+	uint32_t	namelen;
+	uint32_t	padding;
+};
+
+struct fuse_notify_store_out {
+	uint64_t	nodeid;
+	uint64_t	offset;
+	uint32_t	size;
+	uint32_t	padding;
+};
+
+struct fuse_notify_retrieve_out {
+	uint64_t	notify_unique;
+	uint64_t	nodeid;
+	uint64_t	offset;
+	uint32_t	size;
+	uint32_t	padding;
+};
+
+/* Matches the size of fuse_write_in */
+struct fuse_notify_retrieve_in {
+	uint64_t	dummy1;
+	uint64_t	offset;
+	uint32_t	size;
+	uint32_t	dummy2;
+	uint64_t	dummy3;
+	uint64_t	dummy4;
+};
+
+struct fuse_backing_map {
+	int32_t		fd;
+	uint32_t	flags;
+	uint64_t	padding;
+};
+
+/* Device ioctls: */
+#define FUSE_DEV_IOC_MAGIC		229
+#define FUSE_DEV_IOC_CLONE		_IOR(FUSE_DEV_IOC_MAGIC, 0, uint32_t)
+#define FUSE_DEV_IOC_BACKING_OPEN	_IOW(FUSE_DEV_IOC_MAGIC, 1, \
+					     struct fuse_backing_map)
+#define FUSE_DEV_IOC_BACKING_CLOSE	_IOW(FUSE_DEV_IOC_MAGIC, 2, uint32_t)
+
+struct fuse_lseek_in {
+	uint64_t	fh;
+	uint64_t	offset;
+	uint32_t	whence;
+	uint32_t	padding;
+};
+
+struct fuse_lseek_out {
+	uint64_t	offset;
+};
+
+struct fuse_copy_file_range_in {
+	uint64_t	fh_in;
+	uint64_t	off_in;
+	uint64_t	nodeid_out;
+	uint64_t	fh_out;
+	uint64_t	off_out;
+	uint64_t	len;
+	uint64_t	flags;
+};
+
+#define FUSE_SETUPMAPPING_FLAG_WRITE (1ull << 0)
+#define FUSE_SETUPMAPPING_FLAG_READ (1ull << 1)
+struct fuse_setupmapping_in {
+	/* An already open handle */
+	uint64_t	fh;
+	/* Offset into the file to start the mapping */
+	uint64_t	foffset;
+	/* Length of mapping required */
+	uint64_t	len;
+	/* Flags, FUSE_SETUPMAPPING_FLAG_* */
+	uint64_t	flags;
+	/* Offset in Memory Window */
+	uint64_t	moffset;
+};
+
+struct fuse_removemapping_in {
+	/* number of fuse_removemapping_one follows */
+	uint32_t        count;
+};
+
+struct fuse_removemapping_one {
+	/* Offset into the dax window start the unmapping */
+	uint64_t        moffset;
+	/* Length of mapping required */
+	uint64_t	len;
+};
+
+#define FUSE_REMOVEMAPPING_MAX_ENTRY   \
+		(PAGE_SIZE / sizeof(struct fuse_removemapping_one))
+
+struct fuse_syncfs_in {
+	uint64_t	padding;
+};
+
+/*
+ * For each security context, send fuse_secctx with size of security context
+ * fuse_secctx will be followed by security context name and this in turn
+ * will be followed by actual context label.
+ * fuse_secctx, name, context
+ */
+struct fuse_secctx {
+	uint32_t	size;
+	uint32_t	padding;
+};
+
+/*
+ * Contains the information about how many fuse_secctx structures are being
+ * sent and what's the total size of all security contexts (including
+ * size of fuse_secctx_header).
+ *
+ */
+struct fuse_secctx_header {
+	uint32_t	size;
+	uint32_t	nr_secctx;
+};
+
+/**
+ * struct fuse_ext_header - extension header
+ * @size: total size of this extension including this header
+ * @type: type of extension
+ *
+ * This is made compatible with fuse_secctx_header by using type values >
+ * FUSE_MAX_NR_SECCTX
+ */
+struct fuse_ext_header {
+	uint32_t	size;
+	uint32_t	type;
+};
+
+/**
+ * struct fuse_supp_groups - Supplementary group extension
+ * @nr_groups: number of supplementary groups
+ * @groups: flexible array of group IDs
+ */
+struct fuse_supp_groups {
+	uint32_t	nr_groups;
+	uint32_t	groups[];
+};
+
+#endif /* _LINUX_FUSE_H */
diff --git a/support/check.h b/support/check.h
index 711f34b83b..7ea22c7a2c 100644
--- a/support/check.h
+++ b/support/check.h
@@ -25,6 +25,11 @@
 __BEGIN_DECLS
 
 /* Record a test failure, print the failure message to standard output
+   and pass the result of 1 through.  */
+#define FAIL(...) \
+  support_print_failure_impl (__FILE__, __LINE__, __VA_ARGS__)
+
+/* Record a test failure, print the failure message to standard output
    and return 1.  */
 #define FAIL_RET(...) \
   return support_print_failure_impl (__FILE__, __LINE__, __VA_ARGS__)
diff --git a/support/file_contents.h b/support/file_contents.h
new file mode 100644
index 0000000000..9b2d750aae
--- /dev/null
+++ b/support/file_contents.h
@@ -0,0 +1,63 @@
+/* Functionality for checking file contents.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef SUPPORT_FILE_CONTENTS_H
+#define SUPPORT_FILE_CONTENTS_H
+
+#include <support/check.h>
+#include <stdio.h>
+
+__BEGIN_DECLS
+
+/* Check that an already-open file has exactly the given bytes,
+   starting at the current location in the file.  The file position
+   indicator is updated to point after the bytes compared.  Return 0
+   if equal, 1 otherwise or on read error.  */
+int support_compare_file_bytes (FILE *fp, const char *contents, size_t length);
+
+/* Check that an already-open file has exactly the given string as
+   contents, starting at the current offset.  The file position
+   indicator is updated to point after the bytes compared.  Return 0
+   if equal, 1 otherwise or on read error.  */
+int support_compare_file_string (FILE *fp, const char *contents);
+
+/* Check that a not-currently-open file has exactly the given bytes.
+   Return 0 if equal, 1 otherwise or on read error.  */
+int support_open_and_compare_file_bytes (const char *file,
+					 const char *contents,
+					 size_t length);
+
+/* Check that a not-currently-open file has exactly the given string
+   as contents, starting at the current offset.  Return 0 if equal, 1
+   otherwise or on read error.  */
+int support_open_and_compare_file_string (const char *file,
+					  const char *contents);
+
+/* Compare bytes read from an open file with the given string.  The
+   file position indicator is updated to point after the bytes
+   compared.  */
+#define TEST_COMPARE_FILE_STRING(FP, CONTENTS)			\
+  TEST_COMPARE (support_compare_file_string (FP, CONTENTS), 0)
+
+/* Read a file and compare bytes read from it with the given string.  */
+#define TEST_OPEN_AND_COMPARE_FILE_STRING(FILE, CONTENTS)		\
+  TEST_COMPARE (support_open_and_compare_file_string (FILE, CONTENTS), 0)
+
+__END_DECLS
+
+#endif /* SUPPORT_FILE_CONTENTS_H */
diff --git a/support/fuse.h b/support/fuse.h
new file mode 100644
index 0000000000..1c862bedbe
--- /dev/null
+++ b/support/fuse.h
@@ -0,0 +1,217 @@
+/* Facilities for FUSE-backed file system tests.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+/* To run FUSE tests under valgrind, pass the
+   --sim-hints=fuse-compatible option to valgrind.  This option tells
+   valgrind that additional system calls effectively call back into
+   the current program.  */
+
+#ifndef SUPPORT_FUSE_H
+#define SUPPORT_FUSE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <support/bundled/linux/include/uapi/linux/fuse.h>
+
+/* This function must be called furst, before support_fuse_mount, to
+   prepare unprivileged mounting.  */
+void support_fuse_init (void);
+
+/* This function can be called instead of support_fuse_init.  It does
+   not use mount and user namespaces, so it requires root privileges,
+   and cleanup after testing may be incomplete.  This is intended only
+   for test development.  */
+void support_fuse_init_no_namespace (void);
+
+/* Opaque type for tracking FUSE mount state.  */
+struct support_fuse;
+
+/* This function disables a mount point created using
+   support_fuse_mount.  */
+void support_fuse_unmount (struct support_fuse *) __nonnull ((1));
+
+/* This function is called on a separate thread after calling
+   support_fuse_mount.  F is the mount state, and CLOSURE the argument
+   that was passed to support_fuse_mount.  The callback function is
+   expected to call support_fuse_next to read packets from the kernel
+   and handle them according to the test's need.  */
+typedef void (*support_fuse_callback) (struct support_fuse *f, void *closure);
+
+/* This function creates a new mount point, implemented by CALLBACK.
+   CLOSURE is passed to CALLBACK as the second argument.  */
+struct support_fuse *support_fuse_mount (support_fuse_callback callback,
+                                         void *closure)
+  __nonnull ((1)) __attr_dealloc (support_fuse_unmount, 1);
+
+/* This function returns the path to the mount point for F.  The
+   returned string is valid until support_fuse_unmount (F) is called.  */
+const char * support_fuse_mountpoint (struct support_fuse *f) __nonnull ((1));
+
+
+/* Renders the OPCODE as a string (FUSE_* constant.  The caller must
+   free the returned string.  */
+char * support_fuse_opcode (uint32_t opcode) __attr_dealloc_free;
+
+/* Use to provide a checked cast facility.  Use the
+   support_fuse_in_cast macro below.  */
+void *support_fuse_cast_internal (struct fuse_in_header *, uint32_t)
+  __nonnull ((1));
+void *support_fuse_cast_name_internal (struct fuse_in_header *, uint32_t,
+                                       size_t skip, char **name)
+  __nonnull ((1));
+
+/* The macro expansion support_fuse_in_cast (P, TYPE) casts the
+   pointer INH to the appropriate type corresponding to the FUSE_TYPE
+   opcode.  It fails (terminates the process) if INH->opcode does not
+   match FUSE_TYPE.  The type of the returned pointer matches that of
+   the FUSE_* constant.
+
+   Maintenance note: Adding support for additional struct fuse_*_in
+   types is generally easy, except when there is trailing data after
+   the struct (see below for support_fuse_cast_name, for example), and
+   the kernel has changed struct sizes over time.  This has happened
+   recently with struct fuse_setxattr_in, and would require special
+   handling if implemented.  */
+#define support_fuse_payload_type_INIT struct fuse_init_in
+#define support_fuse_payload_type_LOOKUP char
+#define support_fuse_payload_type_OPEN struct fuse_open_in
+#define support_fuse_payload_type_READ struct fuse_read_in
+#define support_fuse_payload_type_SETATTR struct fuse_setattr_in
+#define support_fuse_payload_type_WRITE struct fuse_write_in
+#define support_fuse_cast(typ, inh)                     \
+  ((support_fuse_payload_type_##typ *)                  \
+   support_fuse_cast_internal ((inh), FUSE_##typ))
+
+/* Same as support_fuse_cast, but also writes the passed name to *NAMEP.  */
+#define support_fuse_payload_name_type_CREATE struct fuse_create_in
+#define support_fuse_payload_name_type_MKDIR struct fuse_mkdir_in
+#define support_fuse_cast_name(typ, inh, namep)                         \
+  ((support_fuse_payload_name_type_##typ *)                             \
+   support_fuse_cast_name_internal                                      \
+   ((inh), FUSE_##typ, sizeof (support_fuse_payload_name_type_##typ),   \
+    (namep)))
+
+/* This function should be called from the callback function.  It
+   returns NULL if the mount point has been unmounted.  The result can
+   be cast using support_fuse_in_cast.  The pointer is invalidated
+   with the next call to support_fuse_next.
+
+   Typical use involves handling some basics using the
+   support_fuse_handle_* building blocks, following by a switch
+   statement on the result member of the returned struct, to implement
+   what a particular test needs.  Casts to payload data should be made
+   using support_fuse_in_cast.
+
+   By default, FUSE_FORGET responses are filtered.  See
+   support_fuse_filter_forget for turning that off.  */
+struct fuse_in_header *support_fuse_next (struct support_fuse *f)
+  __nonnull ((1));
+
+/* This function can be called from a callback function to handle
+   basic aspects of directories (OPENDIR, GETATTR, RELEASEDIR).
+   inh->nodeid is used as the inode number for the directory.  This
+   function must be called after support_fuse_next.  */
+bool support_fuse_handle_directory (struct support_fuse *f) __nonnull ((1));
+
+/* This function can be called from a callback function to handle
+   access to the mount point itself, after call support_fuse_next.  */
+bool support_fuse_handle_mountpoint (struct support_fuse *f) __nonnull ((1));
+
+/* If FILTER_ENABLED, future support_fuse_next calls will not return
+   FUSE_FORGET events (and simply discared them, as they require no
+   reply).  If !FILTER_ENABLED, the callback needs to handle
+   FUSE_FORGET events and call support_fuse_no_reply.  */
+void support_fuse_filter_forget (struct support_fuse *f, bool filter_enabled)
+  __nonnull ((1));
+
+/* This function should be called from the callback function after
+   support_fuse_next returned a non-null pointer.  It sends out a
+   response packet on the FUSE device with the supplied payload data.  */
+void support_fuse_reply (struct support_fuse *f,
+                         const void *payload, size_t payload_size)
+  __nonnull ((1)) __attr_access ((__read_only__, 2, 3));
+
+/* This function should be called from the callback function.  It
+   replies to a request with an error indicator.  ERROR must be positive.  */
+void support_fuse_reply_error (struct support_fuse *f, uint32_t error)
+    __nonnull ((1));
+
+/* This function should be called from the callback function.  It
+   sends out an empty (but success-indicating) reply packet.  */
+void support_fuse_reply_empty (struct support_fuse *f) __nonnull ((1));
+
+/* Do not send a reply.  Only to be used after a support_fuse_next
+   call that returned a FUSE_FORGET event.  */
+void support_fuse_no_reply (struct support_fuse *f) __nonnull ((1));
+
+/* Specific reponse preparation functions.  The returned object can be
+   updated as needed.  If a NODEID argument is present, it will be
+   used to set the inode and FUSE nodeid fields.  Without such an
+   argument, it is initialized from the current request (if the reply
+   requires this field).  This function must be called after
+   support_fuse_next.  The actual response must be sent using
+   support_fuse_reply_prepared (or a support_fuse_reply_error call can
+   be used to cancel the response).  */
+struct fuse_entry_out *support_fuse_prepare_entry (struct support_fuse *f,
+                                                   uint64_t nodeid)
+  __nonnull ((1));
+struct fuse_attr_out *support_fuse_prepare_attr (struct support_fuse *f)
+  __nonnull ((1));
+
+/* Similar to the other support_fuse_prepare_* functions, but it
+   prepares for two response packets.  They can be updated through the
+   pointers written to *OUT_ENTRY and *OUT_OPEN prior to calling
+   support_fuse_reply_prepared.  */
+void support_fuse_prepare_create (struct support_fuse *f,
+                                  uint64_t nodeid,
+                                  struct fuse_entry_out **out_entry,
+                                  struct fuse_open_out **out_open)
+  __nonnull ((1, 3, 4));
+
+
+/* Prepare sending a directory stream.  Must be called after
+   support_fuse_next and before support_fuse_dirstream_add.    */
+struct support_fuse_dirstream;
+struct support_fuse_dirstream *support_fuse_prepare_readdir (struct
+                                                             support_fuse *f);
+
+/* Adds directory using D_INO, D_OFF, D_TYPE, D_NAME to the directory
+   stream D.  Must be called after support_fuse_prepare_readdir.
+
+   D_OFF is the offset of the next directory entry, not the current
+   one.  The first entry has offset zero.  The first requested offset
+   can be obtained from the READ payload (struct fuse_read_in) prior
+   to calling this function.
+
+   Returns true if the entry could be added to the buffer, or false if
+   there was insufficient room.  Sending the buffer is delayed until
+   support_fuse_reply_prepared is called.  */
+bool support_fuse_dirstream_add (struct support_fuse_dirstream *d,
+                                 uint64_t d_ino, uint64_t d_off,
+                                 uint32_t d_type,
+                                 const char *d_name);
+
+/* Send a prepared response.  Must be called after one of the
+   support_fuse_prepare_* functions and before the next
+   support_fuse_next call.  */
+void support_fuse_reply_prepared (struct support_fuse *f) __nonnull ((1));
+
+#endif /* SUPPORT_FUSE_H */
diff --git a/support/process_state.h b/support/process_state.h
index 1cf902e91b..9541d8c343 100644
--- a/support/process_state.h
+++ b/support/process_state.h
@@ -31,13 +31,16 @@ enum support_process_state
   support_process_state_dead         = 0x20,  /* X (dead).  */
   support_process_state_zombie       = 0x40,  /* Z (zombie).  */
   support_process_state_parked       = 0x80,  /* P (parked).  */
+  support_process_state_invalid      = 0x100  /* Invalid state.  */
 };
 
 /* Wait for process PID to reach state STATE.  It can be a combination of
    multiple possible states ('process_state_running | process_state_sleeping')
    where the function return when any of these state are observed.
    For an invalid state not represented by SUPPORT_PROCESS_STATE, it fallbacks
-   to a 2 second sleep.  */
-void support_process_state_wait (pid_t pid, enum support_process_state state);
+   to a 2 second sleep.
+   Return the found process state.  */
+enum support_process_state
+support_process_state_wait (pid_t pid, enum support_process_state state);
 
 #endif
diff --git a/support/readdir.h b/support/readdir.h
new file mode 100644
index 0000000000..7d7c7650d4
--- /dev/null
+++ b/support/readdir.h
@@ -0,0 +1,85 @@
+/* Type-generic wrapper for readdir functions.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef SUPPORT_READDIR_H
+#define SUPPORT_READDIR_H
+
+#include <dirent.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/* Definition independent of _FILE_OFFSET_BITS.  */
+struct support_dirent
+{
+  uint64_t d_ino;
+  uint64_t d_off;               /* 0 if d_off is not supported.  */
+  uint32_t d_type;
+  char *d_name;
+};
+
+/* Operation to be performed by support_readdir below.  */
+enum support_readdir_op
+  {
+    SUPPORT_READDIR,
+    SUPPORT_READDIR64,
+    SUPPORT_READDIR_R,
+    SUPPORT_READDIR64_R,
+    SUPPORT_READDIR64_COMPAT,
+    SUPPORT_READDIR64_R_COMPAT,
+  };
+
+/* Returns the last supported function.  May exclude
+   SUPPORT_READDIR64_R_COMPAT if not implemented.  */
+enum support_readdir_op support_readdir_op_last (void);
+
+/* Returns the name of the function that corresponds to the OP constant.  */
+const char *support_readdir_function (enum support_readdir_op op);
+
+/* Returns the d_ino field width for OP, in bits.  */
+unsigned int support_readdir_inode_width (enum support_readdir_op op);
+
+/* Returns the d_off field width for OP, in bits.  Zero if not present.  */
+unsigned int support_readdir_offset_width (enum support_readdir_op op);
+
+/* Returns true if OP is an _r variant with name length restrictions.  */
+bool support_readdir_r_variant (enum support_readdir_op op);
+
+/* First, free E->d_name and set the field to NULL.  Then call the
+   readdir variant as specified by OP.  If successfully, copy fields
+   to E, make a copy of the entry name using strdup, and write its
+   addres sto E->d_name.
+
+   Return true if an entry was read, or false if the end of the
+   directory stream was reached.  Terminates the process upon error.
+   The caller is expected to free E->d_name if the function is not
+   called again for this E.
+
+   Note that this function assumes that E->d_name has been initialized
+   to NULL or has been allocated by a previous call to this function.  */
+bool support_readdir (DIR *stream, enum support_readdir_op op,
+                      struct support_dirent *e) __nonnull ((1, 3));
+
+/* Checks that the readdir operation OP fails with errno value EXPECTED.  */
+void support_readdir_expect_error (DIR *stream, enum support_readdir_op op,
+                                   int expected) __nonnull ((1));
+
+__END_DECLS
+
+#endif /* SUPPORT_READDIR_H */
diff --git a/support/xlstat.c b/support/support_check_stat_fd.c
index 87df988879..4c12adf6b7 100644
--- a/support/xlstat.c
+++ b/support/support_check_stat_fd.c
@@ -1,5 +1,5 @@
-/* lstat64 with error checking.
-   Copyright (C) 2017-2024 Free Software Foundation, Inc.
+/* Error checking for descriptor-based stat functions.
+   Copyright (C) 2024 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -18,11 +18,10 @@
 
 #include <support/check.h>
 #include <support/xunistd.h>
-#include <sys/stat.h>
 
 void
-xlstat (const char *path, struct stat64 *result)
+support_check_stat_fd (const char *name, int fd, int result)
 {
-  if (lstat64 (path, result) != 0)
-    FAIL_EXIT1 ("lstat64 (\"%s\"): %m", path);
+  if (result != 0)
+    FAIL_EXIT1 ("%s (%d): %m", name, fd);
 }
diff --git a/support/support-xfstat.c b/support/support_check_stat_path.c
index ab4b01c97d..3cf72afe59 100644
--- a/support/support-xfstat.c
+++ b/support/support_check_stat_path.c
@@ -1,4 +1,4 @@
-/* fstat64 with error checking.
+/* Error checking for path-based stat functions.
    Copyright (C) 2017-2024 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
@@ -18,11 +18,10 @@
 
 #include <support/check.h>
 #include <support/xunistd.h>
-#include <sys/stat.h>
 
 void
-xfstat (int fd, struct stat64 *result)
+support_check_stat_path (const char *name, const char *path, int result)
 {
-  if (fstat64 (fd, result) != 0)
-    FAIL_EXIT1 ("fstat64 (%d): %m", fd);
+  if (result != 0)
+    FAIL_EXIT1 ("%s (\"%s\"): %m", name, path);
 }
diff --git a/support/support_compare_file_bytes.c b/support/support_compare_file_bytes.c
new file mode 100644
index 0000000000..e261e1da8f
--- /dev/null
+++ b/support/support_compare_file_bytes.c
@@ -0,0 +1,42 @@
+/* Compare bytes from an open file.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+
+#include <support/file_contents.h>
+
+/* Check that an already-open file has exactly the given bytes,
+   starting at the current offset.  */
+
+int
+support_compare_file_bytes (FILE *fp, const char *contents, size_t length)
+{
+  int c;
+  while (length > 0)
+    {
+      c = getc (fp);
+      if (c == EOF || (unsigned char) c != (unsigned char) contents[0])
+	return 1;
+      contents++;
+      length--;
+    }
+  c = getc (fp);
+  if (c != EOF || ferror (fp))
+    return 1;
+  return 0;
+}
diff --git a/support/support_compare_file_string.c b/support/support_compare_file_string.c
new file mode 100644
index 0000000000..04513c3af1
--- /dev/null
+++ b/support/support_compare_file_string.c
@@ -0,0 +1,28 @@
+/* Compare string from an open file.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <support/file_contents.h>
+
+int
+support_compare_file_string (FILE *fp, const char *contents)
+{
+  return support_compare_file_bytes (fp, contents, strlen (contents));
+}
diff --git a/support/support_format_addrinfo.c b/support/support_format_addrinfo.c
index cbc72910a9..77f4db345c 100644
--- a/support/support_format_addrinfo.c
+++ b/support/support_format_addrinfo.c
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <support/support.h>
 #include <support/xmemstream.h>
 
diff --git a/support/support_fuse.c b/support/support_fuse.c
new file mode 100644
index 0000000000..f6c063b549
--- /dev/null
+++ b/support/support_fuse.c
@@ -0,0 +1,706 @@
+/* Facilities for FUSE-backed file system tests.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/fuse.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/sysmacros.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <array_length.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+#include <support/xdirent.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+
+#ifdef __linux__
+# include <sys/mount.h>
+#else
+/* Fallback definitions that mark the test as unsupported.  */
+# define mount(...) ({ FAIL_UNSUPPORTED ("mount"); -1; })
+# define umount(...) ({ FAIL_UNSUPPORTED ("mount"); -1; })
+#endif
+
+struct support_fuse
+{
+  char *mountpoint;
+  void *buffer_start;           /* Begin of allocation.  */
+  void *buffer_next;            /* Next read position.  */
+  void *buffer_limit;           /* End of buffered data.  */
+  void *buffer_end;             /* End of allocation.  */
+  struct fuse_in_header *inh;   /* Most recent request (support_fuse_next).  */
+  union                         /* Space for prepared responses.  */
+  {
+    struct fuse_attr_out attr;
+    struct fuse_entry_out entry;
+    struct
+    {
+      struct fuse_entry_out entry;
+      struct fuse_open_out open;
+    } create;
+  } prepared;
+  void *prepared_pointer;       /* NULL if inactive.  */
+  size_t prepared_size;         /* 0 if inactive.  */
+
+  /* Used for preparing readdir responses.  Already used-up area for
+     the current request is counted by prepared_size.  */
+  void *readdir_buffer;
+  size_t readdir_buffer_size;
+
+  pthread_t handler;            /* Thread handling requests.  */
+  uid_t uid;                    /* Cached value for the current process.  */
+  uid_t gid;                    /* Cached value for the current process.  */
+  int fd;                       /* FUSE file descriptor.  */
+  int connection;               /* Entry under /sys/fs/fuse/connections.  */
+  bool filter_forget;           /* Controls FUSE_FORGET event dropping.  */
+  _Atomic bool disconnected;
+};
+
+struct fuse_thread_wrapper_args
+{
+  struct support_fuse *f;
+  support_fuse_callback callback;
+  void *closure;
+};
+
+/* Set by support_fuse_init to indicate that support_fuse_mount may be
+   called.  */
+static bool support_fuse_init_called;
+
+/* Allocate the read buffer in F with SIZE bytes capacity.  Does not
+   free the previously allocated buffer.  */
+static void support_fuse_allocate (struct support_fuse *f, size_t size)
+  __nonnull ((1));
+
+/* Internal mkdtemp replacement */
+static char * support_fuse_mkdir (const char *prefix) __nonnull ((1));
+
+/* Low-level allocation function for support_fuse_mount.  Does not
+   perform the mount.  */
+static struct support_fuse *support_fuse_open (void);
+
+/* Thread wrapper function for use with pthread_create.  Uses struct
+   fuse_thread_wrapper_args.  */
+static void *support_fuse_thread_wrapper (void *closure)  __nonnull ((1));
+
+/* Initial step before preparing a reply.  SIZE must be the size of
+   the F->prepared member that is going to be used.  */
+static void support_fuse_prepare_1 (struct support_fuse *f, size_t size);
+
+/* Similar to support_fuse_reply_error, but not check that ERROR is
+   not zero.  */
+static void support_fuse_reply_error_1 (struct support_fuse *f,
+                                        uint32_t error) __nonnull ((1));
+
+/* Path to the directory containing mount points.  Initialized by an
+   ELF constructor.  All mountpoints are collected there so that the
+   test wrapper can clean them up without keeping track of them
+   individually.  */
+static char *support_fuse_mountpoints;
+
+/* PID of the process that should clean up the mount points in the ELF
+   destructor.  */
+static pid_t support_fuse_cleanup_pid;
+
+static void
+support_fuse_allocate (struct support_fuse *f, size_t size)
+{
+  f->buffer_start = xmalloc (size);
+  f->buffer_end = f->buffer_start + size;
+  f->buffer_limit = f->buffer_start;
+  f->buffer_next = f->buffer_limit;
+}
+
+void
+support_fuse_filter_forget (struct support_fuse *f, bool filter)
+{
+  f->filter_forget = filter;
+}
+
+void *
+support_fuse_cast_internal (struct fuse_in_header *p, uint32_t expected)
+{
+  if (expected != p->opcode
+      && !(expected == FUSE_READ && p->opcode == FUSE_READDIR))
+    {
+      char *expected1 = support_fuse_opcode (expected);
+      char *actual = support_fuse_opcode (p->opcode);
+      FAIL_EXIT1 ("attempt to cast %s to %s", actual, expected1);
+    }
+  return p + 1;
+}
+
+void *
+support_fuse_cast_name_internal (struct fuse_in_header *p, uint32_t expected,
+                                 size_t skip, char **name)
+{
+  char *result = support_fuse_cast_internal (p, expected);
+  *name = result + skip;
+  return result;
+}
+
+bool
+support_fuse_dirstream_add (struct support_fuse_dirstream *d,
+                            uint64_t d_ino, uint64_t d_off,
+                            uint32_t d_type, const char *d_name)
+{
+  struct support_fuse *f = (struct support_fuse *) d;
+  size_t structlen = offsetof (struct fuse_dirent, name);
+  size_t namelen = strlen (d_name); /* No null termination.  */
+  size_t required_size = FUSE_DIRENT_ALIGN (structlen + namelen);
+  if (f->readdir_buffer_size - f->prepared_size < required_size)
+    return false;
+  struct fuse_dirent entry =
+    {
+      .ino = d_ino,
+      .off = d_off,
+      .type = d_type,
+      .namelen = namelen,
+    };
+  memcpy (f->readdir_buffer + f->prepared_size, &entry, structlen);
+  /* Use strncpy to write padding and avoid passing uninitialized
+     bytes to the read system call.  */
+  strncpy (f->readdir_buffer + f->prepared_size + structlen, d_name,
+           required_size - structlen);
+  f->prepared_size += required_size;
+  return true;
+}
+
+bool
+support_fuse_handle_directory (struct support_fuse *f)
+{
+  TEST_VERIFY (f->inh != NULL);
+  switch (f->inh->opcode)
+    {
+    case FUSE_OPENDIR:
+      {
+        struct fuse_open_out out =
+          {
+          };
+        support_fuse_reply (f, &out, sizeof (out));
+      }
+      return true;
+    case FUSE_RELEASEDIR:
+      support_fuse_reply_empty (f);
+      return true;
+    case FUSE_GETATTR:
+      {
+        struct fuse_attr_out *out = support_fuse_prepare_attr (f);
+        out->attr.mode = S_IFDIR | 0700;
+        support_fuse_reply_prepared (f);
+      }
+      return true;
+    default:
+      return false;
+    }
+}
+
+bool
+support_fuse_handle_mountpoint (struct support_fuse *f)
+{
+  TEST_VERIFY (f->inh != NULL);
+  /* 1 is the root node.  */
+  if (f->inh->opcode == FUSE_GETATTR && f->inh->nodeid == 1)
+    return support_fuse_handle_directory (f);
+  return false;
+}
+
+void
+support_fuse_init (void)
+{
+  support_fuse_init_called = true;
+
+  support_become_root ();
+  if (!support_enter_mount_namespace ())
+    FAIL_UNSUPPORTED ("mount namespaces not supported");
+}
+
+void
+support_fuse_init_no_namespace (void)
+{
+  support_fuse_init_called = true;
+}
+
+static char *
+support_fuse_mkdir (const char *prefix)
+{
+  /* Do not use mkdtemp to avoid interfering with its tests.  */
+  unsigned int counter = 1;
+  unsigned int pid = getpid ();
+  while (true)
+    {
+      char *path = xasprintf ("%s%u.%u/", prefix, pid, counter);
+      if (mkdir (path, 0700) == 0)
+        return path;
+      if (errno != EEXIST)
+        FAIL_EXIT1 ("mkdir (\"%s\"): %m", path);
+      free (path);
+      ++counter;
+    }
+}
+
+struct support_fuse *
+support_fuse_mount (support_fuse_callback callback, void *closure)
+{
+  TEST_VERIFY_EXIT (support_fuse_init_called);
+
+  /* Request at least minor version 12 because it changed struct sizes.  */
+  enum { min_version = 12 };
+
+  struct support_fuse *f = support_fuse_open ();
+  char *mount_options
+    = xasprintf ("fd=%d,rootmode=040700,user_id=%u,group_id=%u",
+                 f->fd, f->uid, f->gid);
+  if (mount ("fuse", f->mountpoint, "fuse.glibc",
+             MS_NOSUID|MS_NODEV, mount_options)
+      != 0)
+    FAIL_EXIT1 ("FUSE mount on %s: %m", f->mountpoint);
+  free (mount_options);
+
+  /* Retry with an older FUSE version.  */
+  while (true)
+    {
+      struct fuse_in_header *inh = support_fuse_next (f);
+      struct fuse_init_in *init_in = support_fuse_cast (INIT, inh);
+      if (init_in->major < 7
+          || (init_in->major == 7 && init_in->minor < min_version))
+        FAIL_UNSUPPORTED ("kernel FUSE version is %u.%u, too old",
+                          init_in->major, init_in->minor);
+      if (init_in->major > 7)
+        {
+          uint32_t major = 7;
+          support_fuse_reply (f, &major, sizeof (major));
+          continue;
+        }
+      TEST_VERIFY (init_in->flags & FUSE_DONT_MASK);
+      struct fuse_init_out out =
+        {
+          .major = 7,
+          .minor = min_version,
+          /* Request that the kernel does not apply umask.  */
+          .flags = FUSE_DONT_MASK,
+        };
+      support_fuse_reply (f, &out, sizeof (out));
+
+      {
+        struct fuse_thread_wrapper_args args =
+          {
+            .f = f,
+            .callback = callback,
+            .closure = closure,
+          };
+        f->handler = xpthread_create (NULL,
+                                      support_fuse_thread_wrapper, &args);
+        struct stat64 st;
+        xstat64 (f->mountpoint, &st);
+        f->connection = minor (st.st_dev);
+        /* Got a reply from the thread,  safe to deallocate args.  */
+      }
+
+      return f;
+    }
+}
+
+const char *
+support_fuse_mountpoint (struct support_fuse *f)
+{
+  return f->mountpoint;
+}
+
+void
+support_fuse_no_reply (struct support_fuse *f)
+{
+  TEST_VERIFY (f->inh != NULL);
+  TEST_COMPARE (f->inh->opcode, FUSE_FORGET);
+  f->inh = NULL;
+}
+
+char *
+support_fuse_opcode (uint32_t op)
+{
+  const char *result;
+  switch (op)
+    {
+#define X(n) case n: result = #n; break
+      X(FUSE_LOOKUP);
+      X(FUSE_FORGET);
+      X(FUSE_GETATTR);
+      X(FUSE_SETATTR);
+      X(FUSE_READLINK);
+      X(FUSE_SYMLINK);
+      X(FUSE_MKNOD);
+      X(FUSE_MKDIR);
+      X(FUSE_UNLINK);
+      X(FUSE_RMDIR);
+      X(FUSE_RENAME);
+      X(FUSE_LINK);
+      X(FUSE_OPEN);
+      X(FUSE_READ);
+      X(FUSE_WRITE);
+      X(FUSE_STATFS);
+      X(FUSE_RELEASE);
+      X(FUSE_FSYNC);
+      X(FUSE_SETXATTR);
+      X(FUSE_GETXATTR);
+      X(FUSE_LISTXATTR);
+      X(FUSE_REMOVEXATTR);
+      X(FUSE_FLUSH);
+      X(FUSE_INIT);
+      X(FUSE_OPENDIR);
+      X(FUSE_READDIR);
+      X(FUSE_RELEASEDIR);
+      X(FUSE_FSYNCDIR);
+      X(FUSE_GETLK);
+      X(FUSE_SETLK);
+      X(FUSE_SETLKW);
+      X(FUSE_ACCESS);
+      X(FUSE_CREATE);
+      X(FUSE_INTERRUPT);
+      X(FUSE_BMAP);
+      X(FUSE_DESTROY);
+      X(FUSE_IOCTL);
+      X(FUSE_POLL);
+      X(FUSE_NOTIFY_REPLY);
+      X(FUSE_BATCH_FORGET);
+      X(FUSE_FALLOCATE);
+      X(FUSE_READDIRPLUS);
+      X(FUSE_RENAME2);
+      X(FUSE_LSEEK);
+      X(FUSE_COPY_FILE_RANGE);
+      X(FUSE_SETUPMAPPING);
+      X(FUSE_REMOVEMAPPING);
+      X(FUSE_SYNCFS);
+      X(FUSE_TMPFILE);
+      X(FUSE_STATX);
+#undef X
+    default:
+      return xasprintf ("FUSE_unknown_%u", op);
+    }
+  return xstrdup (result);
+}
+
+static struct support_fuse *
+support_fuse_open (void)
+{
+  struct support_fuse *result = xmalloc (sizeof (*result));
+  result->mountpoint = support_fuse_mkdir (support_fuse_mountpoints);
+  result->inh = NULL;
+  result->prepared_pointer = NULL;
+  result->prepared_size = 0;
+  result->readdir_buffer = NULL;
+  result->readdir_buffer_size = 0;
+  result->uid = getuid ();
+  result->gid = getgid ();
+  result->fd = open ("/dev/fuse", O_RDWR, 0);
+  if (result->fd < 0)
+    {
+      if (errno == ENOENT || errno == ENODEV || errno == EPERM
+          || errno == EACCES)
+        FAIL_UNSUPPORTED ("cannot open /dev/fuse: %m");
+      else
+        FAIL_EXIT1 ("cannot open /dev/fuse: %m");
+    }
+  result->connection = -1;
+  result->filter_forget = true;
+  result->disconnected = false;
+  support_fuse_allocate (result, FUSE_MIN_READ_BUFFER);
+  return result;
+}
+
+static void
+support_fuse_prepare_1 (struct support_fuse *f, size_t size)
+{
+  TEST_VERIFY (f->prepared_pointer == NULL);
+  f->prepared_size = size;
+  memset (&f->prepared, 0, size);
+  f->prepared_pointer = &f->prepared;
+}
+
+struct fuse_attr_out *
+support_fuse_prepare_attr (struct support_fuse *f)
+{
+  support_fuse_prepare_1 (f, sizeof (f->prepared.attr));
+  f->prepared.attr.attr.uid = f->uid;
+  f->prepared.attr.attr.gid = f->gid;
+  f->prepared.attr.attr.ino = f->inh->nodeid;
+  return &f->prepared.attr;
+}
+
+void
+support_fuse_prepare_create (struct support_fuse *f,
+                             uint64_t nodeid,
+                             struct fuse_entry_out **out_entry,
+                             struct fuse_open_out **out_open)
+{
+  support_fuse_prepare_1 (f, sizeof (f->prepared.create));
+  f->prepared.create.entry.nodeid = nodeid;
+  f->prepared.create.entry.attr.uid = f->uid;
+  f->prepared.create.entry.attr.gid = f->gid;
+  f->prepared.create.entry.attr.ino = nodeid;
+  *out_entry = &f->prepared.create.entry;
+  *out_open = &f->prepared.create.open;
+}
+
+struct fuse_entry_out *
+support_fuse_prepare_entry (struct support_fuse *f, uint64_t nodeid)
+{
+  support_fuse_prepare_1 (f, sizeof (f->prepared.entry));
+  f->prepared.entry.nodeid = nodeid;
+  f->prepared.entry.attr.uid = f->uid;
+  f->prepared.entry.attr.gid = f->gid;
+  f->prepared.entry.attr.ino = nodeid;
+  return &f->prepared.entry;
+}
+
+struct support_fuse_dirstream *
+support_fuse_prepare_readdir (struct support_fuse *f)
+{
+  support_fuse_prepare_1 (f, 0);
+  struct fuse_read_in *p = support_fuse_cast (READ, f->inh);
+  if (p->size > f->readdir_buffer_size)
+    {
+      free (f->readdir_buffer);
+      f->readdir_buffer = xmalloc (p->size);
+      f->readdir_buffer_size = p->size;
+    }
+  f->prepared_pointer = f->readdir_buffer;
+  return (struct support_fuse_dirstream *) f;
+}
+
+struct fuse_in_header *
+support_fuse_next (struct support_fuse *f)
+{
+  TEST_VERIFY (f->inh == NULL);
+  while (true)
+    {
+      if (f->buffer_next < f->buffer_limit)
+        {
+          f->inh = f->buffer_next;
+          f->buffer_next = (void *) f->buffer_next + f->inh->len;
+          /* Suppress FUSE_FORGET responses if requested.  */
+          if (f->filter_forget && f->inh->opcode == FUSE_FORGET)
+            {
+              f->inh = NULL;
+              continue;
+            }
+          return f->inh;
+        }
+      ssize_t ret = read (f->fd, f->buffer_start,
+                          f->buffer_end - f->buffer_start);
+      if (ret == 0)
+        FAIL_EXIT (1, "unexpected EOF on FUSE device");
+      if (ret < 0 && errno == EINVAL)
+        {
+          /* Increase buffer size.  */
+          size_t new_size = 2 * (size_t) (f->buffer_end - f->buffer_start);
+          free (f->buffer_start);
+          support_fuse_allocate (f, new_size);
+          continue;
+        }
+      if (ret < 0)
+        {
+          if (f->disconnected)
+            /* Unmount detected.  */
+            return NULL;
+          FAIL_EXIT1 ("read error on FUSE device: %m");
+        }
+      /* Read was successful, make [next, limit) the active buffer area.  */
+      f->buffer_next = f->buffer_start;
+      f->buffer_limit = (void *) f->buffer_start + ret;
+    }
+}
+
+void
+support_fuse_reply (struct support_fuse *f,
+                    const void *payload, size_t payload_size)
+{
+  TEST_VERIFY_EXIT (f->inh != NULL);
+  TEST_VERIFY (f->prepared_pointer == NULL);
+  struct fuse_out_header outh =
+    {
+      .len = sizeof (outh) + payload_size,
+      .unique = f->inh->unique,
+    };
+  struct iovec iov[] =
+    {
+      { &outh, sizeof (outh) },
+      { (void *) payload, payload_size },
+    };
+  ssize_t ret = writev (f->fd, iov, array_length (iov));
+  if (ret < 0)
+    {
+      if (!f->disconnected)
+        /* Some kernels produce write errors upon disconnect.  */
+        FAIL_EXIT1 ("FUSE write failed for %s response"
+                    " (%zu bytes payload): %m",
+                    support_fuse_opcode (f->inh->opcode), payload_size);
+    }
+  else if (ret != sizeof (outh) + payload_size)
+    FAIL_EXIT1 ("FUSE write short for %s response (%zu bytes payload):"
+                " %zd bytes",
+                support_fuse_opcode (f->inh->opcode), payload_size, ret);
+  f->inh = NULL;
+}
+
+void
+support_fuse_reply_empty (struct support_fuse *f)
+{
+  support_fuse_reply_error_1 (f, 0);
+}
+
+static void
+support_fuse_reply_error_1 (struct support_fuse *f, uint32_t error)
+{
+  TEST_VERIFY_EXIT (f->inh != NULL);
+  struct fuse_out_header outh =
+    {
+      .len = sizeof (outh),
+      .error = -error,
+      .unique = f->inh->unique,
+    };
+  ssize_t ret = write (f->fd, &outh, sizeof (outh));
+  if (ret < 0)
+    {
+      /* Some kernels produce write errors upon disconnect.  */
+      if (!f->disconnected)
+        FAIL_EXIT1 ("FUSE write failed for %s error response: %m",
+                    support_fuse_opcode (f->inh->opcode));
+    }
+  else if (ret != sizeof (outh))
+    FAIL_EXIT1 ("FUSE write short for %s error response: %zd bytes",
+                support_fuse_opcode (f->inh->opcode), ret);
+  f->inh = NULL;
+  f->prepared_pointer = NULL;
+  f->prepared_size = 0;
+}
+
+void
+support_fuse_reply_error (struct support_fuse *f, uint32_t error)
+{
+  TEST_VERIFY (error > 0);
+  support_fuse_reply_error_1 (f, error);
+}
+
+void
+support_fuse_reply_prepared (struct support_fuse *f)
+{
+  TEST_VERIFY_EXIT (f->prepared_pointer != NULL);
+  /* Re-use the non-prepared reply function.  It requires
+     f->prepared_* to be non-null, so reset the fields before the call.  */
+  void *prepared_pointer = f->prepared_pointer;
+  size_t prepared_size = f->prepared_size;
+  f->prepared_pointer = NULL;
+  f->prepared_size = 0;
+  support_fuse_reply (f, prepared_pointer, prepared_size);
+}
+
+static void *
+support_fuse_thread_wrapper (void *closure)
+{
+  struct fuse_thread_wrapper_args args
+    = *(struct fuse_thread_wrapper_args *) closure;
+
+  /* Handle the initial stat call.  */
+  struct fuse_in_header *inh = support_fuse_next (args.f);
+  if (inh == NULL || !support_fuse_handle_mountpoint (args.f))
+    {
+      support_fuse_reply_error (args.f, EIO);
+      return NULL;
+    }
+
+  args.callback  (args.f, args.closure);
+  return NULL;
+}
+
+void
+support_fuse_unmount (struct support_fuse *f)
+{
+  /* Signal the unmount to the handler thread.  Some kernels report
+     not just ENODEV errors on read.  */
+  f->disconnected = true;
+
+  {
+    char *path = xasprintf ("/sys/fs/fuse/connections/%d/abort",
+                            f->connection);
+    /* Some kernels do not support these files under /sys.  */
+    int fd = open (path, O_RDWR | O_TRUNC);
+    if (fd >= 0)
+      {
+        TEST_COMPARE (write (fd, "1", 1), 1);
+        xclose (fd);
+      }
+    free (path);
+  }
+  if (umount (f->mountpoint) != 0)
+    FAIL ("FUSE: umount (\"%s\"): %m", f->mountpoint);
+  xpthread_join (f->handler);
+  if (rmdir (f->mountpoint) != 0)
+    FAIL ("FUSE: rmdir (\"%s\"): %m", f->mountpoint);
+  xclose (f->fd);
+  free (f->buffer_start);
+  free (f->mountpoint);
+  free (f->readdir_buffer);
+  free (f);
+}
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  /* The test_dir test driver variable is not yet set at this point.  */
+  const char *tmpdir = getenv ("TMPDIR");
+  if (tmpdir == NULL || tmpdir[0] == '\0')
+    tmpdir = "/tmp";
+
+  char *prefix = xasprintf ("%s/glibc-tst-fuse.", tmpdir);
+  support_fuse_mountpoints = support_fuse_mkdir (prefix);
+  free (prefix);
+  support_fuse_cleanup_pid = getpid ();
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  if (support_fuse_cleanup_pid != getpid ()
+      || support_fuse_mountpoints == NULL)
+    return;
+  DIR *dir = xopendir (support_fuse_mountpoints);
+  while (true)
+    {
+      struct dirent64 *e = readdir64 (dir);
+      if (e == NULL)
+        /* Ignore errors.  */
+        break;
+      if (*e->d_name == '.')
+        /* Skip "." and "..".  No hidden files expected.  */
+        continue;
+      if (unlinkat (dirfd (dir), e->d_name, AT_REMOVEDIR) != 0)
+        break;
+      rewinddir (dir);
+    }
+  xclosedir (dir);
+  rmdir (support_fuse_mountpoints);
+  free (support_fuse_mountpoints);
+  support_fuse_mountpoints = NULL;
+}
diff --git a/support/support-xstat-time64.c b/support/support_open_and_compare_file_bytes.c
index 451948734a..f804ed8e46 100644
--- a/support/support-xstat-time64.c
+++ b/support/support_open_and_compare_file_bytes.c
@@ -1,5 +1,5 @@
-/* 64-bit time_t stat with error checking.
-   Copyright (C) 2021-2024 Free Software Foundation, Inc.
+/* Compare bytes from a file.
+   Copyright (C) 2024 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -16,17 +16,18 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-/* NB: Non-standard file name to avoid sysdeps override for xstat.  */
+#include <support/file_contents.h>
+#include <support/xstdio.h>
 
-#include <support/check.h>
-#include <support/xunistd.h>
-#include <sys/stat.h>
+/* Check that a not-currently-open file has exactly the given
+   bytes.  */
 
-#if __TIMESIZE != 64
-void
-xstat_time64 (const char *path, struct __stat64_t64 *result)
+int
+support_open_and_compare_file_bytes (const char *file, const char *contents,
+				     size_t length)
 {
-  if (__stat64_time64 (path, result) != 0)
-    FAIL_EXIT1 ("__stat64_time64 (\"%s\"): %m", path);
+  FILE *fp = xfopen (file, "r");
+  int ret = support_compare_file_bytes (fp, contents, length);
+  xfclose (fp);
+  return ret;
 }
-#endif
diff --git a/support/support_open_and_compare_file_string.c b/support/support_open_and_compare_file_string.c
new file mode 100644
index 0000000000..2b596d4c88
--- /dev/null
+++ b/support/support_open_and_compare_file_string.c
@@ -0,0 +1,32 @@
+/* Compare string from a file.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <string.h>
+
+#include <support/file_contents.h>
+#include <support/xstdio.h>
+
+/* Check that a not-currently-open file has exactly the given string
+   as contents, starting at the current offset.  */
+
+int
+support_open_and_compare_file_string (const char *file, const char *contents)
+{
+  return support_open_and_compare_file_bytes (file, contents,
+					      strlen (contents));
+}
diff --git a/support/support_process_state.c b/support/support_process_state.c
index 062335234f..ae8e0a531c 100644
--- a/support/support_process_state.c
+++ b/support/support_process_state.c
@@ -27,7 +27,7 @@
 #include <support/xstdio.h>
 #include <support/check.h>
 
-void
+enum support_process_state
 support_process_state_wait (pid_t pid, enum support_process_state state)
 {
 #ifdef __linux__
@@ -75,7 +75,7 @@ support_process_state_wait (pid_t pid, enum support_process_state state)
 	  {
 	    free (line);
 	    xfclose (fstatus);
-	    return;
+	    return process_states[i].s;
 	  }
 
       rewind (fstatus);
@@ -90,4 +90,6 @@ support_process_state_wait (pid_t pid, enum support_process_state state)
   /* Fallback to nanosleep if an invalid state is found.  */
 #endif
   nanosleep (&(struct timespec) { 1, 0 }, NULL);
+
+  return support_process_state_invalid;
 }
diff --git a/support/support_readdir.c b/support/support_readdir.c
new file mode 100644
index 0000000000..10d808416f
--- /dev/null
+++ b/support/support_readdir.c
@@ -0,0 +1,318 @@
+/* Type-generic wrapper for readdir functions.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/readdir.h>
+
+#include <dlfcn.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xdirent.h>
+
+/* Copied from <olddirent.h>.  */
+struct __old_dirent64
+  {
+    __ino_t d_ino;
+    __off64_t d_off;
+    unsigned short int d_reclen;
+    unsigned char d_type;
+    char d_name[256];
+  };
+
+static struct __old_dirent64 *(*readdir64_compat) (DIR *);
+static int (*readdir64_r_compat) (DIR *, struct __old_dirent64 *,
+                                  struct __old_dirent64 **);
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  /* These compat symbols exists on alpha, i386, m67k , powerpc, s390,
+     sparc. at the same GLIBC_2.1 version. */
+  readdir64_compat = dlvsym (RTLD_DEFAULT, "readdir64", "GLIBC_2.1");
+  readdir64_r_compat = dlvsym (RTLD_DEFAULT, "readdir64_r", "GLIBC_2.1");
+}
+
+enum support_readdir_op
+support_readdir_op_last (void)
+{
+  if (readdir64_r_compat != NULL)
+    {
+      TEST_VERIFY (readdir64_compat != NULL);
+      return SUPPORT_READDIR64_R_COMPAT;
+    }
+  else
+    return SUPPORT_READDIR64_R;
+}
+
+const char *
+support_readdir_function (enum support_readdir_op op)
+{
+  switch (op)
+    {
+      case SUPPORT_READDIR:
+        return "readdir";
+      case SUPPORT_READDIR64:
+        return "readdir64";
+      case SUPPORT_READDIR_R:
+        return "readdir_r";
+      case SUPPORT_READDIR64_R:
+        return "readdir64_r";
+      case SUPPORT_READDIR64_COMPAT:
+        return "readdir64@GBLIC_2.1";
+      case SUPPORT_READDIR64_R_COMPAT:
+        return "readdir64_r@GBLIC_2.1";
+    }
+  FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+unsigned int
+support_readdir_inode_width (enum support_readdir_op op)
+{
+  switch (op)
+    {
+      case SUPPORT_READDIR:
+      case SUPPORT_READDIR_R:
+        return sizeof ((struct dirent) { 0, }.d_ino) * 8;
+      case SUPPORT_READDIR64:
+      case SUPPORT_READDIR64_R:
+        return sizeof ((struct dirent64) { 0, }.d_ino) * 8;
+      case SUPPORT_READDIR64_COMPAT:
+      case SUPPORT_READDIR64_R_COMPAT:
+        return sizeof ((struct __old_dirent64) { 0, }.d_ino) * 8;
+    }
+  FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+unsigned int
+support_readdir_offset_width (enum support_readdir_op op)
+{
+#ifdef _DIRENT_HAVE_D_OFF
+  switch (op)
+    {
+    case SUPPORT_READDIR:
+    case SUPPORT_READDIR_R:
+      return sizeof ((struct dirent) { 0, }.d_off) * 8;
+    case SUPPORT_READDIR64:
+    case SUPPORT_READDIR64_R:
+      return sizeof ((struct dirent64) { 0, }.d_off) * 8;
+    case SUPPORT_READDIR64_COMPAT:
+    case SUPPORT_READDIR64_R_COMPAT:
+      return sizeof ((struct __old_dirent64) { 0, }.d_off) * 8;
+    }
+#else
+  switch (op)
+    {
+    case SUPPORT_READDIR:
+    case SUPPORT_READDIR_R:
+    case SUPPORT_READDIR64:
+    case SUPPORT_READDIR64_R:
+    case SUPPORT_READDIR64_COMPAT:
+    case SUPPORT_READDIR64_R_COMPAT:
+      return 0;
+    }
+#endif
+  FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+bool
+support_readdir_r_variant (enum support_readdir_op op)
+{
+  switch (op)
+    {
+      case SUPPORT_READDIR:
+      case SUPPORT_READDIR64:
+      case SUPPORT_READDIR64_COMPAT:
+        return false;
+      case SUPPORT_READDIR_R:
+      case SUPPORT_READDIR64_R:
+      case SUPPORT_READDIR64_R_COMPAT:
+        return true;
+    }
+  FAIL_EXIT1 ("invalid support_readdir_op constant: %d", op);
+}
+
+static bool
+copy_dirent (struct support_dirent *dst, struct dirent *src)
+{
+  if (src == NULL)
+    return false;
+  dst->d_ino = src->d_ino;
+#ifdef _DIRENT_HAVE_D_OFF
+  dst->d_off = src->d_off;
+#else
+  dst->d_off = 0;
+#endif
+  dst->d_type = src->d_type;
+  dst->d_name = xstrdup (src->d_name);
+  return true;
+}
+
+static bool
+copy_dirent64 (struct support_dirent *dst, struct dirent64 *src)
+{
+  if (src == NULL)
+    return false;
+  dst->d_ino = src->d_ino;
+#ifdef _DIRENT_HAVE_D_OFF
+  dst->d_off = src->d_off;
+#else
+  dst->d_off = 0;
+#endif
+  dst->d_type = src->d_type;
+  dst->d_name = xstrdup (src->d_name);
+  return true;
+}
+
+static bool
+copy_old_dirent64 (struct support_dirent *dst, struct __old_dirent64 *src)
+{
+  if (src == NULL)
+    return false;
+  dst->d_ino = src->d_ino;
+#ifdef _DIRENT_HAVE_D_OFF
+  dst->d_off = src->d_off;
+#else
+  dst->d_off = 0;
+#endif
+  dst->d_type = src->d_type;
+  dst->d_name = xstrdup (src->d_name);
+  return true;
+}
+
+bool
+support_readdir (DIR *stream, enum support_readdir_op op,
+                 struct support_dirent *e)
+{
+  free (e->d_name);
+  e->d_name = NULL;
+  switch (op)
+    {
+    case SUPPORT_READDIR:
+      return copy_dirent (e, xreaddir (stream));
+    case SUPPORT_READDIR64:
+      return copy_dirent64 (e, xreaddir64 (stream));
+
+      /* The functions readdir_r, readdir64_r were deprecated in glibc 2.24.  */
+      DIAG_PUSH_NEEDS_COMMENT;
+      DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+
+    case SUPPORT_READDIR_R:
+      {
+        struct dirent buf;
+        if (!xreaddir_r (stream, &buf))
+          return false;
+        return copy_dirent (e, &buf);
+      }
+    case SUPPORT_READDIR64_R:
+      {
+        struct dirent64 buf;
+        if (!xreaddir64_r (stream, &buf))
+          return false;
+        return copy_dirent64 (e, &buf);
+      }
+
+      DIAG_POP_NEEDS_COMMENT;
+
+    case SUPPORT_READDIR64_COMPAT:
+      if (readdir64_compat == NULL)
+        FAIL_EXIT1 ("readdir64 compat function not implemented");
+      return copy_old_dirent64 (e, readdir64_compat (stream));
+
+    case SUPPORT_READDIR64_R_COMPAT:
+      {
+        if (readdir64_r_compat == NULL)
+          FAIL_EXIT1 ("readdir64_r compat function not implemented");
+        struct __old_dirent64 buf;
+        struct __old_dirent64 *e1;
+        int ret = readdir64_r_compat (stream, &buf, &e1);
+        if (ret != 0)
+          {
+            errno = ret;
+            FAIL ("readdir64_r@GLIBC_2.1: %m");
+            return false;
+          }
+        if (e1 == NULL)
+          return false;
+        return copy_old_dirent64 (e, e1);
+      }
+    }
+  FAIL_EXIT1 ("support_readdir: invalid op argument %d", (int) op);
+}
+
+void
+support_readdir_expect_error (DIR *stream, enum support_readdir_op op,
+                              int expected)
+{
+  switch (op)
+    {
+    case SUPPORT_READDIR:
+      errno = 0;
+      TEST_VERIFY (readdir (stream) == NULL);
+      TEST_COMPARE (errno, expected);
+      return;
+    case SUPPORT_READDIR64:
+      errno = 0;
+      TEST_VERIFY (readdir64 (stream) == NULL);
+      TEST_COMPARE (errno, expected);
+      return;
+
+      /* The functions readdir_r, readdir64_r were deprecated in glibc 2.24.  */
+      DIAG_PUSH_NEEDS_COMMENT;
+      DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+
+    case SUPPORT_READDIR_R:
+      {
+        struct dirent buf;
+        struct dirent *e;
+        errno = readdir_r (stream, &buf, &e);
+        TEST_COMPARE (errno, expected);;
+      }
+      return;
+    case SUPPORT_READDIR64_R:
+      {
+        struct dirent64 buf;
+        struct dirent64 *e;
+        errno = readdir64_r (stream, &buf, &e);
+        TEST_COMPARE (errno, expected);;
+      }
+      return;
+
+      DIAG_POP_NEEDS_COMMENT;
+
+    case SUPPORT_READDIR64_COMPAT:
+      if (readdir64_compat == NULL)
+        FAIL_EXIT1 ("readdir64_r compat function not implemented");
+      errno = 0;
+      TEST_VERIFY (readdir64_compat (stream) == NULL);
+      TEST_COMPARE (errno, expected);
+      return;
+    case SUPPORT_READDIR64_R_COMPAT:
+      {
+        if (readdir64_r_compat == NULL)
+          FAIL_EXIT1 ("readdir64_r compat function not implemented");
+        struct __old_dirent64 buf;
+        struct __old_dirent64 *e;
+        errno = readdir64_r_compat (stream, &buf, &e);
+        TEST_COMPARE (errno, expected);
+      }
+      return;
+    }
+  FAIL_EXIT1 ("support_readdir_expect_error: invalid op argument %d",
+              (int) op);
+}
diff --git a/support/support_readdir_check.c b/support/support_readdir_check.c
new file mode 100644
index 0000000000..5687004276
--- /dev/null
+++ b/support/support_readdir_check.c
@@ -0,0 +1,30 @@
+/* Error-checking helper for xreaddir, xreaddir64.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/xdirent.h>
+
+#include <support/check.h>
+
+void *
+support_readdir_check (const char *name, void *result, int saved_errno)
+{
+  if (result == NULL && errno != 0)
+    FAIL_EXIT1 ("%s: %m", name);
+  errno = saved_errno;
+  return result;
+}
diff --git a/support/xlstat-time64.c b/support/support_readdir_r_check.c
index 2bc3ca6593..6bbb0d0b32 100644
--- a/support/xlstat-time64.c
+++ b/support/support_readdir_r_check.c
@@ -1,5 +1,5 @@
-/* 64-bit time_t stat with error checking.
-   Copyright (C) 2021-2024 Free Software Foundation, Inc.
+/* Error-checking helper for xreaddir_r, xreaddir64_r.
+   Copyright (C) 2024 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -16,17 +16,20 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-/* NB: Non-standard file name to avoid sysdeps override for xstat.  */
+#include <support/xdirent.h>
 
 #include <support/check.h>
-#include <support/xunistd.h>
-#include <sys/stat.h>
 
-#if __TIMESIZE != 64
-void
-xlstat_time64 (const char *path, struct __stat64_t64 *result)
+int
+support_readdir_r_check (const char *name, int result, void *buf, void *ptr)
 {
-  if (__lstat64_time64 (path, result) != 0)
-    FAIL_EXIT1 ("__lstat64_time64 (\"%s\"): %m", path);
+  if (result != 0)
+    {
+      errno = result;
+      FAIL_EXIT1 ("%s: %m", name);
+    }
+  if (buf != ptr)
+    FAIL_EXIT1 ("%s: buffer pointer and returned pointer differ: %p != %p",
+                name, buf, ptr);
+  return result;
 }
-#endif
diff --git a/support/support_test_compare_failure.c b/support/support_test_compare_failure.c
index ae73d200cd..dba79e413f 100644
--- a/support/support_test_compare_failure.c
+++ b/support/support_test_compare_failure.c
@@ -17,7 +17,9 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <errno.h>
+#include <limits.h>
 #include <stdio.h>
+#include <string.h>
 #include <support/check.h>
 
 static void
@@ -31,7 +33,14 @@ report (const char *which, const char *expr, long long value, int positive,
     printf ("%lld", value);
   unsigned long long mask
     = (~0ULL) >> (8 * (sizeof (unsigned long long) - size));
-  printf (" (0x%llx); from: %s\n", (unsigned long long) value & mask, expr);
+  const char *errno_constant = NULL;
+  if (strcmp (expr, "errno") == 0
+      && positive && (unsigned long long int) value <= INT_MAX)
+    errno_constant = strerrorname_np (value);
+  printf (" (0x%llx", (unsigned long long) value & mask);
+  if (errno_constant != NULL)
+    printf (", %s", errno_constant);
+  printf ("); from: %s\n", expr);
 }
 
 void
diff --git a/support/test-driver.c b/support/test-driver.c
index f4c3e4d666..04ceebc08f 100644
--- a/support/test-driver.c
+++ b/support/test-driver.c
@@ -155,6 +155,7 @@ main (int argc, char **argv)
     {
       CMDLINE_OPTIONS
       TEST_DEFAULT_OPTIONS
+      { 0, }
     };
   test_config.options = &options;
 #endif
diff --git a/support/timespec-add.c b/support/timespec-add.c
index 55fd812f31..57b968c316 100644
--- a/support/timespec-add.c
+++ b/support/timespec-add.c
@@ -20,7 +20,6 @@
 /* Return the sum of two timespec values A and B.  On overflow, return
    an extremal value.  This assumes 0 <= tv_nsec < TIMESPEC_HZ.  */
 
-#include <config.h>
 #include "timespec.h"
 
 #include "intprops.h"
diff --git a/support/timespec-sub.c b/support/timespec-sub.c
index 7d89c1415a..2897343fce 100644
--- a/support/timespec-sub.c
+++ b/support/timespec-sub.c
@@ -21,7 +21,6 @@
    overflow, return an extremal value.  This assumes 0 <= tv_nsec <
    TIMESPEC_HZ.  */
 
-#include <config.h>
 #include "timespec.h"
 
 #include "intprops.h"
diff --git a/support/tst-support-process_state.c b/support/tst-support-process_state.c
index d73269320f..4a88eae3a7 100644
--- a/support/tst-support-process_state.c
+++ b/support/tst-support-process_state.c
@@ -68,28 +68,39 @@ do_test (void)
   if (test_verbose)
     printf ("info: waiting pid %d, state_stopped/state_tracing_stop\n",
 	    (int) pid);
-  support_process_state_wait (pid, stop_state);
+  {
+    enum support_process_state state =
+      support_process_state_wait (pid, stop_state);
+    TEST_VERIFY (state == support_process_state_stopped
+		 || state == support_process_state_tracing_stop);
+  }
 
   if (kill (pid, SIGCONT) != 0)
     FAIL_RET ("kill (%d, SIGCONT): %m\n", pid);
 
   if (test_verbose)
     printf ("info: waiting pid %d, state_sleeping\n", (int) pid);
-  support_process_state_wait (pid, support_process_state_sleeping);
+  TEST_COMPARE (support_process_state_wait (pid,
+					    support_process_state_sleeping),
+		support_process_state_sleeping);
 
   if (kill (pid, SIGUSR1) != 0)
     FAIL_RET ("kill (%d, SIGUSR1): %m\n", pid);
 
   if (test_verbose)
     printf ("info: waiting pid %d, state_running\n", (int) pid);
-  support_process_state_wait (pid, support_process_state_running);
+  TEST_COMPARE (support_process_state_wait (pid,
+					    support_process_state_running),
+		support_process_state_running);
 
   if (kill (pid, SIGKILL) != 0)
     FAIL_RET ("kill (%d, SIGKILL): %m\n", pid);
 
   if (test_verbose)
     printf ("info: waiting pid %d, state_zombie\n", (int) pid);
-  support_process_state_wait (pid, support_process_state_zombie);
+  TEST_COMPARE (support_process_state_wait (pid,
+					    support_process_state_zombie),
+		support_process_state_zombie);;
 
   siginfo_t info;
   int r = waitid (P_PID, pid, &info, WEXITED);
diff --git a/support/tst-support_fuse.c b/support/tst-support_fuse.c
new file mode 100644
index 0000000000..9ee637cbab
--- /dev/null
+++ b/support/tst-support_fuse.c
@@ -0,0 +1,349 @@
+/* Facilities for FUSE-backed file system tests.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/fuse.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xdirent.h>
+#include <support/xunistd.h>
+
+static void
+fuse_thread (struct support_fuse *f, void *closure)
+{
+  /* Turn on returning FUSE_FORGET responses.  */
+  support_fuse_filter_forget (f, false);
+
+  /* Inode and nodeid for "file" and "new".  */
+  enum { NODE_FILE = 2, NODE_NEW, NODE_SUBDIR, NODE_SYMLINK };
+  struct fuse_in_header *inh;
+  while ((inh = support_fuse_next (f)) != NULL)
+    {
+      {
+        char *opcode = support_fuse_opcode (inh->opcode);
+        printf ("info: (T) event %s(%llu) len=%u nodeid=%llu\n",
+                opcode, (unsigned long long int) inh->unique, inh->len,
+                (unsigned long long int) inh->nodeid);
+        free (opcode);
+      }
+
+      /* Handle mountpoint and basic directory operation for the root (1).  */
+      if (support_fuse_handle_mountpoint (f)
+          || (inh->nodeid == 1 && support_fuse_handle_directory (f)))
+        continue;
+
+      switch (inh->opcode)
+        {
+        case FUSE_READDIR:
+          /* Implementation of getdents64.  */
+          if (inh->nodeid == 1)
+            {
+              struct support_fuse_dirstream *d
+                = support_fuse_prepare_readdir (f);
+              TEST_COMPARE (support_fuse_cast (READ, inh)->offset, 0);
+              TEST_VERIFY (support_fuse_dirstream_add (d, 1, 1, DT_DIR, "."));
+              TEST_VERIFY (support_fuse_dirstream_add (d, 1, 2, DT_DIR, ".."));
+              TEST_VERIFY (support_fuse_dirstream_add (d, NODE_FILE, 3, DT_REG,
+                                                       "file"));
+              support_fuse_reply_prepared (f);
+            }
+          else
+            support_fuse_reply_error (f, EIO);
+          break;
+        case FUSE_LOOKUP:
+          /* Part of the implementation of open.  */
+          {
+            char *name = support_fuse_cast (LOOKUP, inh);
+            printf ("  name: %s\n", name);
+            if (inh->nodeid == 1 && strcmp (name, "file") == 0)
+              {
+                struct fuse_entry_out *out
+                  = support_fuse_prepare_entry (f, NODE_FILE);
+                out->attr.mode = S_IFREG | 0600;
+                support_fuse_reply_prepared (f);
+              }
+            else if (inh->nodeid == 1 && strcmp (name, "symlink") == 0)
+              {
+                struct fuse_entry_out *out
+                  = support_fuse_prepare_entry (f, NODE_SYMLINK);
+                out->attr.mode = S_IFLNK | 0777;
+                support_fuse_reply_prepared (f);
+              }
+            else
+              support_fuse_reply_error (f, ENOENT);
+          }
+          break;
+        case FUSE_OPEN:
+          /* Implementation of open.  */
+          {
+            struct fuse_open_in *p = support_fuse_cast (OPEN, inh);
+            if (inh->nodeid == NODE_FILE)
+              {
+                TEST_VERIFY (!(p->flags & O_EXCL));
+                struct fuse_open_out out = { 0, };
+                support_fuse_reply (f, &out, sizeof (out));
+              }
+            else
+              support_fuse_reply_error (f, ENOENT);
+          }
+          break;
+        case FUSE_GETATTR:
+          /* Happens after open.  */
+          if (inh->nodeid == NODE_FILE)
+            {
+              struct fuse_attr_out *out = support_fuse_prepare_attr (f);
+              out->attr.mode = S_IFREG | 0600;
+              out->attr.size = strlen ("Hello, world!");
+              support_fuse_reply_prepared (f);
+            }
+          else
+            support_fuse_reply_error (f, ENOENT);
+          break;
+        case FUSE_READ:
+          /* Implementation of read.  */
+          if (inh->nodeid == NODE_FILE)
+            {
+              struct fuse_read_in *p = support_fuse_cast (READ, inh);
+              TEST_COMPARE (p->offset, 0);
+              TEST_VERIFY (p->size >= strlen ("Hello, world!"));
+              support_fuse_reply (f,
+                                  "Hello, world!", strlen ("Hello, world!"));
+            }
+          else
+            support_fuse_reply_error (f, EIO);
+          break;
+        case FUSE_FLUSH:
+          /* Sent in response to close.  */
+          support_fuse_reply_empty (f);
+          break;
+        case FUSE_GETXATTR:
+          /* This happens as part of a open-for-write operation.
+             Signal no support for extended attributes.  */
+          support_fuse_reply_error (f, ENOSYS);
+          break;
+        case FUSE_SETATTR:
+          /* This happens as part of a open-for-write operation to
+             implement O_TRUNC.  */
+          if (inh->nodeid == NODE_FILE)
+            {
+              struct fuse_setattr_in *p = support_fuse_cast (SETATTR, inh);
+              /* FATTR_LOCKOWNER may also be set.  */
+              TEST_COMPARE ((p->valid) & ~ FATTR_LOCKOWNER, FATTR_SIZE);
+              TEST_COMPARE (p->size, 0);
+              struct fuse_attr_out *out = support_fuse_prepare_attr (f);
+              out->attr.mode = S_IFREG | 0600;
+              support_fuse_reply_prepared (f);
+            }
+          else
+            support_fuse_reply_error (f, EIO);
+          break;
+        case FUSE_WRITE:
+          /* Implementation of write.  */
+          if (inh->nodeid == NODE_FILE)
+            {
+              struct fuse_write_in *p = support_fuse_cast (WRITE, inh);
+              TEST_COMPARE (p->offset, 0);
+              /* Write payload follows after struct fuse_write_in.  */
+              TEST_COMPARE_BLOB (p + 1, p->size,
+                                 "Good day to you too.",
+                                 strlen ("Good day to you too."));
+              struct fuse_write_out out =
+                {
+                  .size = p->size,
+                };
+              support_fuse_reply (f, &out, sizeof (out));
+            }
+          else
+            support_fuse_reply_error (f, EIO);
+          break;
+        case FUSE_CREATE:
+          /* Implementation of O_CREAT.  */
+          if (inh->nodeid == 1)
+            {
+              char *name;
+              struct fuse_create_in *p
+                = support_fuse_cast_name (CREATE, inh, &name);
+              TEST_VERIFY (S_ISREG (p->mode));
+              TEST_COMPARE (p->mode & 07777, 0600);
+              TEST_COMPARE_STRING (name, "new");
+              struct fuse_entry_out *out_entry;
+              struct fuse_open_out *out_open;
+              support_fuse_prepare_create (f, NODE_NEW, &out_entry, &out_open);
+              out_entry->attr.mode = S_IFREG | 0600;
+              support_fuse_reply_prepared (f);
+            }
+          else
+            support_fuse_reply_error (f, EIO);
+          break;
+        case FUSE_MKDIR:
+          /* Implementation of mkdir.  */
+          {
+            if (inh->nodeid == 1)
+              {
+                char *name;
+                struct fuse_mkdir_in *p
+                  = support_fuse_cast_name (MKDIR, inh, &name);
+                TEST_COMPARE (p->mode, 01234);
+                TEST_COMPARE_STRING (name, "subdir");
+                struct fuse_entry_out *out
+                  = support_fuse_prepare_entry (f, NODE_SUBDIR);
+                out->attr.mode = S_IFDIR | p->mode;
+                support_fuse_reply_prepared (f);
+              }
+            else
+              support_fuse_reply_error (f, EIO);
+          }
+          break;
+        case FUSE_READLINK:
+          /* Implementation of readlink.  */
+          TEST_COMPARE (inh->nodeid, NODE_SYMLINK);
+          if (inh->nodeid == NODE_SYMLINK)
+            support_fuse_reply (f, "target-of-symbolic-link",
+                                strlen ("target-of-symbolic-link"));
+          else
+            support_fuse_reply_error (f, EINVAL);
+          break;
+        case FUSE_FORGET:
+          support_fuse_no_reply (f);
+          break;
+        default:
+          support_fuse_reply_error (f, EIO);
+        }
+    }
+}
+
+static int
+do_test (void)
+{
+  support_fuse_init ();
+
+  struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);
+
+  printf ("info: Attributes of mountpoint/root directory %s\n",
+          support_fuse_mountpoint (f));
+  {
+    struct statx st;
+    xstatx (AT_FDCWD, support_fuse_mountpoint (f), 0, STATX_BASIC_STATS, &st);
+    TEST_COMPARE (st.stx_uid, getuid ());
+    TEST_COMPARE (st.stx_gid, getgid ());
+    TEST_VERIFY (S_ISDIR (st.stx_mode));
+    TEST_COMPARE (st.stx_mode & 07777, 0700);
+  }
+
+  printf ("info: List directory %s\n", support_fuse_mountpoint (f));
+  {
+    DIR *dir = xopendir (support_fuse_mountpoint (f));
+
+    struct dirent *e = xreaddir (dir);
+    TEST_COMPARE (e->d_ino, 1);
+#ifdef _DIRENT_HAVE_D_OFF
+    TEST_COMPARE (e->d_off, 1);
+#endif
+    TEST_COMPARE (e->d_type, DT_DIR);
+    TEST_COMPARE_STRING (e->d_name, ".");
+
+    e = xreaddir (dir);
+    TEST_COMPARE (e->d_ino, 1);
+#ifdef _DIRENT_HAVE_D_OFF
+    TEST_COMPARE (e->d_off, 2);
+#endif
+    TEST_COMPARE (e->d_type, DT_DIR);
+    TEST_COMPARE_STRING (e->d_name, "..");
+
+    e = xreaddir (dir);
+    TEST_COMPARE (e->d_ino, 2);
+#ifdef _DIRENT_HAVE_D_OFF
+    TEST_COMPARE (e->d_off, 3);
+#endif
+    TEST_COMPARE (e->d_type, DT_REG);
+    TEST_COMPARE_STRING (e->d_name, "file");
+
+    TEST_COMPARE (closedir (dir), 0);
+  }
+
+  char *file_path = xasprintf ("%s/file", support_fuse_mountpoint (f));
+
+  printf ("info: Attributes of file %s\n", file_path);
+  {
+    struct statx st;
+    xstatx (AT_FDCWD, file_path, 0, STATX_BASIC_STATS, &st);
+    TEST_COMPARE (st.stx_uid, getuid ());
+    TEST_COMPARE (st.stx_gid, getgid ());
+    TEST_VERIFY (S_ISREG (st.stx_mode));
+    TEST_COMPARE (st.stx_mode & 07777, 0600);
+    TEST_COMPARE (st.stx_size, strlen ("Hello, world!"));
+  }
+
+  printf ("info: Read from %s\n", file_path);
+  {
+    int fd = xopen (file_path, O_RDONLY, 0);
+    char buf[64];
+    ssize_t len = read (fd, buf, sizeof (buf));
+    if (len < 0)
+      FAIL_EXIT1 ("read: %m");
+    TEST_COMPARE_BLOB (buf, len, "Hello, world!", strlen ("Hello, world!"));
+    xclose (fd);
+  }
+
+  printf ("info: Write to %s\n", file_path);
+  {
+    int fd = xopen (file_path, O_WRONLY | O_TRUNC, 0);
+    xwrite (fd, "Good day to you too.", strlen ("Good day to you too."));
+    xclose (fd);
+  }
+
+  printf ("info: Attempt O_EXCL creation of existing %s\n", file_path);
+  /* O_EXCL creation shall fail.  */
+  errno = 0;
+  TEST_COMPARE (open64 (file_path, O_RDWR | O_EXCL | O_CREAT, 0600), -1);
+  TEST_COMPARE (errno, EEXIST);
+
+  free (file_path);
+
+  {
+    char *new_path = xasprintf ("%s/new", support_fuse_mountpoint (f));
+    printf ("info: Test successful O_EXCL creation at %s\n", new_path);
+    int fd = xopen (new_path, O_RDWR | O_EXCL | O_CREAT, 0600);
+    xclose (fd);
+    free (new_path);
+  }
+
+  {
+    char *subdir_path = xasprintf ("%s/subdir", support_fuse_mountpoint (f));
+    xmkdir (subdir_path, 01234);
+    free (subdir_path);
+  }
+
+  {
+    char *symlink_path = xasprintf ("%s/symlink", support_fuse_mountpoint (f));
+    char *target = xreadlink (symlink_path);
+    TEST_COMPARE_STRING (target, "target-of-symbolic-link");
+    free (target);
+    free (symlink_path);
+  }
+
+  support_fuse_unmount (f);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/tst-support_readdir.c b/support/tst-support_readdir.c
new file mode 100644
index 0000000000..66be94fa80
--- /dev/null
+++ b/support/tst-support_readdir.c
@@ -0,0 +1,73 @@
+/* Test the support_readdir function.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/readdir.h>
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/xdirent.h>
+#include <support/xunistd.h>
+
+static int
+do_test (void)
+{
+  DIR *reference_stream = xopendir (".");
+  struct dirent64 *reference = xreaddir64 (reference_stream);
+
+  for (enum support_readdir_op op = 0; op <= support_readdir_op_last (); ++op)
+    {
+      DIR *stream = xopendir (".");
+      struct support_dirent e;
+      memset (&e, 0xcc, sizeof (e));
+      e.d_name = NULL;
+      TEST_VERIFY (support_readdir (stream, op, &e));
+      TEST_COMPARE (e.d_ino, reference->d_ino);
+#ifdef _DIRENT_HAVE_D_OFF
+      TEST_VERIFY (support_readdir_offset_width (op) != 0);
+      TEST_COMPARE (e.d_off, reference->d_off);
+#else
+      TEST_COMPARE (support_readdir_offset_width (op), 0);
+      TEST_COMPARE (e.d_off, 0);
+#endif
+      TEST_COMPARE (e.d_type, reference->d_type);
+      TEST_COMPARE_STRING (e.d_name, reference->d_name);
+      free (e.d_name);
+      xclosedir (stream);
+    }
+
+  xclosedir (reference_stream);
+
+  /* Error injection test.  */
+  int devnull = xopen ("/dev/null", O_RDONLY, 0);
+  for (enum support_readdir_op op = 0; op <= support_readdir_op_last (); ++op)
+    {
+      DIR *stream = xopendir (".");
+      /* A descriptor incompatible with readdir.  */
+      xdup2 (devnull, dirfd (stream));
+      errno = -1;
+      support_readdir_expect_error (stream, op, ENOTDIR);
+      xclosedir (stream);
+    }
+  xclose (devnull);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/tst-xdirent.c b/support/tst-xdirent.c
new file mode 100644
index 0000000000..642483165a
--- /dev/null
+++ b/support/tst-xdirent.c
@@ -0,0 +1,76 @@
+/* Compile test for error-checking wrappers for <dirent.h>
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/xdirent.h>
+
+#include <libc-diag.h>
+#include <support/check.h>
+#include <unistd.h>
+
+static int
+do_test (void)
+{
+  {
+    DIR *d = xopendir (".");
+    struct dirent *e = xreaddir (d);
+    /* Assume that the "." special entry always comes first.  */
+    TEST_COMPARE_STRING (e->d_name, ".");
+    while (xreaddir (d) != NULL)
+      ;
+    xclosedir (d);
+  }
+
+  {
+    DIR *d = xopendir (".");
+    struct dirent64 *e = xreaddir64 (d);
+    TEST_COMPARE_STRING (e->d_name, ".");
+    while (xreaddir64 (d) != NULL)
+      ;
+    xclosedir (d);
+  }
+
+  /* The functions readdir_r, readdir64_r were deprecated in glibc 2.24.  */
+  DIAG_PUSH_NEEDS_COMMENT;
+  DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+
+  {
+    DIR *d = xopendir (".");
+    struct dirent buf = { 0, };
+    TEST_VERIFY (xreaddir_r (d, &buf));
+    TEST_COMPARE_STRING (buf.d_name, ".");
+    while (xreaddir_r (d, &buf))
+      ;
+    xclosedir (d);
+  }
+
+  {
+    DIR *d = xopendir (".");
+    struct dirent64 buf = { 0, };
+    TEST_VERIFY (xreaddir64_r (d, &buf));
+    TEST_COMPARE_STRING (buf.d_name, ".");
+    while (xreaddir64_r (d, &buf))
+      ;
+    xclosedir (d);
+  }
+
+  DIAG_POP_NEEDS_COMMENT;
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/support-xstat.c b/support/xclosedir.c
index ce866f74d2..b490df5598 100644
--- a/support/support-xstat.c
+++ b/support/xclosedir.c
@@ -1,5 +1,5 @@
-/* stat64 with error checking.
-   Copyright (C) 2017-2024 Free Software Foundation, Inc.
+/* Error-checking wrapper for closedir.
+   Copyright (C) 2024 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -16,15 +16,13 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-/* NB: Non-standard file name to avoid sysdeps override for xstat.  */
+#include <support/xdirent.h>
 
 #include <support/check.h>
-#include <support/xunistd.h>
-#include <sys/stat.h>
 
 void
-xstat (const char *path, struct stat64 *result)
+xclosedir (DIR *dir)
 {
-  if (stat64 (path, result) != 0)
-    FAIL_EXIT1 ("stat64 (\"%s\"): %m", path);
+  if (closedir (dir) != 0)
+    FAIL_EXIT1 ("closedir: %m");
 }
diff --git a/support/xdirent.h b/support/xdirent.h
new file mode 100644
index 0000000000..8465d70ec1
--- /dev/null
+++ b/support/xdirent.h
@@ -0,0 +1,86 @@
+/* Error-checking wrappers for <dirent.h>
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#ifndef SUPPORT_XDIRENT_H
+#define SUPPORT_XDIRENT_H
+
+#include <dirent.h>
+#include <errno.h>
+#include <libc-diag.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+__BEGIN_DECLS
+
+DIR *xopendir (const char *path);
+DIR *xfdopendir (int fd);
+void xclosedir (DIR *);
+
+void *support_readdir_check (const char *, void *, int);
+
+static __attribute__ ((unused)) struct dirent *
+xreaddir (DIR *stream)
+{
+  int saved_errno = errno;
+  errno = 0;
+  struct dirent *result = readdir (stream);
+  return support_readdir_check ("readdir", result, saved_errno);
+}
+
+static __attribute__ ((unused)) struct dirent64 *
+xreaddir64 (DIR *stream)
+{
+  int saved_errno = errno;
+  errno = 0;
+  struct dirent64 *result = readdir64 (stream);
+  return support_readdir_check ("readdir64", result, saved_errno);
+}
+
+/* The functions readdir_r, readdir64_r were deprecated in glibc 2.24.  */
+DIAG_PUSH_NEEDS_COMMENT;
+DIAG_IGNORE_NEEDS_COMMENT (4.9, "-Wdeprecated-declarations");
+
+int support_readdir_r_check (const char *, int, void *, void *);
+
+static __attribute__ ((unused)) bool
+xreaddir_r (DIR *stream, struct dirent *buf)
+{
+  struct dirent *ptr;
+  int ret = readdir_r (stream, buf, &ptr);
+  if (ret == 0 && ptr == NULL)
+    return false;
+  support_readdir_r_check ("readdir_r", ret, buf, ptr);
+  return true;
+}
+
+static __attribute__ ((unused)) bool
+xreaddir64_r (DIR *stream, struct dirent64 *buf)
+{
+  struct dirent64 *ptr;
+  int ret = readdir64_r (stream, buf, &ptr);
+  if (ret == 0 && ptr == NULL)
+    return false;
+  support_readdir_r_check ("readdir64_r", ret, buf, ptr);
+  return true;
+}
+
+DIAG_POP_NEEDS_COMMENT;
+
+__END_DECLS
+
+#endif /* SUPPORT_XDIRENT_H */
diff --git a/support/xfdopendir.c b/support/xfdopendir.c
new file mode 100644
index 0000000000..d881d28c73
--- /dev/null
+++ b/support/xfdopendir.c
@@ -0,0 +1,30 @@
+/* Error-checking wrapper for fdopendir.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/xdirent.h>
+
+#include <support/check.h>
+
+DIR *
+xfdopendir (int fd)
+{
+  DIR *result = fdopendir (fd);
+  if (result == NULL)
+    FAIL_EXIT1 ("fdopendir (%d): %m", fd);
+  return result;
+}
diff --git a/support/xopendir.c b/support/xopendir.c
new file mode 100644
index 0000000000..e4aee07fee
--- /dev/null
+++ b/support/xopendir.c
@@ -0,0 +1,30 @@
+/* Error-checking wrapper for opendir.
+   Copyright (C) 2024 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <support/xdirent.h>
+
+#include <support/check.h>
+
+DIR *
+xopendir (const char *path)
+{
+  DIR *result = opendir (path);
+  if (result == NULL)
+    FAIL_EXIT1 ("opendir (\"%s\"): %m", path);
+  return result;
+}
diff --git a/support/support-xfstat-time64.c b/support/xstatx.c
index 589a69bb3e..621f2440f8 100644
--- a/support/support-xfstat-time64.c
+++ b/support/xstatx.c
@@ -1,5 +1,5 @@
-/* 64-bit time_t stat with error checking.
-   Copyright (C) 2021-2024 Free Software Foundation, Inc.
+/* Error-checking wrapper for statx.
+   Copyright (C) 2024 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -16,17 +16,17 @@
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-/* NB: Non-standard file name to avoid sysdeps override for xstat.  */
+#include <support/xunistd.h>
 
+#include <fcntl.h>
 #include <support/check.h>
-#include <support/xunistd.h>
 #include <sys/stat.h>
 
-#if __TIMESIZE != 64
 void
-xfstat_time64 (int fd, struct __stat64_t64 *result)
+xstatx (int fd, const char *path, int flags, unsigned int mask,
+        struct statx *stx)
 {
-  if (__fstat64_time64 (fd, result) != 0)
-    FAIL_EXIT1 ("__fstat64_time64 (%d): %m", fd);
+  if (statx (fd, path, flags, mask, stx) != 0)
+    FAIL_EXIT1 ("statx (AT_FDCWD, \"%s\", 0x%x, 0x%x): %m",
+                path, (unsigned int) flags, mask);
 }
-#endif
diff --git a/support/xunistd.h b/support/xunistd.h
index 13be9a46a3..204951bce7 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -29,28 +29,28 @@
 
 __BEGIN_DECLS
 
-struct stat64;
+struct statx;
 
 pid_t xfork (void);
 pid_t xwaitpid (pid_t, int *status, int flags);
 void xpipe (int[2]);
 void xdup2 (int, int);
 int xopen (const char *path, int flags, mode_t);
-#ifndef __USE_TIME64_REDIRECTS
-# ifdef __USE_FILE_OFFSET64
-void xstat (const char *path, struct stat *);
-void xlstat (const char *path, struct stat *);
-void xfstat (int fd, struct stat *);
-# else
-void xstat (const char *path, struct stat64 *);
-void xlstat (const char *path, struct stat64 *);
-void xfstat (int fd, struct stat64 *);
-# endif
-#else
-void __REDIRECT (xstat, (const char *path, struct stat *), xstat_time64);
-void __REDIRECT (xlstat, (const char *path, struct stat *), xlstat_time64);
-void __REDIRECT (xfstat, (int fd, struct stat *), xfstat_time64);
-#endif
+void support_check_stat_fd (const char *name, int fd, int result);
+void support_check_stat_path (const char *name, const char *path, int result);
+#define xstat(path, st) \
+  (support_check_stat_path ("stat", (path), stat ((path), (st))))
+#define xfstat(fd, st) \
+  (support_check_stat_fd ("fstat", (fd), fstat ((fd), (st))))
+#define xlstat(path, st) \
+  (support_check_stat_path ("lstat", (path), lstat ((path), (st))))
+#define xstat64(path, st) \
+  (support_check_stat_path ("stat64", (path), stat64 ((path), (st))))
+#define xfstat64(fd, st) \
+  (support_check_stat_fd ("fstat64", (fd), fstat64 ((fd), (st))))
+#define xlstat64(path, st) \
+  (support_check_stat_path ("lstat64", (path), lstat64 ((path), (st))))
+void xstatx (int, const char *, int, unsigned int, struct statx *);
 void xmkdir (const char *path, mode_t);
 void xchroot (const char *path);
 void xunlink (const char *path);