summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Lawrence <paullawrence@google.com>2019-08-02 09:54:06 -0700
committerPaul Lawrence <paullawrence@google.com>2019-08-02 09:54:06 -0700
commitf1aef731a708eea5f6e3daf04cb2b9e732326f1e (patch)
treed3bae6f92993531b0f60de7dd35c11da5d4da914
parent3fb189c2fc0eb6c5cbe0fc2b99679eceb1ed0e31 (diff)
parent33a4c341e473a159044f3cbb25ac66b654733132 (diff)
Merge remote-tracking branch 'user/master'
-rw-r--r--.gitignore41
-rw-r--r--AUTHORS131
-rw-r--r--Android.bp50
-rw-r--r--GPL2.txt339
-rw-r--r--LGPL2.txt502
-rw-r--r--LICENSE13
-rw-r--r--METADATA18
-rw-r--r--MODULE_LICENSE_LGPL20
l---------NOTICE1
-rw-r--r--README.md156
-rw-r--r--include/cuse_lowlevel.h87
-rw-r--r--include/fuse.h1268
-rw-r--r--include/fuse_common.h812
-rw-r--r--include/fuse_kernel.h848
-rw-r--r--include/fuse_lowlevel.h2056
-rw-r--r--include/fuse_opt.h271
-rw-r--r--include/meson.build4
-rw-r--r--lib/android_config.h2
-rw-r--r--lib/buffer.c321
-rw-r--r--lib/config.h42
-rw-r--r--lib/cuse_lowlevel.c366
-rwxr-xr-xlib/fuse.c5076
-rw-r--r--lib/fuse_i.h139
-rw-r--r--lib/fuse_loop.c46
-rw-r--r--lib/fuse_loop_mt.c362
-rw-r--r--lib/fuse_lowlevel.c3069
-rw-r--r--lib/fuse_misc.h59
-rw-r--r--lib/fuse_opt.c422
-rw-r--r--lib/fuse_signals.c91
-rw-r--r--lib/fuse_versionscript162
-rw-r--r--lib/helper.c440
-rw-r--r--lib/meson.build53
-rw-r--r--lib/modules/iconv.c718
-rw-r--r--lib/modules/subdir.c675
-rw-r--r--lib/mount.c602
-rw-r--r--lib/mount_bsd.c292
-rw-r--r--lib/mount_util.c369
-rw-r--r--lib/mount_util.h18
-rw-r--r--meson.build110
39 files changed, 20031 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..97045f6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+#
+# NOTE! Don't add files that are generated in specific
+# subdirectories here. Add them in the ".gitignore" file
+# in that subdirectory instead.
+#
+# NOTE! Please use 'git ls-files -i --exclude-standard'
+# command after changing this file, to see if there are
+# any tracked files which get ignored after the change.
+*.o
+*.lo
+*.la
+*.gz
+\#*#
+*.orig
+*~
+Makefile.in
+Makefile
+*.m4
+stamp-h*
+config.*
+/ltmain.sh
+/configure
+/install-sh
+/mkinstalldirs
+/missing
+/*.cache
+/depcomp
+/compile
+/libtool
+/INSTALL
+/*.pc
+/.pc
+/patches*
+/m4
+TAGS
+/GPATH
+/GRTAGS
+/GSYMS
+/GTAGS
+/test/test_setattr
+/build/
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..7e97c8f
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,131 @@
+Current Maintainer
+------------------
+
+Nikolaus Rath <Nikolaus@rath.org>
+
+
+Past Maintainers
+----------------
+
+Miklos Szeredi <miklos@szeredi.hu> (until 12/2015)
+
+
+Contributors
+------------
+
+CUSE has been written by Tejun Heo <teheo@suse.de>. Furthermore, the
+following people have contributed patches (autogenerated list):
+
+1c7718e7 <shirokovMartin@gmail.com>
+admorgan <admorgan@morgancomputers.net>
+Ahmed Masud <ahmed.masud@trustifier.com>
+Alan Somers <asomers@gmail.com>
+Albert Chen <hselin.chen@gmail.com>
+Alexander <aleksandr.rvachev@eltex-co.ru>
+alex <devkral@web.de>
+Alex Richman <alex@richman.io>
+amosonn <amosonn@gmail.com>
+Anatol Pomozov <anatol.pomozov@gmail.com>
+Angelo G. Del Regno <kholk11@gmail.com>
+Antonio SJ Musumeci <trapexit@spawn.link>
+Arunav Sanyal <Khalian@users.noreply.github.com>
+Ashley Pittman <ashleypittman@users.noreply.github.com>
+Banglang <banglang.huang@foxmail.com>
+Baptiste Daroussin <bapt@FreeBSD.org>
+Benjamin Barenblat <bbaren@google.com>
+Bill Zissimooulos <billziss@navimatics.com>
+Bill Zissimopoulos <billziss@navimatics.com>
+bobrofon <ifbossfor@ya.ru>
+Brian Naylor <bnaylor@netapp.com>
+Carl Edquist <edquist@cs.wisc.edu>
+Carlos Maiolino <cmaiolino-H+wXaHxf7aLQT0dZR+AlfA@public.gmane.org>
+Chad Austin <chad@chadaustin.me>
+Christopher Harrison <ch12@sanger.ac.uk>
+Consus <consus@gmx.com>
+Csaba Henk <csaba.henk@creo.hu>
+Csaba Henk <csaba@lowlife.hu>
+cvs2git <>
+Dalvik Khertel <khertel@outlook.com>
+Daniel Fullmer <danielrf12@gmail.com>
+Daniel Thau <danthau@bedrocklinux.org>
+David McNab <david@rebirthing.co.nz>
+David Sheets <sheets@alum.mit.edu>
+divinity76 <divinity76@gmail.com>
+DrDaveD <2129743+DrDaveD@users.noreply.github.com>
+Emmanuel Dreyfus <manu@netbsd.org>
+Enke Chen <enkechen@yahoo.com>
+Eric Engestrom <eric@engestrom.ch>
+Eric Wong <normalperson@yhbt.net>
+Fabrice Bauzac <fbauzac@amadeus.com>
+Fedor Korotkov <fedor.korotkov@gmail.com>
+Feng Shuo <steve.shuo.feng@gmail.com>
+Forty-Bot <Forty-Bot@users.noreply.github.com>
+guraga <rulumasi@dodsi.com>
+HazelFZ <xfzfygz@gmail.com>
+Heiko Becker <heirecka@exherbo.org>
+Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
+Ikey Doherty <michael.i.doherty@intel.com>
+itsdeepak <deepak.sn@samsung.com>
+Jan Blumschein <jan@jan-blumschein.de>
+Jann Horn <jannh@google.com>
+Jay Hankins <jay-hankins@users.noreply.github.com>
+Jean-Pierre André <jpandre@users.noreply.github.com>
+Joachim Schiele <joachim.schiele@daimler.com>
+Joachim Schiele <js@lastlog.de>
+Joerg Thalheim <joerg@thalheim.io>
+John Muir <john@jmuir.com>
+Joseph Dodge <joseph.dodge@veritas.com>
+Josh Soref <jsoref@users.noreply.github.com>
+Kevin Vigor <kvigor@fb.com>
+Laszlo Boszormenyi (GCS) <gcs@debian.org>
+Laszlo Papp <ext-laszlo.papp@nokia.com>
+Liu Bo <liub.liubo@gmail.com>
+Madan Valluri <mvalluri@cumulus-systems.com>
+Marcin Sulikowski <marcin.sulikowski@editshare.com>
+Mark Glines <mark@glines.org>
+Martin Blanchard <tchaik@gmx.com>
+Mateusz Urbańczyk <murbanczyk@qed.ai>
+Mattias Nissler <mnissler@chromium.org>
+maxice8 <30738253+maxice8@users.noreply.github.com>
+Max Krasnyansky <maxk@kernel.org>
+Michael Forney <mforney@mforney.org>
+Michael Grigoriev <mag@luminal.org>
+Mihail Konev <k.mvc@ya.ru>
+Miklos Szeredi <miklos@szeredi.hu>
+Miklos Szeredi <mszeredi@redhat.com>
+Miklos Szeredi <mszeredi@suse.cz>
+Miklos Szeredi <szmi@users.noreply.github.com>
+mkmm@gmx-topmail.de <mkmm@gmx-topmail.de>
+Natanael Copa <ncopa@alpinelinux.org>
+Niels de Vos <ndevos@redhat.com>
+Nikolaus Rath <Nikolaus@rath.org>
+Oded Arbel <oded@geek.co.il>
+Olivier Blin <olivier.blin@softathome.com>
+pablomh <pablomh@gmail.com>
+Peter Lemenkov <lemenkov@gmail.com>
+Przemyslaw Pawelczyk <przemoc@gmail.com>
+Przemysław Pawełczyk <przemoc@gmail.com>
+Ratna_Bolla@dell.com <Ratna_Bolla@dell.com>
+Reuben Hawkins <reubenhwk@gmail.com>
+Richard W.M. Jones <rjones@redhat.com>
+Riku Voipio <riku.voipio@linaro.org>
+Robo Shimmer <roboshim@users.noreply.github.com>
+Roland Bauerschmidt <rb@debian.org>
+Roman Bogorodskiy <bogorodskiy@gmail.com>
+Rostislav <rostislav@users.noreply.github.com>
+Rostislav Skudnov <rostislav@tuxera.com>
+Sam Huffman <40582525+samh-sifive@users.noreply.github.com>
+Sam Stuewe <halosghost@archlinux.info>
+Sangwoo Moon <swmoon00@gmail.com>
+scosu <mpargmann@allfex.org>
+Scott Worley <scottworley@scottworley.com>
+Sebastian Pipping <sebastian@pipping.org>
+Sławek Rudnicki <slawek.rudnicki@editshare.com>
+Tej Chajed <tchajed@mit.edu>
+therealneworld@gmail.com <therealneworld@gmail.com>
+Tomohiro Kusumi <kusumi.tomohiro@gmail.com>
+userwithuid <userwithuid@gmail.com>
+Valentin Plugaru <valentin.plugaru@uni.lu>
+Vivek Goyal <vgoyal@redhat.com>
+William Woodruff <william@yossarian.net>
+Winfried Koehler <w_scan@gmx-topmail.de>
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..9fb4a16
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,50 @@
+cc_defaults {
+ name: "libfuse_default_flags",
+ local_include_dirs: ["include/", "lib/"],
+ cflags: [
+ "-D_FILE_OFFSET_BITS=64",
+ "-DFUSERMOUNT_DIR=\"/system/bin\"",
+ "-DFUSE_USE_VERSION=34",
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ "-Wno-sign-compare",
+ "-Wno-incompatible-pointer-types",
+ "-Wno-missing-field-initializers",
+ "-Wno-unused-parameter",
+ "-Wno-unused-variable",
+ ],
+
+ clang: true,
+
+ ldflags: [
+ ],
+}
+
+cc_library {
+ name: "libfuse",
+ defaults: [
+ "libfuse_default_flags",
+ ],
+
+ export_include_dirs: ["include"],
+
+ version_script: "lib/fuse_versionscript",
+
+ srcs: [
+ "lib/buffer.c",
+ "lib/cuse_lowlevel.c",
+ "lib/fuse.c",
+ "lib/fuse_loop.c",
+ "lib/fuse_loop_mt.c",
+ "lib/fuse_lowlevel.c",
+ "lib/fuse_opt.c",
+ "lib/fuse_signals.c",
+ "lib/helper.c",
+ // "lib/mount_bsd.c",
+ "lib/modules/subdir.c",
+ "lib/modules/iconv.c",
+ "lib/mount.c",
+ "lib/mount_util.c",
+ ],
+}
diff --git a/GPL2.txt b/GPL2.txt
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/GPL2.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, 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 Lesser 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 Street, 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 Lesser General
+Public License instead of this License.
diff --git a/LGPL2.txt b/LGPL2.txt
new file mode 100644
index 0000000..4362b49
--- /dev/null
+++ b/LGPL2.txt
@@ -0,0 +1,502 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, 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.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+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 and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, 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 library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+ 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 Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+ If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be 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.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+ 9. 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 Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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 with
+this License.
+
+ 11. 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 Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This 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.
+
+ This 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 this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..184a5b2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+The following files may be used under the terms of the GNU Lesser
+General Public License, version 2.1 ("LGPL"):
+
+- All files in the include/ directory.
+- All files in the lib/ directory.
+- meson.build
+
+The full terms of the LGPL can be found in the LGPL2.txt file.
+
+
+All other files may be used only under the terms of the GNU General
+Public License, version 2 ("GPL"). The full text of this license can
+be found in the GPL2.txt file.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..9a45a95
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,18 @@
+name: "libfuse"
+description:
+ "The reference implementation of the Linux FUSE (Filesystem in Userspace) "
+ "interface"
+
+third_party {
+ url {
+ type: HOMEPAGE
+ value: "https://github.com/libfuse"
+ }
+ url {
+ type: GIT
+ value: "https://github.com/libfuse/libfuse.git"
+ }
+ version: "fuse-3.6.2"
+ last_upgrade_date { year: 2019 month: 7 day: 19 }
+ license_type: RESTRICTED_IF_STATICALLY_LINKED
+}
diff --git a/MODULE_LICENSE_LGPL2 b/MODULE_LICENSE_LGPL2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_LGPL2
diff --git a/NOTICE b/NOTICE
new file mode 120000
index 0000000..7a694c9
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1 @@
+LICENSE \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8fd40f5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,156 @@
+libfuse
+=======
+
+About
+-----
+
+FUSE (Filesystem in Userspace) is an interface for userspace programs
+to export a filesystem to the Linux kernel. The FUSE project consists
+of two components: the *fuse* kernel module (maintained in the regular
+kernel repositories) and the *libfuse* userspace library (maintained
+in this repository). libfuse provides the reference implementation
+for communicating with the FUSE kernel module.
+
+A FUSE file system is typically implemented as a standalone
+application that links with libfuse. libfuse provides functions to
+mount the file system, unmount it, read requests from the kernel, and
+send responses back. libfuse offers two APIs: a "high-level",
+synchronous API, and a "low-level" asynchronous API. In both cases,
+incoming requests from the kernel are passed to the main program using
+callbacks. When using the high-level API, the callbacks may work with
+file names and paths instead of inodes, and processing of a request
+finishes when the callback function returns. When using the low-level
+API, the callbacks must work with inodes and responses must be sent
+explicitly using a separate set of API functions.
+
+
+Development Status
+------------------
+
+libfuse is shipped by all major Linux distributions and has been in
+production use across a wide range of systems for many years. However,
+at present libfuse does not have any active, regular contributors. The
+current maintainer continues to apply pull requests and makes regular
+releases, but unfortunately has no capacity to do any development
+beyond addressing high-impact issues. When reporting bugs, please
+understand that unless you are including a pull request or are
+reporting a critical issue, you will probably not get a response. If
+you are using libfuse, please consider to contribute to the project.
+
+
+Supported Platforms
+-------------------
+
+* Linux (fully)
+* BSD (mostly/best-effort)
+* For OS-X, please use [OSXFUSE](https://osxfuse.github.io/)
+
+
+Installation
+------------
+
+You can download libfuse from
+https://github.com/libfuse/libfuse/releases. To build and install, we
+recommend to use [Meson](http://mesonbuild.com/) and
+[Ninja](https://ninja-build.org). After extracting the libfuse
+tarball, create a (temporary) build directory and run Meson:
+
+ $ mkdir build; cd build
+ $ meson ..
+
+Normally, the default build options will work fine. If you
+nevertheless want to adjust them, you can do so with the *mesonconf*
+command:
+
+ $ mesonconf # list options
+ $ mesonconf -D disable-mtab=true # set an option
+
+To build, test and install libfuse, you then use Ninja:
+
+ $ ninja
+ $ sudo python3 -m pytest test/
+ $ sudo ninja install
+
+Running the tests requires the [py.test](http://www.pytest.org/)
+Python module. Instead of running the tests as root, the majority of
+tests can also be run as a regular user if *util/fusermount3* is made
+setuid root first:
+
+ $ sudo chown root:root util/fusermount3
+ $ sudo chmod 4755 util/fusermount3
+ $ python3 -m pytest test/
+
+Security implications
+---------------------
+
+The *fusermount3* program is installed setuid root. This is done to
+allow normal users to mount their own filesystem implementations.
+
+To limit the harm that malicious users can do this way, *fusermount3*
+enforces the following limitations:
+
+ - The user can only mount on a mountpoint for which he has write
+ permission
+
+ - The mountpoint must not be a sticky directory which isn't owned by
+ the user (like /tmp usually is)
+
+ - No other user (including root) can access the contents of the
+ mounted filesystem (though this can be relaxed by allowing the use
+ of the *allow_other* and *allow_root* mount options in
+ */etc/fuse.conf*)
+
+
+If you intend to use the *allow_other* mount options, be aware that
+FUSE has an unresolved [security
+bug](https://github.com/libfuse/libfuse/issues/15): if the
+*default_permissions* mount option is not used, the results of the
+first permission check performed by the file system for a directory
+entry will be re-used for subsequent accesses as long as the inode of
+the accessed entry is present in the kernel cache - even if the
+permissions have since changed, and even if the subsequent access is
+made by a different user. This is of little concern if the filesystem
+is accessible only to the mounting user (which has full access to the
+filesystem anyway), but becomes a security issue when other users are
+allowed to access the filesystem (since they can exploit this to
+perform operations on the filesystem that they do not actually have
+permissions for).
+
+This bug needs to be fixed in the Linux kernel and has been known
+since 2006 but unfortunately no fix has been applied yet. If you
+depend on correct permission handling for FUSE file systems, the only
+workaround is to use `default_permissions` (which does not currently
+support ACLs), or to completely disable caching of directory entry
+attributes.
+
+Building your own filesystem
+------------------------------
+
+FUSE comes with several example file systems in the `examples`
+directory. For example, the *passthrough* examples mirror the contents
+of the root directory under the mountpoint. Start from there and adapt
+the code!
+
+The documentation of the API functions and necessary callbacks is
+mostly contained in the files `include/fuse.h` (for the high-level
+API) and `include/fuse_lowlevel.h` (for the low-level API). An
+autogenerated html version of the API is available in the `doc/html`
+directory and at http://libfuse.github.io/doxygen.
+
+
+Getting Help
+------------
+
+If you need help, please ask on the <fuse-devel@lists.sourceforge.net>
+mailing list (subscribe at
+https://lists.sourceforge.net/lists/listinfo/fuse-devel).
+
+Please report any bugs on the GitHub issue tracker at
+https://github.com/libfuse/libfuse/issues.
+
+
+Professional Support
+--------------------
+
+Professional support is offered via [Rath
+Consulting](http://www.rath-consulting.biz).
diff --git a/include/cuse_lowlevel.h b/include/cuse_lowlevel.h
new file mode 100644
index 0000000..80476c2
--- /dev/null
+++ b/include/cuse_lowlevel.h
@@ -0,0 +1,87 @@
+/*
+ CUSE: Character device in Userspace
+ Copyright (C) 2008-2009 SUSE Linux Products GmbH
+ Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+
+ Read example/cusexmp.c for usages.
+*/
+
+#ifndef CUSE_LOWLEVEL_H_
+#define CUSE_LOWLEVEL_H_
+
+#ifndef FUSE_USE_VERSION
+#define FUSE_USE_VERSION 29
+#endif
+
+#include "fuse_lowlevel.h"
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CUSE_UNRESTRICTED_IOCTL (1 << 0) /* use unrestricted ioctl */
+
+struct fuse_session;
+
+struct cuse_info {
+ unsigned dev_major;
+ unsigned dev_minor;
+ unsigned dev_info_argc;
+ const char **dev_info_argv;
+ unsigned flags;
+};
+
+/*
+ * Most ops behave almost identically to the matching fuse_lowlevel
+ * ops except that they don't take @ino.
+ *
+ * init_done : called after initialization is complete
+ * read/write : always direct IO, simultaneous operations allowed
+ * ioctl : might be in unrestricted mode depending on ci->flags
+ */
+struct cuse_lowlevel_ops {
+ void (*init) (void *userdata, struct fuse_conn_info *conn);
+ void (*init_done) (void *userdata);
+ void (*destroy) (void *userdata);
+ void (*open) (fuse_req_t req, struct fuse_file_info *fi);
+ void (*read) (fuse_req_t req, size_t size, off_t off,
+ struct fuse_file_info *fi);
+ void (*write) (fuse_req_t req, const char *buf, size_t size, off_t off,
+ struct fuse_file_info *fi);
+ void (*flush) (fuse_req_t req, struct fuse_file_info *fi);
+ void (*release) (fuse_req_t req, struct fuse_file_info *fi);
+ void (*fsync) (fuse_req_t req, int datasync, struct fuse_file_info *fi);
+ void (*ioctl) (fuse_req_t req, int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned int flags,
+ const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+ void (*poll) (fuse_req_t req, struct fuse_file_info *fi,
+ struct fuse_pollhandle *ph);
+};
+
+struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+ const struct cuse_info *ci,
+ const struct cuse_lowlevel_ops *clop,
+ void *userdata);
+
+struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+ const struct cuse_info *ci,
+ const struct cuse_lowlevel_ops *clop,
+ int *multithreaded, void *userdata);
+
+void cuse_lowlevel_teardown(struct fuse_session *se);
+
+int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+ const struct cuse_lowlevel_ops *clop, void *userdata);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* CUSE_LOWLEVEL_H_ */
diff --git a/include/fuse.h b/include/fuse.h
new file mode 100644
index 0000000..2d2291c
--- /dev/null
+++ b/include/fuse.h
@@ -0,0 +1,1268 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#ifndef FUSE_H_
+#define FUSE_H_
+
+/** @file
+ *
+ * This file defines the library interface of FUSE
+ *
+ * IMPORTANT: you should define FUSE_USE_VERSION before including this header.
+ */
+
+#include "fuse_common.h"
+
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ----------------------------------------------------------- *
+ * Basic FUSE API *
+ * ----------------------------------------------------------- */
+
+/** Handle for a FUSE filesystem */
+struct fuse;
+
+/**
+ * Readdir flags, passed to ->readdir()
+ */
+enum fuse_readdir_flags {
+ /**
+ * "Plus" mode.
+ *
+ * The kernel wants to prefill the inode cache during readdir. The
+ * filesystem may honour this by filling in the attributes and setting
+ * FUSE_FILL_DIR_FLAGS for the filler function. The filesystem may also
+ * just ignore this flag completely.
+ */
+ FUSE_READDIR_PLUS = (1 << 0),
+};
+
+enum fuse_fill_dir_flags {
+ /**
+ * "Plus" mode: all file attributes are valid
+ *
+ * The attributes are used by the kernel to prefill the inode cache
+ * during a readdir.
+ *
+ * It is okay to set FUSE_FILL_DIR_PLUS if FUSE_READDIR_PLUS is not set
+ * and vice versa.
+ */
+ FUSE_FILL_DIR_PLUS = (1 << 1),
+};
+
+/** Function to add an entry in a readdir() operation
+ *
+ * The *off* parameter can be any non-zero value that enables the
+ * filesystem to identify the current point in the directory
+ * stream. It does not need to be the actual physical position. A
+ * value of zero is reserved to indicate that seeking in directories
+ * is not supported.
+ *
+ * @param buf the buffer passed to the readdir() operation
+ * @param name the file name of the directory entry
+ * @param stat file attributes, can be NULL
+ * @param off offset of the next entry or zero
+ * @param flags fill flags
+ * @return 1 if buffer is full, zero otherwise
+ */
+typedef int (*fuse_fill_dir_t) (void *buf, const char *name,
+ const struct stat *stbuf, off_t off,
+ enum fuse_fill_dir_flags flags);
+/**
+ * Configuration of the high-level API
+ *
+ * This structure is initialized from the arguments passed to
+ * fuse_new(), and then passed to the file system's init() handler
+ * which should ensure that the configuration is compatible with the
+ * file system implementation.
+ */
+struct fuse_config {
+ /**
+ * If `set_gid` is non-zero, the st_gid attribute of each file
+ * is overwritten with the value of `gid`.
+ */
+ int set_gid;
+ unsigned int gid;
+
+ /**
+ * If `set_uid` is non-zero, the st_uid attribute of each file
+ * is overwritten with the value of `uid`.
+ */
+ int set_uid;
+ unsigned int uid;
+
+ /**
+ * If `set_mode` is non-zero, the any permissions bits set in
+ * `umask` are unset in the st_mode attribute of each file.
+ */
+ int set_mode;
+ unsigned int umask;
+
+ /**
+ * The timeout in seconds for which name lookups will be
+ * cached.
+ */
+ double entry_timeout;
+
+ /**
+ * The timeout in seconds for which a negative lookup will be
+ * cached. This means, that if file did not exist (lookup
+ * retuned ENOENT), the lookup will only be redone after the
+ * timeout, and the file/directory will be assumed to not
+ * exist until then. A value of zero means that negative
+ * lookups are not cached.
+ */
+ double negative_timeout;
+
+ /**
+ * The timeout in seconds for which file/directory attributes
+ * (as returned by e.g. the `getattr` handler) are cached.
+ */
+ double attr_timeout;
+
+ /**
+ * Allow requests to be interrupted
+ */
+ int intr;
+
+ /**
+ * Specify which signal number to send to the filesystem when
+ * a request is interrupted. The default is hardcoded to
+ * USR1.
+ */
+ int intr_signal;
+
+ /**
+ * Normally, FUSE assigns inodes to paths only for as long as
+ * the kernel is aware of them. With this option inodes are
+ * instead remembered for at least this many seconds. This
+ * will require more memory, but may be necessary when using
+ * applications that make use of inode numbers.
+ *
+ * A number of -1 means that inodes will be remembered for the
+ * entire life-time of the file-system process.
+ */
+ int remember;
+
+ /**
+ * The default behavior is that if an open file is deleted,
+ * the file is renamed to a hidden file (.fuse_hiddenXXX), and
+ * only removed when the file is finally released. This
+ * relieves the filesystem implementation of having to deal
+ * with this problem. This option disables the hiding
+ * behavior, and files are removed immediately in an unlink
+ * operation (or in a rename operation which overwrites an
+ * existing file).
+ *
+ * It is recommended that you not use the hard_remove
+ * option. When hard_remove is set, the following libc
+ * functions fail on unlinked files (returning errno of
+ * ENOENT): read(2), write(2), fsync(2), close(2), f*xattr(2),
+ * ftruncate(2), fstat(2), fchmod(2), fchown(2)
+ */
+ int hard_remove;
+
+ /**
+ * Honor the st_ino field in the functions getattr() and
+ * fill_dir(). This value is used to fill in the st_ino field
+ * in the stat(2), lstat(2), fstat(2) functions and the d_ino
+ * field in the readdir(2) function. The filesystem does not
+ * have to guarantee uniqueness, however some applications
+ * rely on this value being unique for the whole filesystem.
+ *
+ * Note that this does *not* affect the inode that libfuse
+ * and the kernel use internally (also called the "nodeid").
+ */
+ int use_ino;
+
+ /**
+ * If use_ino option is not given, still try to fill in the
+ * d_ino field in readdir(2). If the name was previously
+ * looked up, and is still in the cache, the inode number
+ * found there will be used. Otherwise it will be set to -1.
+ * If use_ino option is given, this option is ignored.
+ */
+ int readdir_ino;
+
+ /**
+ * This option disables the use of page cache (file content cache)
+ * in the kernel for this filesystem. This has several affects:
+ *
+ * 1. Each read(2) or write(2) system call will initiate one
+ * or more read or write operations, data will not be
+ * cached in the kernel.
+ *
+ * 2. The return value of the read() and write() system calls
+ * will correspond to the return values of the read and
+ * write operations. This is useful for example if the
+ * file size is not known in advance (before reading it).
+ *
+ * Internally, enabling this option causes fuse to set the
+ * `direct_io` field of `struct fuse_file_info` - overwriting
+ * any value that was put there by the file system.
+ */
+ int direct_io;
+
+ /**
+ * This option disables flushing the cache of the file
+ * contents on every open(2). This should only be enabled on
+ * filesystems where the file data is never changed
+ * externally (not through the mounted FUSE filesystem). Thus
+ * it is not suitable for network filesystems and other
+ * intermediate filesystems.
+ *
+ * NOTE: if this option is not specified (and neither
+ * direct_io) data is still cached after the open(2), so a
+ * read(2) system call will not always initiate a read
+ * operation.
+ *
+ * Internally, enabling this option causes fuse to set the
+ * `keep_cache` field of `struct fuse_file_info` - overwriting
+ * any value that was put there by the file system.
+ */
+ int kernel_cache;
+
+ /**
+ * This option is an alternative to `kernel_cache`. Instead of
+ * unconditionally keeping cached data, the cached data is
+ * invalidated on open(2) if if the modification time or the
+ * size of the file has changed since it was last opened.
+ */
+ int auto_cache;
+
+ /**
+ * The timeout in seconds for which file attributes are cached
+ * for the purpose of checking if auto_cache should flush the
+ * file data on open.
+ */
+ int ac_attr_timeout_set;
+ double ac_attr_timeout;
+
+ /**
+ * If this option is given the file-system handlers for the
+ * following operations will not receive path information:
+ * read, write, flush, release, fsync, readdir, releasedir,
+ * fsyncdir, lock, ioctl and poll.
+ *
+ * For the truncate, getattr, chmod, chown and utimens
+ * operations the path will be provided only if the struct
+ * fuse_file_info argument is NULL.
+ */
+ int nullpath_ok;
+
+ /**
+ * The remaining options are used by libfuse internally and
+ * should not be touched.
+ */
+ int show_help;
+ char *modules;
+ int debug;
+};
+
+
+/**
+ * The file system operations:
+ *
+ * Most of these should work very similarly to the well known UNIX
+ * file system operations. A major exception is that instead of
+ * returning an error in 'errno', the operation should return the
+ * negated error value (-errno) directly.
+ *
+ * All methods are optional, but some are essential for a useful
+ * filesystem (e.g. getattr). Open, flush, release, fsync, opendir,
+ * releasedir, fsyncdir, access, create, truncate, lock, init and
+ * destroy are special purpose methods, without which a full featured
+ * filesystem can still be implemented.
+ *
+ * In general, all methods are expected to perform any necessary
+ * permission checking. However, a filesystem may delegate this task
+ * to the kernel by passing the `default_permissions` mount option to
+ * `fuse_new()`. In this case, methods will only be called if
+ * the kernel's permission check has succeeded.
+ *
+ * Almost all operations take a path which can be of any length.
+ */
+struct fuse_operations {
+ /** Get file attributes.
+ *
+ * Similar to stat(). The 'st_dev' and 'st_blksize' fields are
+ * ignored. The 'st_ino' field is ignored except if the 'use_ino'
+ * mount option is given. In that case it is passed to userspace,
+ * but libfuse and the kernel will still assign a different
+ * inode for internal use (called the "nodeid").
+ *
+ * `fi` will always be NULL if the file is not currently open, but
+ * may also be NULL if the file is open.
+ */
+ int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);
+
+ /** Read the target of a symbolic link
+ *
+ * The buffer should be filled with a null terminated string. The
+ * buffer size argument includes the space for the terminating
+ * null character. If the linkname is too long to fit in the
+ * buffer, it should be truncated. The return value should be 0
+ * for success.
+ */
+ int (*readlink) (const char *, char *, size_t);
+
+ /** Create a file node
+ *
+ * This is called for creation of all non-directory, non-symlink
+ * nodes. If the filesystem defines a create() method, then for
+ * regular files that will be called instead.
+ */
+ int (*mknod) (const char *, mode_t, dev_t);
+
+ /** Create a directory
+ *
+ * Note that the mode argument may not have the type specification
+ * bits set, i.e. S_ISDIR(mode) can be false. To obtain the
+ * correct directory type bits use mode|S_IFDIR
+ * */
+ int (*mkdir) (const char *, mode_t);
+
+ /** Remove a file */
+ int (*unlink) (const char *);
+
+ /** Remove a directory */
+ int (*rmdir) (const char *);
+
+ /** Create a symbolic link */
+ int (*symlink) (const char *, const char *);
+
+ /** Rename a file
+ *
+ * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If
+ * RENAME_NOREPLACE is specified, the filesystem must not
+ * overwrite *newname* if it exists and return an error
+ * instead. If `RENAME_EXCHANGE` is specified, the filesystem
+ * must atomically exchange the two files, i.e. both must
+ * exist and neither may be deleted.
+ */
+ int (*rename) (const char *, const char *, unsigned int flags);
+
+ /** Create a hard link to a file */
+ int (*link) (const char *, const char *);
+
+ /** Change the permission bits of a file
+ *
+ * `fi` will always be NULL if the file is not currenlty open, but
+ * may also be NULL if the file is open.
+ */
+ int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);
+
+ /** Change the owner and group of a file
+ *
+ * `fi` will always be NULL if the file is not currenlty open, but
+ * may also be NULL if the file is open.
+ *
+ * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
+ * expected to reset the setuid and setgid bits.
+ */
+ int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);
+
+ /** Change the size of a file
+ *
+ * `fi` will always be NULL if the file is not currenlty open, but
+ * may also be NULL if the file is open.
+ *
+ * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
+ * expected to reset the setuid and setgid bits.
+ */
+ int (*truncate) (const char *, off_t, struct fuse_file_info *fi);
+
+ /** Open a file
+ *
+ * Open flags are available in fi->flags. The following rules
+ * apply.
+ *
+ * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be
+ * filtered out / handled by the kernel.
+ *
+ * - Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH)
+ * should be used by the filesystem to check if the operation is
+ * permitted. If the ``-o default_permissions`` mount option is
+ * given, this check is already done by the kernel before calling
+ * open() and may thus be omitted by the filesystem.
+ *
+ * - When writeback caching is enabled, the kernel may send
+ * read requests even for files opened with O_WRONLY. The
+ * filesystem should be prepared to handle this.
+ *
+ * - When writeback caching is disabled, the filesystem is
+ * expected to properly handle the O_APPEND flag and ensure
+ * that each write is appending to the end of the file.
+ *
+ * - When writeback caching is enabled, the kernel will
+ * handle O_APPEND. However, unless all changes to the file
+ * come through the kernel this will not work reliably. The
+ * filesystem should thus either ignore the O_APPEND flag
+ * (and let the kernel handle it), or return an error
+ * (indicating that reliably O_APPEND is not available).
+ *
+ * Filesystem may store an arbitrary file handle (pointer,
+ * index, etc) in fi->fh, and use this in other all other file
+ * operations (read, write, flush, release, fsync).
+ *
+ * Filesystem may also implement stateless file I/O and not store
+ * anything in fi->fh.
+ *
+ * There are also some flags (direct_io, keep_cache) which the
+ * filesystem may set in fi, to change the way the file is opened.
+ * See fuse_file_info structure in <fuse_common.h> for more details.
+ *
+ * If this request is answered with an error code of ENOSYS
+ * and FUSE_CAP_NO_OPEN_SUPPORT is set in
+ * `fuse_conn_info.capable`, this is treated as success and
+ * future calls to open will also succeed without being send
+ * to the filesystem process.
+ *
+ */
+ int (*open) (const char *, struct fuse_file_info *);
+
+ /** Read data from an open file
+ *
+ * Read should return exactly the number of bytes requested except
+ * on EOF or error, otherwise the rest of the data will be
+ * substituted with zeroes. An exception to this is when the
+ * 'direct_io' mount option is specified, in which case the return
+ * value of the read system call will reflect the return value of
+ * this operation.
+ */
+ int (*read) (const char *, char *, size_t, off_t,
+ struct fuse_file_info *);
+
+ /** Write data to an open file
+ *
+ * Write should return exactly the number of bytes requested
+ * except on error. An exception to this is when the 'direct_io'
+ * mount option is specified (see read operation).
+ *
+ * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
+ * expected to reset the setuid and setgid bits.
+ */
+ int (*write) (const char *, const char *, size_t, off_t,
+ struct fuse_file_info *);
+
+ /** Get file system statistics
+ *
+ * The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored
+ */
+ int (*statfs) (const char *, struct statvfs *);
+
+ /** Possibly flush cached data
+ *
+ * BIG NOTE: This is not equivalent to fsync(). It's not a
+ * request to sync dirty data.
+ *
+ * Flush is called on each close() of a file descriptor, as opposed to
+ * release which is called on the close of the last file descriptor for
+ * a file. Under Linux, errors returned by flush() will be passed to
+ * userspace as errors from close(), so flush() is a good place to write
+ * back any cached dirty data. However, many applications ignore errors
+ * on close(), and on non-Linux systems, close() may succeed even if flush()
+ * returns an error. For these reasons, filesystems should not assume
+ * that errors returned by flush will ever be noticed or even
+ * delivered.
+ *
+ * NOTE: The flush() method may be called more than once for each
+ * open(). This happens if more than one file descriptor refers to an
+ * open file handle, e.g. due to dup(), dup2() or fork() calls. It is
+ * not possible to determine if a flush is final, so each flush should
+ * be treated equally. Multiple write-flush sequences are relatively
+ * rare, so this shouldn't be a problem.
+ *
+ * Filesystems shouldn't assume that flush will be called at any
+ * particular point. It may be called more times than expected, or not
+ * at all.
+ *
+ * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html
+ */
+ int (*flush) (const char *, struct fuse_file_info *);
+
+ /** Release an open file
+ *
+ * Release is called when there are no more references to an open
+ * file: all file descriptors are closed and all memory mappings
+ * are unmapped.
+ *
+ * For every open() call there will be exactly one release() call
+ * with the same flags and file handle. It is possible to
+ * have a file opened more than once, in which case only the last
+ * release will mean, that no more reads/writes will happen on the
+ * file. The return value of release is ignored.
+ */
+ int (*release) (const char *, struct fuse_file_info *);
+
+ /** Synchronize file contents
+ *
+ * If the datasync parameter is non-zero, then only the user data
+ * should be flushed, not the meta data.
+ */
+ int (*fsync) (const char *, int, struct fuse_file_info *);
+
+ /** Set extended attributes */
+ int (*setxattr) (const char *, const char *, const char *, size_t, int);
+
+ /** Get extended attributes */
+ int (*getxattr) (const char *, const char *, char *, size_t);
+
+ /** List extended attributes */
+ int (*listxattr) (const char *, char *, size_t);
+
+ /** Remove extended attributes */
+ int (*removexattr) (const char *, const char *);
+
+ /** Open directory
+ *
+ * Unless the 'default_permissions' mount option is given,
+ * this method should check if opendir is permitted for this
+ * directory. Optionally opendir may also return an arbitrary
+ * filehandle in the fuse_file_info structure, which will be
+ * passed to readdir, releasedir and fsyncdir.
+ */
+ int (*opendir) (const char *, struct fuse_file_info *);
+
+ /** Read directory
+ *
+ * The filesystem may choose between two modes of operation:
+ *
+ * 1) The readdir implementation ignores the offset parameter, and
+ * passes zero to the filler function's offset. The filler
+ * function will not return '1' (unless an error happens), so the
+ * whole directory is read in a single readdir operation.
+ *
+ * 2) The readdir implementation keeps track of the offsets of the
+ * directory entries. It uses the offset parameter and always
+ * passes non-zero offset to the filler function. When the buffer
+ * is full (or an error happens) the filler function will return
+ * '1'.
+ */
+ int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,
+ struct fuse_file_info *, enum fuse_readdir_flags);
+
+ /** Release directory
+ */
+ int (*releasedir) (const char *, struct fuse_file_info *);
+
+ /** Synchronize directory contents
+ *
+ * If the datasync parameter is non-zero, then only the user data
+ * should be flushed, not the meta data
+ */
+ int (*fsyncdir) (const char *, int, struct fuse_file_info *);
+
+ /**
+ * Initialize filesystem
+ *
+ * The return value will passed in the `private_data` field of
+ * `struct fuse_context` to all file operations, and as a
+ * parameter to the destroy() method. It overrides the initial
+ * value provided to fuse_main() / fuse_new().
+ */
+ void *(*init) (struct fuse_conn_info *conn,
+ struct fuse_config *cfg);
+
+ /**
+ * Clean up filesystem
+ *
+ * Called on filesystem exit.
+ */
+ void (*destroy) (void *private_data);
+
+ /**
+ * Check file access permissions
+ *
+ * This will be called for the access() system call. If the
+ * 'default_permissions' mount option is given, this method is not
+ * called.
+ *
+ * This method is not called under Linux kernel versions 2.4.x
+ */
+ int (*access) (const char *, int);
+
+ /**
+ * Create and open a file
+ *
+ * If the file does not exist, first create it with the specified
+ * mode, and then open it.
+ *
+ * If this method is not implemented or under Linux kernel
+ * versions earlier than 2.6.15, the mknod() and open() methods
+ * will be called instead.
+ */
+ int (*create) (const char *, mode_t, struct fuse_file_info *);
+
+ /**
+ * Perform POSIX file locking operation
+ *
+ * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW.
+ *
+ * For the meaning of fields in 'struct flock' see the man page
+ * for fcntl(2). The l_whence field will always be set to
+ * SEEK_SET.
+ *
+ * For checking lock ownership, the 'fuse_file_info->owner'
+ * argument must be used.
+ *
+ * For F_GETLK operation, the library will first check currently
+ * held locks, and if a conflicting lock is found it will return
+ * information without calling this method. This ensures, that
+ * for local locks the l_pid field is correctly filled in. The
+ * results may not be accurate in case of race conditions and in
+ * the presence of hard links, but it's unlikely that an
+ * application would rely on accurate GETLK results in these
+ * cases. If a conflicting lock is not found, this method will be
+ * called, and the filesystem may fill out l_pid by a meaningful
+ * value, or it may leave this field zero.
+ *
+ * For F_SETLK and F_SETLKW the l_pid field will be set to the pid
+ * of the process performing the locking operation.
+ *
+ * Note: if this method is not implemented, the kernel will still
+ * allow file locking to work locally. Hence it is only
+ * interesting for network filesystems and similar.
+ */
+ int (*lock) (const char *, struct fuse_file_info *, int cmd,
+ struct flock *);
+
+ /**
+ * Change the access and modification times of a file with
+ * nanosecond resolution
+ *
+ * This supersedes the old utime() interface. New applications
+ * should use this.
+ *
+ * `fi` will always be NULL if the file is not currenlty open, but
+ * may also be NULL if the file is open.
+ *
+ * See the utimensat(2) man page for details.
+ */
+ int (*utimens) (const char *, const struct timespec tv[2],
+ struct fuse_file_info *fi);
+
+ /**
+ * Map block index within file to block index within device
+ *
+ * Note: This makes sense only for block device backed filesystems
+ * mounted with the 'blkdev' option
+ */
+ int (*bmap) (const char *, size_t blocksize, uint64_t *idx);
+
+ /**
+ * Ioctl
+ *
+ * flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in
+ * 64bit environment. The size and direction of data is
+ * determined by _IOC_*() decoding of cmd. For _IOC_NONE,
+ * data will be NULL, for _IOC_WRITE data is out area, for
+ * _IOC_READ in area and if both are set in/out area. In all
+ * non-NULL cases, the area is of _IOC_SIZE(cmd) bytes.
+ *
+ * If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a
+ * directory file handle.
+ *
+ * Note : the unsigned long request submitted by the application
+ * is truncated to 32 bits.
+ */
+ int (*ioctl) (const char *, unsigned int cmd, void *arg,
+ struct fuse_file_info *, unsigned int flags, void *data);
+
+ /**
+ * Poll for IO readiness events
+ *
+ * Note: If ph is non-NULL, the client should notify
+ * when IO readiness events occur by calling
+ * fuse_notify_poll() with the specified ph.
+ *
+ * Regardless of the number of times poll with a non-NULL ph
+ * is received, single notification is enough to clear all.
+ * Notifying more times incurs overhead but doesn't harm
+ * correctness.
+ *
+ * The callee is responsible for destroying ph with
+ * fuse_pollhandle_destroy() when no longer in use.
+ */
+ int (*poll) (const char *, struct fuse_file_info *,
+ struct fuse_pollhandle *ph, unsigned *reventsp);
+
+ /** Write contents of buffer to an open file
+ *
+ * Similar to the write() method, but data is supplied in a
+ * generic buffer. Use fuse_buf_copy() to transfer data to
+ * the destination.
+ *
+ * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
+ * expected to reset the setuid and setgid bits.
+ */
+ int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,
+ struct fuse_file_info *);
+
+ /** Store data from an open file in a buffer
+ *
+ * Similar to the read() method, but data is stored and
+ * returned in a generic buffer.
+ *
+ * No actual copying of data has to take place, the source
+ * file descriptor may simply be stored in the buffer for
+ * later data transfer.
+ *
+ * The buffer must be allocated dynamically and stored at the
+ * location pointed to by bufp. If the buffer contains memory
+ * regions, they too must be allocated using malloc(). The
+ * allocated memory will be freed by the caller.
+ */
+ int (*read_buf) (const char *, struct fuse_bufvec **bufp,
+ size_t size, off_t off, struct fuse_file_info *);
+ /**
+ * Perform BSD file locking operation
+ *
+ * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN
+ *
+ * Nonblocking requests will be indicated by ORing LOCK_NB to
+ * the above operations
+ *
+ * For more information see the flock(2) manual page.
+ *
+ * Additionally fi->owner will be set to a value unique to
+ * this open file. This same value will be supplied to
+ * ->release() when the file is released.
+ *
+ * Note: if this method is not implemented, the kernel will still
+ * allow file locking to work locally. Hence it is only
+ * interesting for network filesystems and similar.
+ */
+ int (*flock) (const char *, struct fuse_file_info *, int op);
+
+ /**
+ * Allocates space for an open file
+ *
+ * This function ensures that required space is allocated for specified
+ * file. If this function returns success then any subsequent write
+ * request to specified range is guaranteed not to fail because of lack
+ * of space on the file system media.
+ */
+ int (*fallocate) (const char *, int, off_t, off_t,
+ struct fuse_file_info *);
+
+ /**
+ * Copy a range of data from one file to another
+ *
+ * Performs an optimized copy between two file descriptors without the
+ * additional cost of transferring data through the FUSE kernel module
+ * to user space (glibc) and then back into the FUSE filesystem again.
+ *
+ * In case this method is not implemented, glibc falls back to reading
+ * data from the source and writing to the destination. Effectively
+ * doing an inefficient copy of the data.
+ */
+ ssize_t (*copy_file_range) (const char *path_in,
+ struct fuse_file_info *fi_in,
+ off_t offset_in, const char *path_out,
+ struct fuse_file_info *fi_out,
+ off_t offset_out, size_t size, int flags);
+};
+
+/** Extra context that may be needed by some filesystems
+ *
+ * The uid, gid and pid fields are not filled in case of a writepage
+ * operation.
+ */
+struct fuse_context {
+ /** Pointer to the fuse object */
+ struct fuse *fuse;
+
+ /** User ID of the calling process */
+ uid_t uid;
+
+ /** Group ID of the calling process */
+ gid_t gid;
+
+ /** Process ID of the calling thread */
+ pid_t pid;
+
+ /** Private filesystem data */
+ void *private_data;
+
+ /** Umask of the calling process */
+ mode_t umask;
+};
+
+/**
+ * Main function of FUSE.
+ *
+ * This is for the lazy. This is all that has to be called from the
+ * main() function.
+ *
+ * This function does the following:
+ * - parses command line options, and handles --help and
+ * --version
+ * - installs signal handlers for INT, HUP, TERM and PIPE
+ * - registers an exit handler to unmount the filesystem on program exit
+ * - creates a fuse handle
+ * - registers the operations
+ * - calls either the single-threaded or the multi-threaded event loop
+ *
+ * Most file systems will have to parse some file-system specific
+ * arguments before calling this function. It is recommended to do
+ * this with fuse_opt_parse() and a processing function that passes
+ * through any unknown options (this can also be achieved by just
+ * passing NULL as the processing function). That way, the remaining
+ * options can be passed directly to fuse_main().
+ *
+ * fuse_main() accepts all options that can be passed to
+ * fuse_parse_cmdline(), fuse_new(), or fuse_session_new().
+ *
+ * Option parsing skips argv[0], which is assumed to contain the
+ * program name. This element must always be present and is used to
+ * construct a basic ``usage: `` message for the --help
+ * output. argv[0] may also be set to the empty string. In this case
+ * the usage message is suppressed. This can be used by file systems
+ * to print their own usage line first. See hello.c for an example of
+ * how to do this.
+ *
+ * Note: this is currently implemented as a macro.
+ *
+ * The following error codes may be returned from fuse_main():
+ * 1: Invalid option arguments
+ * 2: No mount point specified
+ * 3: FUSE setup failed
+ * 4: Mounting failed
+ * 5: Failed to daemonize (detach from session)
+ * 6: Failed to set up signal handlers
+ * 7: An error occured during the life of the file system
+ *
+ * @param argc the argument counter passed to the main() function
+ * @param argv the argument vector passed to the main() function
+ * @param op the file system operation
+ * @param private_data Initial value for the `private_data`
+ * field of `struct fuse_context`. May be overridden by the
+ * `struct fuse_operations.init` handler.
+ * @return 0 on success, nonzero on failure
+ *
+ * Example usage, see hello.c
+ */
+/*
+ int fuse_main(int argc, char *argv[], const struct fuse_operations *op,
+ void *private_data);
+*/
+#define fuse_main(argc, argv, op, private_data) \
+ fuse_main_real(argc, argv, op, sizeof(*(op)), private_data)
+
+/* ----------------------------------------------------------- *
+ * More detailed API *
+ * ----------------------------------------------------------- */
+
+/**
+ * Print available options (high- and low-level) to stdout. This is
+ * not an exhaustive list, but includes only those options that may be
+ * of interest to an end-user of a file system.
+ *
+ * The function looks at the argument vector only to determine if
+ * there are additional modules to be loaded (module=foo option),
+ * and attempts to call their help functions as well.
+ *
+ * @param args the argument vector.
+ */
+void fuse_lib_help(struct fuse_args *args);
+
+/**
+ * Create a new FUSE filesystem.
+ *
+ * This function accepts most file-system independent mount options
+ * (like context, nodev, ro - see mount(8)), as well as the
+ * FUSE-specific mount options from mount.fuse(8).
+ *
+ * If the --help option is specified, the function writes a help text
+ * to stdout and returns NULL.
+ *
+ * Option parsing skips argv[0], which is assumed to contain the
+ * program name. This element must always be present and is used to
+ * construct a basic ``usage: `` message for the --help output. If
+ * argv[0] is set to the empty string, no usage message is included in
+ * the --help output.
+ *
+ * If an unknown option is passed in, an error message is written to
+ * stderr and the function returns NULL.
+ *
+ * @param args argument vector
+ * @param op the filesystem operations
+ * @param op_size the size of the fuse_operations structure
+ * @param private_data Initial value for the `private_data`
+ * field of `struct fuse_context`. May be overridden by the
+ * `struct fuse_operations.init` handler.
+ * @return the created FUSE handle
+ */
+#if FUSE_USE_VERSION == 30
+struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+ size_t op_size, void *private_data);
+#define fuse_new(args, op, size, data) fuse_new_30(args, op, size, data)
+#else
+struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op,
+ size_t op_size, void *private_data);
+#endif
+
+/**
+ * Mount a FUSE file system.
+ *
+ * @param mountpoint the mount point path
+ * @param f the FUSE handle
+ *
+ * @return 0 on success, -1 on failure.
+ **/
+int fuse_mount(struct fuse *f, const char *mountpoint);
+
+/**
+ * Unmount a FUSE file system.
+ *
+ * See fuse_session_unmount() for additional information.
+ *
+ * @param f the FUSE handle
+ **/
+void fuse_unmount(struct fuse *f);
+
+/**
+ * Destroy the FUSE handle.
+ *
+ * NOTE: This function does not unmount the filesystem. If this is
+ * needed, call fuse_unmount() before calling this function.
+ *
+ * @param f the FUSE handle
+ */
+void fuse_destroy(struct fuse *f);
+
+/**
+ * FUSE event loop.
+ *
+ * Requests from the kernel are processed, and the appropriate
+ * operations are called.
+ *
+ * For a description of the return value and the conditions when the
+ * event loop exits, refer to the documentation of
+ * fuse_session_loop().
+ *
+ * @param f the FUSE handle
+ * @return see fuse_session_loop()
+ *
+ * See also: fuse_loop_mt()
+ */
+int fuse_loop(struct fuse *f);
+
+/**
+ * Flag session as terminated
+ *
+ * This function will cause any running event loops to exit on
+ * the next opportunity.
+ *
+ * @param f the FUSE handle
+ */
+void fuse_exit(struct fuse *f);
+
+/**
+ * FUSE event loop with multiple threads
+ *
+ * Requests from the kernel are processed, and the appropriate
+ * operations are called. Request are processed in parallel by
+ * distributing them between multiple threads.
+ *
+ * For a description of the return value and the conditions when the
+ * event loop exits, refer to the documentation of
+ * fuse_session_loop().
+ *
+ * Note: using fuse_loop() instead of fuse_loop_mt() means you are running in
+ * single-threaded mode, and that you will not have to worry about reentrancy,
+ * though you will have to worry about recursive lookups. In single-threaded
+ * mode, FUSE will wait for one callback to return before calling another.
+ *
+ * Enabling multiple threads, by using fuse_loop_mt(), will cause FUSE to make
+ * multiple simultaneous calls into the various callback functions given by your
+ * fuse_operations record.
+ *
+ * If you are using multiple threads, you can enjoy all the parallel execution
+ * and interactive response benefits of threads, and you get to enjoy all the
+ * benefits of race conditions and locking bugs, too. Ensure that any code used
+ * in the callback function of fuse_operations is also thread-safe.
+ *
+ * @param f the FUSE handle
+ * @param config loop configuration
+ * @return see fuse_session_loop()
+ *
+ * See also: fuse_loop()
+ */
+#if FUSE_USE_VERSION < 32
+int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+#define fuse_loop_mt(f, clone_fd) fuse_loop_mt_31(f, clone_fd)
+#else
+int fuse_loop_mt(struct fuse *f, struct fuse_loop_config *config);
+#endif
+
+/**
+ * Get the current context
+ *
+ * The context is only valid for the duration of a filesystem
+ * operation, and thus must not be stored and used later.
+ *
+ * @return the context
+ */
+struct fuse_context *fuse_get_context(void);
+
+/**
+ * Get the current supplementary group IDs for the current request
+ *
+ * Similar to the getgroups(2) system call, except the return value is
+ * always the total number of group IDs, even if it is larger than the
+ * specified size.
+ *
+ * The current fuse kernel module in linux (as of 2.6.30) doesn't pass
+ * the group list to userspace, hence this function needs to parse
+ * "/proc/$TID/task/$TID/status" to get the group IDs.
+ *
+ * This feature may not be supported on all operating systems. In
+ * such a case this function will return -ENOSYS.
+ *
+ * @param size size of given array
+ * @param list array of group IDs to be filled in
+ * @return the total number of supplementary group IDs or -errno on failure
+ */
+int fuse_getgroups(int size, gid_t list[]);
+
+/**
+ * Check if the current request has already been interrupted
+ *
+ * @return 1 if the request has been interrupted, 0 otherwise
+ */
+int fuse_interrupted(void);
+
+/**
+ * Invalidates cache for the given path.
+ *
+ * This calls fuse_lowlevel_notify_inval_inode internally.
+ *
+ * @return 0 on successful invalidation, negative error value otherwise.
+ * This routine may return -ENOENT to indicate that there was
+ * no entry to be invalidated, e.g., because the path has not
+ * been seen before or has been forgotten; this should not be
+ * considered to be an error.
+ */
+int fuse_invalidate_path(struct fuse *f, const char *path);
+
+/**
+ * The real main function
+ *
+ * Do not call this directly, use fuse_main()
+ */
+int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+ size_t op_size, void *private_data);
+
+/**
+ * Start the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ * @return 0 on success and -1 on error
+ */
+int fuse_start_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Stop the cleanup thread when using option "remember".
+ *
+ * This is done automatically by fuse_loop_mt()
+ * @param fuse struct fuse pointer for fuse instance
+ */
+void fuse_stop_cleanup_thread(struct fuse *fuse);
+
+/**
+ * Iterate over cache removing stale entries
+ * use in conjunction with "-oremember"
+ *
+ * NOTE: This is already done for the standard sessions
+ *
+ * @param fuse struct fuse pointer for fuse instance
+ * @return the number of seconds until the next cleanup
+ */
+int fuse_clean_cache(struct fuse *fuse);
+
+/*
+ * Stacking API
+ */
+
+/**
+ * Fuse filesystem object
+ *
+ * This is opaque object represents a filesystem layer
+ */
+struct fuse_fs;
+
+/*
+ * These functions call the relevant filesystem operation, and return
+ * the result.
+ *
+ * If the operation is not defined, they return -ENOSYS, with the
+ * exception of fuse_fs_open, fuse_fs_release, fuse_fs_opendir,
+ * fuse_fs_releasedir and fuse_fs_statfs, which return 0.
+ */
+
+int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+ struct fuse_file_info *fi);
+int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+ const char *newpath, unsigned int flags);
+int fuse_fs_unlink(struct fuse_fs *fs, const char *path);
+int fuse_fs_rmdir(struct fuse_fs *fs, const char *path);
+int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname,
+ const char *path);
+int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath);
+int fuse_fs_release(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi);
+int fuse_fs_open(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi);
+int fuse_fs_read(struct fuse_fs *fs, const char *path, char *buf, size_t size,
+ off_t off, struct fuse_file_info *fi);
+int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec **bufp, size_t size, off_t off,
+ struct fuse_file_info *fi);
+int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *buf,
+ size_t size, off_t off, struct fuse_file_info *fi);
+int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec *buf, off_t off,
+ struct fuse_file_info *fi);
+int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+ struct fuse_file_info *fi);
+int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi);
+int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf);
+int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi);
+int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+ fuse_fill_dir_t filler, off_t off,
+ struct fuse_file_info *fi, enum fuse_readdir_flags flags);
+int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+ struct fuse_file_info *fi);
+int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi);
+int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+ struct fuse_file_info *fi);
+int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, int cmd, struct flock *lock);
+int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, int op);
+int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+ struct fuse_file_info *fi);
+int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid, gid_t gid,
+ struct fuse_file_info *fi);
+int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+ struct fuse_file_info *fi);
+int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+ const struct timespec tv[2], struct fuse_file_info *fi);
+int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask);
+int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+ size_t len);
+int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+ dev_t rdev);
+int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode);
+int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+ const char *value, size_t size, int flags);
+int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+ char *value, size_t size);
+int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+ size_t size);
+int fuse_fs_removexattr(struct fuse_fs *fs, const char *path,
+ const char *name);
+int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+ uint64_t *idx);
+int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+ void *arg, struct fuse_file_info *fi, unsigned int flags,
+ void *data);
+int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+ unsigned *reventsp);
+int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+ off_t offset, off_t length, struct fuse_file_info *fi);
+ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+ struct fuse_file_info *fi_in, off_t off_in,
+ const char *path_out,
+ struct fuse_file_info *fi_out, off_t off_out,
+ size_t len, int flags);
+void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+ struct fuse_config *cfg);
+void fuse_fs_destroy(struct fuse_fs *fs);
+
+int fuse_notify_poll(struct fuse_pollhandle *ph);
+
+/**
+ * Create a new fuse filesystem object
+ *
+ * This is usually called from the factory of a fuse module to create
+ * a new instance of a filesystem.
+ *
+ * @param op the filesystem operations
+ * @param op_size the size of the fuse_operations structure
+ * @param private_data Initial value for the `private_data`
+ * field of `struct fuse_context`. May be overridden by the
+ * `struct fuse_operations.init` handler.
+ * @return a new filesystem object
+ */
+struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+ void *private_data);
+
+/**
+ * Factory for creating filesystem objects
+ *
+ * The function may use and remove options from 'args' that belong
+ * to this module.
+ *
+ * For now the 'fs' vector always contains exactly one filesystem.
+ * This is the filesystem which will be below the newly created
+ * filesystem in the stack.
+ *
+ * @param args the command line arguments
+ * @param fs NULL terminated filesystem object vector
+ * @return the new filesystem object
+ */
+typedef struct fuse_fs *(*fuse_module_factory_t)(struct fuse_args *args,
+ struct fuse_fs *fs[]);
+/**
+ * Register filesystem module
+ *
+ * If the "-omodules=*name*_:..." option is present, filesystem
+ * objects are created and pushed onto the stack with the *factory_*
+ * function.
+ *
+ * @param name_ the name of this filesystem module
+ * @param factory_ the factory function for this filesystem module
+ */
+#define FUSE_REGISTER_MODULE(name_, factory_) \
+ fuse_module_factory_t fuse_module_ ## name_ ## _factory = factory_
+
+/** Get session from fuse object */
+struct fuse_session *fuse_get_session(struct fuse *f);
+
+/**
+ * Open a FUSE file descriptor and set up the mount for the given
+ * mountpoint and flags.
+ *
+ * @param mountpoint reference to the mount in the file system
+ * @param options mount options
+ * @return the FUSE file descriptor or -1 upon error
+ */
+int fuse_open_channel(const char *mountpoint, const char *options);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FUSE_H_ */
diff --git a/include/fuse_common.h b/include/fuse_common.h
new file mode 100644
index 0000000..0481aac
--- /dev/null
+++ b/include/fuse_common.h
@@ -0,0 +1,812 @@
+/* FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+/** @file */
+
+#if !defined(FUSE_H_) && !defined(FUSE_LOWLEVEL_H_)
+#error "Never include <fuse_common.h> directly; use <fuse.h> or <fuse_lowlevel.h> instead."
+#endif
+
+#ifndef FUSE_COMMON_H_
+#define FUSE_COMMON_H_
+
+#include "fuse_opt.h"
+#include <stdint.h>
+#include <sys/types.h>
+
+/** Major version of FUSE library interface */
+#define FUSE_MAJOR_VERSION 3
+
+/** Minor version of FUSE library interface */
+#define FUSE_MINOR_VERSION 2
+
+#define FUSE_MAKE_VERSION(maj, min) ((maj) * 10 + (min))
+#define FUSE_VERSION FUSE_MAKE_VERSION(FUSE_MAJOR_VERSION, FUSE_MINOR_VERSION)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Information about an open file.
+ *
+ * File Handles are created by the open, opendir, and create methods and closed
+ * by the release and releasedir methods. Multiple file handles may be
+ * concurrently open for the same file. Generally, a client will create one
+ * file handle per file descriptor, though in some cases multiple file
+ * descriptors can share a single file handle.
+ */
+struct fuse_file_info {
+ /** Open flags. Available in open() and release() */
+ int flags;
+
+ /** In case of a write operation indicates if this was caused
+ by a delayed write from the page cache. If so, then the
+ context's pid, uid, and gid fields will not be valid, and
+ the *fh* value may not match the *fh* value that would
+ have been sent with the corresponding individual write
+ requests if write caching had been disabled. */
+ unsigned int writepage : 1;
+
+ /** Can be filled in by open, to use direct I/O on this file. */
+ unsigned int direct_io : 1;
+
+ /** Can be filled in by open. It signals the kernel that any
+ currently cached file data (ie., data that the filesystem
+ provided the last time the file was open) need not be
+ invalidated. Has no effect when set in other contexts (in
+ particular it does nothing when set by opendir()). */
+ unsigned int keep_cache : 1;
+
+ /** Indicates a flush operation. Set in flush operation, also
+ maybe set in highlevel lock operation and lowlevel release
+ operation. */
+ unsigned int flush : 1;
+
+ /** Can be filled in by open, to indicate that the file is not
+ seekable. */
+ unsigned int nonseekable : 1;
+
+ /* Indicates that flock locks for this file should be
+ released. If set, lock_owner shall contain a valid value.
+ May only be set in ->release(). */
+ unsigned int flock_release : 1;
+
+ /** Can be filled in by opendir. It signals the kernel to
+ enable caching of entries returned by readdir(). Has no
+ effect when set in other contexts (in particular it does
+ nothing when set by open()). */
+ unsigned int cache_readdir : 1;
+
+ /** Padding. Reserved for future use*/
+ unsigned int padding : 26;
+
+ /** File handle id. May be filled in by filesystem in create,
+ * open, and opendir(). Available in most other file operations on the
+ * same file handle. */
+ uint64_t fh;
+
+ /** Lock owner id. Available in locking operations and flush */
+ uint64_t lock_owner;
+
+ /** Requested poll events. Available in ->poll. Only set on kernels
+ which support it. If unsupported, this field is set to zero. */
+ uint32_t poll_events;
+};
+
+/**
+ * Configuration parameters passed to fuse_session_loop_mt() and
+ * fuse_loop_mt().
+ */
+struct fuse_loop_config {
+ /**
+ * whether to use separate device fds for each thread
+ * (may increase performance)
+ */
+ int clone_fd;
+
+ /**
+ * The maximum number of available worker threads before they
+ * start to get deleted when they become idle. If not
+ * specified, the default is 10.
+ *
+ * Adjusting this has performance implications; a very small number
+ * of threads in the pool will cause a lot of thread creation and
+ * deletion overhead and performance may suffer. When set to 0, a new
+ * thread will be created to service every operation.
+ */
+ unsigned int max_idle_threads;
+};
+
+/**************************************************************************
+ * Capability bits for 'fuse_conn_info.capable' and 'fuse_conn_info.want' *
+ **************************************************************************/
+
+/**
+ * Indicates that the filesystem supports asynchronous read requests.
+ *
+ * If this capability is not requested/available, the kernel will
+ * ensure that there is at most one pending read request per
+ * file-handle at any time, and will attempt to order read requests by
+ * increasing offset.
+ *
+ * This feature is enabled by default when supported by the kernel.
+ */
+#define FUSE_CAP_ASYNC_READ (1 << 0)
+
+/**
+ * Indicates that the filesystem supports "remote" locking.
+ *
+ * This feature is enabled by default when supported by the kernel,
+ * and if getlk() and setlk() handlers are implemented.
+ */
+#define FUSE_CAP_POSIX_LOCKS (1 << 1)
+
+/**
+ * Indicates that the filesystem supports the O_TRUNC open flag. If
+ * disabled, and an application specifies O_TRUNC, fuse first calls
+ * truncate() and then open() with O_TRUNC filtered out.
+ *
+ * This feature is enabled by default when supported by the kernel.
+ */
+#define FUSE_CAP_ATOMIC_O_TRUNC (1 << 3)
+
+/**
+ * Indicates that the filesystem supports lookups of "." and "..".
+ *
+ * This feature is disabled by default.
+ */
+#define FUSE_CAP_EXPORT_SUPPORT (1 << 4)
+
+/**
+ * Indicates that the kernel should not apply the umask to the
+ * file mode on create operations.
+ *
+ * This feature is disabled by default.
+ */
+#define FUSE_CAP_DONT_MASK (1 << 6)
+
+/**
+ * Indicates that libfuse should try to use splice() when writing to
+ * the fuse device. This may improve performance.
+ *
+ * This feature is disabled by default.
+ */
+#define FUSE_CAP_SPLICE_WRITE (1 << 7)
+
+/**
+ * Indicates that libfuse should try to move pages instead of copying when
+ * writing to / reading from the fuse device. This may improve performance.
+ *
+ * This feature is disabled by default.
+ */
+#define FUSE_CAP_SPLICE_MOVE (1 << 8)
+
+/**
+ * Indicates that libfuse should try to use splice() when reading from
+ * the fuse device. This may improve performance.
+ *
+ * This feature is enabled by default when supported by the kernel and
+ * if the filesystem implements a write_buf() handler.
+ */
+#define FUSE_CAP_SPLICE_READ (1 << 9)
+
+/**
+ * If set, the calls to flock(2) will be emulated using POSIX locks and must
+ * then be handled by the filesystem's setlock() handler.
+ *
+ * If not set, flock(2) calls will be handled by the FUSE kernel module
+ * internally (so any access that does not go through the kernel cannot be taken
+ * into account).
+ *
+ * This feature is enabled by default when supported by the kernel and
+ * if the filesystem implements a flock() handler.
+ */
+#define FUSE_CAP_FLOCK_LOCKS (1 << 10)
+
+/**
+ * Indicates that the filesystem supports ioctl's on directories.
+ *
+ * This feature is enabled by default when supported by the kernel.
+ */
+#define FUSE_CAP_IOCTL_DIR (1 << 11)
+
+/**
+ * Traditionally, while a file is open the FUSE kernel module only
+ * asks the filesystem for an update of the file's attributes when a
+ * client attempts to read beyond EOF. This is unsuitable for
+ * e.g. network filesystems, where the file contents may change
+ * without the kernel knowing about it.
+ *
+ * If this flag is set, FUSE will check the validity of the attributes
+ * on every read. If the attributes are no longer valid (i.e., if the
+ * *attr_timeout* passed to fuse_reply_attr() or set in `struct
+ * fuse_entry_param` has passed), it will first issue a `getattr`
+ * request. If the new mtime differs from the previous value, any
+ * cached file *contents* will be invalidated as well.
+ *
+ * This flag should always be set when available. If all file changes
+ * go through the kernel, *attr_timeout* should be set to a very large
+ * number to avoid unnecessary getattr() calls.
+ *
+ * This feature is enabled by default when supported by the kernel.
+ */
+#define FUSE_CAP_AUTO_INVAL_DATA (1 << 12)
+
+/**
+ * Indicates that the filesystem supports readdirplus.
+ *
+ * This feature is enabled by default when supported by the kernel and if the
+ * filesystem implements a readdirplus() handler.
+ */
+#define FUSE_CAP_READDIRPLUS (1 << 13)
+
+/**
+ * Indicates that the filesystem supports adaptive readdirplus.
+ *
+ * If FUSE_CAP_READDIRPLUS is not set, this flag has no effect.
+ *
+ * If FUSE_CAP_READDIRPLUS is set and this flag is not set, the kernel
+ * will always issue readdirplus() requests to retrieve directory
+ * contents.
+ *
+ * If FUSE_CAP_READDIRPLUS is set and this flag is set, the kernel
+ * will issue both readdir() and readdirplus() requests, depending on
+ * how much information is expected to be required.
+ *
+ * This feature is enabled by default when supported by the kernel and
+ * if the filesystem implements both a readdirplus() and a readdir()
+ * handler.
+ */
+#define FUSE_CAP_READDIRPLUS_AUTO (1 << 14)
+
+/**
+ * Indicates that the filesystem supports asynchronous direct I/O submission.
+ *
+ * If this capability is not requested/available, the kernel will ensure that
+ * there is at most one pending read and one pending write request per direct
+ * I/O file-handle at any time.
+ *
+ * This feature is enabled by default when supported by the kernel.
+ */
+#define FUSE_CAP_ASYNC_DIO (1 << 15)
+
+/**
+ * Indicates that writeback caching should be enabled. This means that
+ * individual write request may be buffered and merged in the kernel
+ * before they are send to the filesystem.
+ *
+ * This feature is disabled by default.
+ */
+#define FUSE_CAP_WRITEBACK_CACHE (1 << 16)
+
+/**
+ * Indicates support for zero-message opens. If this flag is set in
+ * the `capable` field of the `fuse_conn_info` structure, then the
+ * filesystem may return `ENOSYS` from the open() handler to indicate
+ * success. Further attempts to open files will be handled in the
+ * kernel. (If this flag is not set, returning ENOSYS will be treated
+ * as an error and signaled to the caller).
+ *
+ * Setting (or unsetting) this flag in the `want` field has *no
+ * effect*.
+ */
+#define FUSE_CAP_NO_OPEN_SUPPORT (1 << 17)
+
+/**
+ * Indicates support for parallel directory operations. If this flag
+ * is unset, the FUSE kernel module will ensure that lookup() and
+ * readdir() requests are never issued concurrently for the same
+ * directory.
+ *
+ * This feature is enabled by default when supported by the kernel.
+ */
+#define FUSE_CAP_PARALLEL_DIROPS (1 << 18)
+
+/**
+ * Indicates support for POSIX ACLs.
+ *
+ * If this feature is enabled, the kernel will cache and have
+ * responsibility for enforcing ACLs. ACL will be stored as xattrs and
+ * passed to userspace, which is responsible for updating the ACLs in
+ * the filesystem, keeping the file mode in sync with the ACL, and
+ * ensuring inheritance of default ACLs when new filesystem nodes are
+ * created. Note that this requires that the file system is able to
+ * parse and interpret the xattr representation of ACLs.
+ *
+ * Enabling this feature implicitly turns on the
+ * ``default_permissions`` mount option (even if it was not passed to
+ * mount(2)).
+ *
+ * This feature is disabled by default.
+ */
+#define FUSE_CAP_POSIX_ACL (1 << 19)
+
+/**
+ * Indicates that the filesystem is responsible for unsetting
+ * setuid and setgid bits when a file is written, truncated, or
+ * its owner is changed.
+ *
+ * This feature is enabled by default when supported by the kernel.
+ */
+#define FUSE_CAP_HANDLE_KILLPRIV (1 << 20)
+
+/**
+ * Indicates support for zero-message opendirs. If this flag is set in
+ * the `capable` field of the `fuse_conn_info` structure, then the filesystem
+ * may return `ENOSYS` from the opendir() handler to indicate success. Further
+ * opendir and releasedir messages will be handled in the kernel. (If this
+ * flag is not set, returning ENOSYS will be treated as an error and signalled
+ * to the caller.)
+ *
+ * Setting (or unsetting) this flag in the `want` field has *no effect*.
+ */
+#define FUSE_CAP_NO_OPENDIR_SUPPORT (1 << 24)
+
+/**
+ * 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_DIR: is a directory
+ *
+ * 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_DIR (1 << 4)
+
+#define FUSE_IOCTL_MAX_IOV 256
+
+/**
+ * Connection information, passed to the ->init() method
+ *
+ * Some of the elements are read-write, these can be changed to
+ * indicate the value requested by the filesystem. The requested
+ * value must usually be smaller than the indicated value.
+ */
+struct fuse_conn_info {
+ /**
+ * Major version of the protocol (read-only)
+ */
+ unsigned proto_major;
+
+ /**
+ * Minor version of the protocol (read-only)
+ */
+ unsigned proto_minor;
+
+ /**
+ * Maximum size of the write buffer
+ */
+ unsigned max_write;
+
+ /**
+ * Maximum size of read requests. A value of zero indicates no
+ * limit. However, even if the filesystem does not specify a
+ * limit, the maximum size of read requests will still be
+ * limited by the kernel.
+ *
+ * NOTE: For the time being, the maximum size of read requests
+ * must be set both here *and* passed to fuse_session_new()
+ * using the ``-o max_read=<n>`` mount option. At some point
+ * in the future, specifying the mount option will no longer
+ * be necessary.
+ */
+ unsigned max_read;
+
+ /**
+ * Maximum readahead
+ */
+ unsigned max_readahead;
+
+ /**
+ * Capability flags that the kernel supports (read-only)
+ */
+ unsigned capable;
+
+ /**
+ * Capability flags that the filesystem wants to enable.
+ *
+ * libfuse attempts to initialize this field with
+ * reasonable default values before calling the init() handler.
+ */
+ unsigned want;
+
+ /**
+ * Maximum number of pending "background" requests. A
+ * background request is any type of request for which the
+ * total number is not limited by other means. As of kernel
+ * 4.8, only two types of requests fall into this category:
+ *
+ * 1. Read-ahead requests
+ * 2. Asynchronous direct I/O requests
+ *
+ * Read-ahead requests are generated (if max_readahead is
+ * non-zero) by the kernel to preemptively fill its caches
+ * when it anticipates that userspace will soon read more
+ * data.
+ *
+ * Asynchronous direct I/O requests are generated if
+ * FUSE_CAP_ASYNC_DIO is enabled and userspace submits a large
+ * direct I/O request. In this case the kernel will internally
+ * split it up into multiple smaller requests and submit them
+ * to the filesystem concurrently.
+ *
+ * Note that the following requests are *not* background
+ * requests: writeback requests (limited by the kernel's
+ * flusher algorithm), regular (i.e., synchronous and
+ * buffered) userspace read/write requests (limited to one per
+ * thread), asynchronous read requests (Linux's io_submit(2)
+ * call actually blocks, so these are also limited to one per
+ * thread).
+ */
+ unsigned max_background;
+
+ /**
+ * Kernel congestion threshold parameter. If the number of pending
+ * background requests exceeds this number, the FUSE kernel module will
+ * mark the filesystem as "congested". This instructs the kernel to
+ * expect that queued requests will take some time to complete, and to
+ * adjust its algorithms accordingly (e.g. by putting a waiting thread
+ * to sleep instead of using a busy-loop).
+ */
+ unsigned congestion_threshold;
+
+ /**
+ * When FUSE_CAP_WRITEBACK_CACHE is enabled, the kernel is responsible
+ * for updating mtime and ctime when write requests are received. The
+ * updated values are passed to the filesystem with setattr() requests.
+ * However, if the filesystem does not support the full resolution of
+ * the kernel timestamps (nanoseconds), the mtime and ctime values used
+ * by kernel and filesystem will differ (and result in an apparent
+ * change of times after a cache flush).
+ *
+ * To prevent this problem, this variable can be used to inform the
+ * kernel about the timestamp granularity supported by the file-system.
+ * The value should be power of 10. The default is 1, i.e. full
+ * nano-second resolution. Filesystems supporting only second resolution
+ * should set this to 1000000000.
+ */
+ unsigned time_gran;
+
+ /**
+ * For future use.
+ */
+ unsigned reserved[22];
+};
+
+struct fuse_session;
+struct fuse_pollhandle;
+struct fuse_conn_info_opts;
+
+/**
+ * This function parses several command-line options that can be used
+ * to override elements of struct fuse_conn_info. The pointer returned
+ * by this function should be passed to the
+ * fuse_apply_conn_info_opts() method by the file system's init()
+ * handler.
+ *
+ * Before using this function, think twice if you really want these
+ * parameters to be adjustable from the command line. In most cases,
+ * they should be determined by the file system internally.
+ *
+ * The following options are recognized:
+ *
+ * -o max_write=N sets conn->max_write
+ * -o max_readahead=N sets conn->max_readahead
+ * -o max_background=N sets conn->max_background
+ * -o congestion_threshold=N sets conn->congestion_threshold
+ * -o async_read sets FUSE_CAP_ASYNC_READ in conn->want
+ * -o sync_read unsets FUSE_CAP_ASYNC_READ in conn->want
+ * -o atomic_o_trunc sets FUSE_CAP_ATOMIC_O_TRUNC in conn->want
+ * -o no_remote_lock Equivalent to -o no_remote_flock,no_remote_posix_lock
+ * -o no_remote_flock Unsets FUSE_CAP_FLOCK_LOCKS in conn->want
+ * -o no_remote_posix_lock Unsets FUSE_CAP_POSIX_LOCKS in conn->want
+ * -o [no_]splice_write (un-)sets FUSE_CAP_SPLICE_WRITE in conn->want
+ * -o [no_]splice_move (un-)sets FUSE_CAP_SPLICE_MOVE in conn->want
+ * -o [no_]splice_read (un-)sets FUSE_CAP_SPLICE_READ in conn->want
+ * -o [no_]auto_inval_data (un-)sets FUSE_CAP_AUTO_INVAL_DATA in conn->want
+ * -o readdirplus=no unsets FUSE_CAP_READDIRPLUS in conn->want
+ * -o readdirplus=yes sets FUSE_CAP_READDIRPLUS and unsets
+ * FUSE_CAP_READDIRPLUS_AUTO in conn->want
+ * -o readdirplus=auto sets FUSE_CAP_READDIRPLUS and
+ * FUSE_CAP_READDIRPLUS_AUTO in conn->want
+ * -o [no_]async_dio (un-)sets FUSE_CAP_ASYNC_DIO in conn->want
+ * -o [no_]writeback_cache (un-)sets FUSE_CAP_WRITEBACK_CACHE in conn->want
+ * -o time_gran=N sets conn->time_gran
+ *
+ * Known options will be removed from *args*, unknown options will be
+ * passed through unchanged.
+ *
+ * @param args argument vector (input+output)
+ * @return parsed options
+ **/
+struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args);
+
+/**
+ * This function applies the (parsed) parameters in *opts* to the
+ * *conn* pointer. It may modify the following fields: wants,
+ * max_write, max_readahead, congestion_threshold, max_background,
+ * time_gran. A field is only set (or unset) if the corresponding
+ * option has been explicitly set.
+ */
+void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+ struct fuse_conn_info *conn);
+
+/**
+ * Go into the background
+ *
+ * @param foreground if true, stay in the foreground
+ * @return 0 on success, -1 on failure
+ */
+int fuse_daemonize(int foreground);
+
+/**
+ * Get the version of the library
+ *
+ * @return the version
+ */
+int fuse_version(void);
+
+/**
+ * Get the full package version string of the library
+ *
+ * @return the package version
+ */
+const char *fuse_pkgversion(void);
+
+/**
+ * Destroy poll handle
+ *
+ * @param ph the poll handle
+ */
+void fuse_pollhandle_destroy(struct fuse_pollhandle *ph);
+
+/* ----------------------------------------------------------- *
+ * Data buffer *
+ * ----------------------------------------------------------- */
+
+/**
+ * Buffer flags
+ */
+enum fuse_buf_flags {
+ /**
+ * Buffer contains a file descriptor
+ *
+ * If this flag is set, the .fd field is valid, otherwise the
+ * .mem fields is valid.
+ */
+ FUSE_BUF_IS_FD = (1 << 1),
+
+ /**
+ * Seek on the file descriptor
+ *
+ * If this flag is set then the .pos field is valid and is
+ * used to seek to the given offset before performing
+ * operation on file descriptor.
+ */
+ FUSE_BUF_FD_SEEK = (1 << 2),
+
+ /**
+ * Retry operation on file descriptor
+ *
+ * If this flag is set then retry operation on file descriptor
+ * until .size bytes have been copied or an error or EOF is
+ * detected.
+ */
+ FUSE_BUF_FD_RETRY = (1 << 3),
+};
+
+/**
+ * Buffer copy flags
+ */
+enum fuse_buf_copy_flags {
+ /**
+ * Don't use splice(2)
+ *
+ * Always fall back to using read and write instead of
+ * splice(2) to copy data from one file descriptor to another.
+ *
+ * If this flag is not set, then only fall back if splice is
+ * unavailable.
+ */
+ FUSE_BUF_NO_SPLICE = (1 << 1),
+
+ /**
+ * Force splice
+ *
+ * Always use splice(2) to copy data from one file descriptor
+ * to another. If splice is not available, return -EINVAL.
+ */
+ FUSE_BUF_FORCE_SPLICE = (1 << 2),
+
+ /**
+ * Try to move data with splice.
+ *
+ * If splice is used, try to move pages from the source to the
+ * destination instead of copying. See documentation of
+ * SPLICE_F_MOVE in splice(2) man page.
+ */
+ FUSE_BUF_SPLICE_MOVE = (1 << 3),
+
+ /**
+ * Don't block on the pipe when copying data with splice
+ *
+ * Makes the operations on the pipe non-blocking (if the pipe
+ * is full or empty). See SPLICE_F_NONBLOCK in the splice(2)
+ * man page.
+ */
+ FUSE_BUF_SPLICE_NONBLOCK= (1 << 4),
+};
+
+/**
+ * Single data buffer
+ *
+ * Generic data buffer for I/O, extended attributes, etc... Data may
+ * be supplied as a memory pointer or as a file descriptor
+ */
+struct fuse_buf {
+ /**
+ * Size of data in bytes
+ */
+ size_t size;
+
+ /**
+ * Buffer flags
+ */
+ enum fuse_buf_flags flags;
+
+ /**
+ * Memory pointer
+ *
+ * Used unless FUSE_BUF_IS_FD flag is set.
+ */
+ void *mem;
+
+ /**
+ * File descriptor
+ *
+ * Used if FUSE_BUF_IS_FD flag is set.
+ */
+ int fd;
+
+ /**
+ * File position
+ *
+ * Used if FUSE_BUF_FD_SEEK flag is set.
+ */
+ off_t pos;
+};
+
+/**
+ * Data buffer vector
+ *
+ * An array of data buffers, each containing a memory pointer or a
+ * file descriptor.
+ *
+ * Allocate dynamically to add more than one buffer.
+ */
+struct fuse_bufvec {
+ /**
+ * Number of buffers in the array
+ */
+ size_t count;
+
+ /**
+ * Index of current buffer within the array
+ */
+ size_t idx;
+
+ /**
+ * Current offset within the current buffer
+ */
+ size_t off;
+
+ /**
+ * Array of buffers
+ */
+ struct fuse_buf buf[1];
+};
+
+/* Initialize bufvec with a single buffer of given size */
+#define FUSE_BUFVEC_INIT(size__) \
+ ((struct fuse_bufvec) { \
+ /* .count= */ 1, \
+ /* .idx = */ 0, \
+ /* .off = */ 0, \
+ /* .buf = */ { /* [0] = */ { \
+ /* .size = */ (size__), \
+ /* .flags = */ (enum fuse_buf_flags) 0, \
+ /* .mem = */ NULL, \
+ /* .fd = */ -1, \
+ /* .pos = */ 0, \
+ } } \
+ } )
+
+/**
+ * Get total size of data in a fuse buffer vector
+ *
+ * @param bufv buffer vector
+ * @return size of data
+ */
+size_t fuse_buf_size(const struct fuse_bufvec *bufv);
+
+/**
+ * Copy data from one buffer vector to another
+ *
+ * @param dst destination buffer vector
+ * @param src source buffer vector
+ * @param flags flags controlling the copy
+ * @return actual number of bytes copied or -errno on error
+ */
+ssize_t fuse_buf_copy(struct fuse_bufvec *dst, struct fuse_bufvec *src,
+ enum fuse_buf_copy_flags flags);
+
+/* ----------------------------------------------------------- *
+ * Signal handling *
+ * ----------------------------------------------------------- */
+
+/**
+ * Exit session on HUP, TERM and INT signals and ignore PIPE signal
+ *
+ * Stores session in a global variable. May only be called once per
+ * process until fuse_remove_signal_handlers() is called.
+ *
+ * Once either of the POSIX signals arrives, the signal handler calls
+ * fuse_session_exit().
+ *
+ * @param se the session to exit
+ * @return 0 on success, -1 on failure
+ *
+ * See also:
+ * fuse_remove_signal_handlers()
+ */
+int fuse_set_signal_handlers(struct fuse_session *se);
+
+/**
+ * Restore default signal handlers
+ *
+ * Resets global session. After this fuse_set_signal_handlers() may
+ * be called again.
+ *
+ * @param se the same session as given in fuse_set_signal_handlers()
+ *
+ * See also:
+ * fuse_set_signal_handlers()
+ */
+void fuse_remove_signal_handlers(struct fuse_session *se);
+
+/* ----------------------------------------------------------- *
+ * Compatibility stuff *
+ * ----------------------------------------------------------- */
+
+#if !defined(FUSE_USE_VERSION) || FUSE_USE_VERSION < 30
+# error only API version 30 or greater is supported
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+
+/*
+ * This interface uses 64 bit off_t.
+ *
+ * On 32bit systems please add -D_FILE_OFFSET_BITS=64 to your compile flags!
+ */
+
+#if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 6) && !defined __cplusplus
+_Static_assert(sizeof(off_t) == 8, "fuse: off_t must be 64bit");
+#else
+struct _fuse_off_t_must_be_64bit_dummy_struct \
+ { unsigned _fuse_off_t_must_be_64bit:((sizeof(off_t) == 8) ? 1 : -1); };
+#endif
+
+#endif /* FUSE_COMMON_H_ */
diff --git a/include/fuse_kernel.h b/include/fuse_kernel.h
new file mode 100644
index 0000000..2971d29
--- /dev/null
+++ b/include/fuse_kernel.h
@@ -0,0 +1,848 @@
+/* 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.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
+ */
+
+#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 31
+
+/** 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 padding;
+};
+
+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)
+
+/**
+ * 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)
+ */
+#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)
+
+/**
+ * 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
+ */
+#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)
+
+/**
+ * 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_PRIV: kill suid and sgid bits
+ */
+#define FUSE_WRITE_CACHE (1 << 0)
+#define FUSE_WRITE_LOCKOWNER (1 << 1)
+#define FUSE_WRITE_KILL_PRIV (1 << 2)
+
+/**
+ * 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)
+
+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,
+
+ /* CUSE specific operations */
+ CUSE_INIT = 4096,
+};
+
+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_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;
+};
+
+#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 unused;
+};
+
+struct fuse_create_in {
+ uint32_t flags;
+ uint32_t mode;
+ uint32_t umask;
+ uint32_t padding;
+};
+
+struct fuse_open_out {
+ uint64_t fh;
+ uint32_t open_flags;
+ uint32_t padding;
+};
+
+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;
+};
+
+struct fuse_setxattr_in {
+ uint32_t size;
+ uint32_t flags;
+};
+
+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;
+};
+
+#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 padding;
+ uint32_t unused[8];
+};
+
+#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;
+};
+
+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;
+ uint32_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[];
+};
+
+#define FUSE_NAME_OFFSET offsetof(struct fuse_dirent, name)
+#define FUSE_DIRENT_ALIGN(x) \
+ (((x) + sizeof(uint64_t) - 1) & ~(sizeof(uint64_t) - 1))
+#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 padding;
+};
+
+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;
+};
+
+/* Device ioctls: */
+#define FUSE_DEV_IOC_CLONE _IOR(229, 0, 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;
+};
+
+#endif /* _LINUX_FUSE_H */
diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
new file mode 100644
index 0000000..2073460
--- /dev/null
+++ b/include/fuse_lowlevel.h
@@ -0,0 +1,2056 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#ifndef FUSE_LOWLEVEL_H_
+#define FUSE_LOWLEVEL_H_
+
+/** @file
+ *
+ * Low level API
+ *
+ * IMPORTANT: you should define FUSE_USE_VERSION before including this
+ * header. To use the newest API define it to 31 (recommended for any
+ * new application).
+ */
+
+#ifndef FUSE_USE_VERSION
+#error FUSE_USE_VERSION not defined
+#endif
+
+#include "fuse_common.h"
+
+#include <utime.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/uio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ----------------------------------------------------------- *
+ * Miscellaneous definitions *
+ * ----------------------------------------------------------- */
+
+/** The node ID of the root inode */
+#define FUSE_ROOT_ID 1
+
+/** Inode number type */
+typedef uint64_t fuse_ino_t;
+
+/** Request pointer type */
+typedef struct fuse_req *fuse_req_t;
+
+/**
+ * Session
+ *
+ * This provides hooks for processing requests, and exiting
+ */
+struct fuse_session;
+
+/** Directory entry parameters supplied to fuse_reply_entry() */
+struct fuse_entry_param {
+ /** Unique inode number
+ *
+ * In lookup, zero means negative entry (from version 2.5)
+ * Returning ENOENT also means negative entry, but by setting zero
+ * ino the kernel may cache negative entries for entry_timeout
+ * seconds.
+ */
+ fuse_ino_t ino;
+
+ /** Generation number for this entry.
+ *
+ * If the file system will be exported over NFS, the
+ * ino/generation pairs need to be unique over the file
+ * system's lifetime (rather than just the mount time). So if
+ * the file system reuses an inode after it has been deleted,
+ * it must assign a new, previously unused generation number
+ * to the inode at the same time.
+ *
+ */
+ uint64_t generation;
+
+ /** Inode attributes.
+ *
+ * Even if attr_timeout == 0, attr must be correct. For example,
+ * for open(), FUSE uses attr.st_size from lookup() to determine
+ * how many bytes to request. If this value is not correct,
+ * incorrect data will be returned.
+ */
+ struct stat attr;
+
+ /** Validity timeout (in seconds) for inode attributes. If
+ attributes only change as a result of requests that come
+ through the kernel, this should be set to a very large
+ value. */
+ double attr_timeout;
+
+ /** Validity timeout (in seconds) for the name. If directory
+ entries are changed/deleted only as a result of requests
+ that come through the kernel, this should be set to a very
+ large value. */
+ double entry_timeout;
+};
+
+/**
+ * Additional context associated with requests.
+ *
+ * Note that the reported client uid, gid and pid may be zero in some
+ * situations. For example, if the FUSE file system is running in a
+ * PID or user namespace but then accessed from outside the namespace,
+ * there is no valid uid/pid/gid that could be reported.
+ */
+struct fuse_ctx {
+ /** User ID of the calling process */
+ uid_t uid;
+
+ /** Group ID of the calling process */
+ gid_t gid;
+
+ /** Thread ID of the calling process */
+ pid_t pid;
+
+ /** Umask of the calling process */
+ mode_t umask;
+};
+
+struct fuse_forget_data {
+ fuse_ino_t ino;
+ uint64_t nlookup;
+};
+
+/* 'to_set' flags in setattr */
+#define FUSE_SET_ATTR_MODE (1 << 0)
+#define FUSE_SET_ATTR_UID (1 << 1)
+#define FUSE_SET_ATTR_GID (1 << 2)
+#define FUSE_SET_ATTR_SIZE (1 << 3)
+#define FUSE_SET_ATTR_ATIME (1 << 4)
+#define FUSE_SET_ATTR_MTIME (1 << 5)
+#define FUSE_SET_ATTR_ATIME_NOW (1 << 7)
+#define FUSE_SET_ATTR_MTIME_NOW (1 << 8)
+#define FUSE_SET_ATTR_CTIME (1 << 10)
+
+/* ----------------------------------------------------------- *
+ * Request methods and replies *
+ * ----------------------------------------------------------- */
+
+/**
+ * Low level filesystem operations
+ *
+ * Most of the methods (with the exception of init and destroy)
+ * receive a request handle (fuse_req_t) as their first argument.
+ * This handle must be passed to one of the specified reply functions.
+ *
+ * This may be done inside the method invocation, or after the call
+ * has returned. The request handle is valid until one of the reply
+ * functions is called.
+ *
+ * Other pointer arguments (name, fuse_file_info, etc) are not valid
+ * after the call has returned, so if they are needed later, their
+ * contents have to be copied.
+ *
+ * In general, all methods are expected to perform any necessary
+ * permission checking. However, a filesystem may delegate this task
+ * to the kernel by passing the `default_permissions` mount option to
+ * `fuse_session_new()`. In this case, methods will only be called if
+ * the kernel's permission check has succeeded.
+ *
+ * The filesystem sometimes needs to handle a return value of -ENOENT
+ * from the reply function, which means, that the request was
+ * interrupted, and the reply discarded. For example if
+ * fuse_reply_open() return -ENOENT means, that the release method for
+ * this file will not be called.
+ */
+struct fuse_lowlevel_ops {
+ /**
+ * Initialize filesystem
+ *
+ * This function is called when libfuse establishes
+ * communication with the FUSE kernel module. The file system
+ * should use this module to inspect and/or modify the
+ * connection parameters provided in the `conn` structure.
+ *
+ * Note that some parameters may be overwritten by options
+ * passed to fuse_session_new() which take precedence over the
+ * values set in this handler.
+ *
+ * There's no reply to this function
+ *
+ * @param userdata the user data passed to fuse_session_new()
+ */
+ void (*init) (void *userdata, struct fuse_conn_info *conn);
+
+ /**
+ * Clean up filesystem.
+ *
+ * Called on filesystem exit. When this method is called, the
+ * connection to the kernel may be gone already, so that eg. calls
+ * to fuse_lowlevel_notify_* will fail.
+ *
+ * There's no reply to this function
+ *
+ * @param userdata the user data passed to fuse_session_new()
+ */
+ void (*destroy) (void *userdata);
+
+ /**
+ * Look up a directory entry by name and get its attributes.
+ *
+ * Valid replies:
+ * fuse_reply_entry
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the parent directory
+ * @param name the name to look up
+ */
+ void (*lookup) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
+ /**
+ * Forget about an inode
+ *
+ * This function is called when the kernel removes an inode
+ * from its internal caches.
+ *
+ * The inode's lookup count increases by one for every call to
+ * fuse_reply_entry and fuse_reply_create. The nlookup parameter
+ * indicates by how much the lookup count should be decreased.
+ *
+ * Inodes with a non-zero lookup count may receive request from
+ * the kernel even after calls to unlink, rmdir or (when
+ * overwriting an existing file) rename. Filesystems must handle
+ * such requests properly and it is recommended to defer removal
+ * of the inode until the lookup count reaches zero. Calls to
+ * unlink, rmdir or rename will be followed closely by forget
+ * unless the file or directory is open, in which case the
+ * kernel issues forget only after the release or releasedir
+ * calls.
+ *
+ * Note that if a file system will be exported over NFS the
+ * inodes lifetime must extend even beyond forget. See the
+ * generation field in struct fuse_entry_param above.
+ *
+ * On unmount the lookup count for all inodes implicitly drops
+ * to zero. It is not guaranteed that the file system will
+ * receive corresponding forget messages for the affected
+ * inodes.
+ *
+ * Valid replies:
+ * fuse_reply_none
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param nlookup the number of lookups to forget
+ */
+ void (*forget) (fuse_req_t req, fuse_ino_t ino, uint64_t nlookup);
+
+ /**
+ * Get file attributes.
+ *
+ * If writeback caching is enabled, the kernel may have a
+ * better idea of a file's length than the FUSE file system
+ * (eg if there has been a write that extended the file size,
+ * but that has not yet been passed to the filesystem.n
+ *
+ * In this case, the st_size value provided by the file system
+ * will be ignored.
+ *
+ * Valid replies:
+ * fuse_reply_attr
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi for future use, currently always NULL
+ */
+ void (*getattr) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi);
+
+ /**
+ * Set file attributes
+ *
+ * In the 'attr' argument only members indicated by the 'to_set'
+ * bitmask contain valid values. Other members contain undefined
+ * values.
+ *
+ * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
+ * expected to reset the setuid and setgid bits if the file
+ * size or owner is being changed.
+ *
+ * If the setattr was invoked from the ftruncate() system call
+ * under Linux kernel versions 2.6.15 or later, the fi->fh will
+ * contain the value set by the open method or will be undefined
+ * if the open method didn't set any value. Otherwise (not
+ * ftruncate call, or kernel version earlier than 2.6.15) the fi
+ * parameter will be NULL.
+ *
+ * Valid replies:
+ * fuse_reply_attr
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param attr the attributes
+ * @param to_set bit mask of attributes which should be set
+ * @param fi file information, or NULL
+ */
+ void (*setattr) (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+ int to_set, struct fuse_file_info *fi);
+
+ /**
+ * Read symbolic link
+ *
+ * Valid replies:
+ * fuse_reply_readlink
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ */
+ void (*readlink) (fuse_req_t req, fuse_ino_t ino);
+
+ /**
+ * Create file node
+ *
+ * Create a regular file, character device, block device, fifo or
+ * socket node.
+ *
+ * Valid replies:
+ * fuse_reply_entry
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the parent directory
+ * @param name to create
+ * @param mode file type and mode with which to create the new file
+ * @param rdev the device number (only valid if created file is a device)
+ */
+ void (*mknod) (fuse_req_t req, fuse_ino_t parent, const char *name,
+ mode_t mode, dev_t rdev);
+
+ /**
+ * Create a directory
+ *
+ * Valid replies:
+ * fuse_reply_entry
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the parent directory
+ * @param name to create
+ * @param mode with which to create the new file
+ */
+ void (*mkdir) (fuse_req_t req, fuse_ino_t parent, const char *name,
+ mode_t mode);
+
+ /**
+ * Remove a file
+ *
+ * If the file's inode's lookup count is non-zero, the file
+ * system is expected to postpone any removal of the inode
+ * until the lookup count reaches zero (see description of the
+ * forget function).
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the parent directory
+ * @param name to remove
+ */
+ void (*unlink) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
+ /**
+ * Remove a directory
+ *
+ * If the directory's inode's lookup count is non-zero, the
+ * file system is expected to postpone any removal of the
+ * inode until the lookup count reaches zero (see description
+ * of the forget function).
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the parent directory
+ * @param name to remove
+ */
+ void (*rmdir) (fuse_req_t req, fuse_ino_t parent, const char *name);
+
+ /**
+ * Create a symbolic link
+ *
+ * Valid replies:
+ * fuse_reply_entry
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param link the contents of the symbolic link
+ * @param parent inode number of the parent directory
+ * @param name to create
+ */
+ void (*symlink) (fuse_req_t req, const char *link, fuse_ino_t parent,
+ const char *name);
+
+ /** Rename a file
+ *
+ * If the target exists it should be atomically replaced. If
+ * the target's inode's lookup count is non-zero, the file
+ * system is expected to postpone any removal of the inode
+ * until the lookup count reaches zero (see description of the
+ * forget function).
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EINVAL, i.e. all
+ * future bmap requests will fail with EINVAL without being
+ * send to the filesystem process.
+ *
+ * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If
+ * RENAME_NOREPLACE is specified, the filesystem must not
+ * overwrite *newname* if it exists and return an error
+ * instead. If `RENAME_EXCHANGE` is specified, the filesystem
+ * must atomically exchange the two files, i.e. both must
+ * exist and neither may be deleted.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the old parent directory
+ * @param name old name
+ * @param newparent inode number of the new parent directory
+ * @param newname new name
+ */
+ void (*rename) (fuse_req_t req, fuse_ino_t parent, const char *name,
+ fuse_ino_t newparent, const char *newname,
+ unsigned int flags);
+
+ /**
+ * Create a hard link
+ *
+ * Valid replies:
+ * fuse_reply_entry
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the old inode number
+ * @param newparent inode number of the new parent directory
+ * @param newname new name to create
+ */
+ void (*link) (fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+ const char *newname);
+
+ /**
+ * Open a file
+ *
+ * Open flags are available in fi->flags. The following rules
+ * apply.
+ *
+ * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be
+ * filtered out / handled by the kernel.
+ *
+ * - Access modes (O_RDONLY, O_WRONLY, O_RDWR) should be used
+ * by the filesystem to check if the operation is
+ * permitted. If the ``-o default_permissions`` mount
+ * option is given, this check is already done by the
+ * kernel before calling open() and may thus be omitted by
+ * the filesystem.
+ *
+ * - When writeback caching is enabled, the kernel may send
+ * read requests even for files opened with O_WRONLY. The
+ * filesystem should be prepared to handle this.
+ *
+ * - When writeback caching is disabled, the filesystem is
+ * expected to properly handle the O_APPEND flag and ensure
+ * that each write is appending to the end of the file.
+ *
+ * - When writeback caching is enabled, the kernel will
+ * handle O_APPEND. However, unless all changes to the file
+ * come through the kernel this will not work reliably. The
+ * filesystem should thus either ignore the O_APPEND flag
+ * (and let the kernel handle it), or return an error
+ * (indicating that reliably O_APPEND is not available).
+ *
+ * Filesystem may store an arbitrary file handle (pointer,
+ * index, etc) in fi->fh, and use this in other all other file
+ * operations (read, write, flush, release, fsync).
+ *
+ * Filesystem may also implement stateless file I/O and not store
+ * anything in fi->fh.
+ *
+ * There are also some flags (direct_io, keep_cache) which the
+ * filesystem may set in fi, to change the way the file is opened.
+ * See fuse_file_info structure in <fuse_common.h> for more details.
+ *
+ * If this request is answered with an error code of ENOSYS
+ * and FUSE_CAP_NO_OPEN_SUPPORT is set in
+ * `fuse_conn_info.capable`, this is treated as success and
+ * future calls to open and release will also succeed without being
+ * sent to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_open
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ */
+ void (*open) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi);
+
+ /**
+ * Read data
+ *
+ * Read should send exactly the number of bytes requested except
+ * on EOF or error, otherwise the rest of the data will be
+ * substituted with zeroes. An exception to this is when the file
+ * has been opened in 'direct_io' mode, in which case the return
+ * value of the read system call will reflect the return value of
+ * this operation.
+ *
+ * fi->fh will contain the value set by the open method, or will
+ * be undefined if the open method didn't set any value.
+ *
+ * Valid replies:
+ * fuse_reply_buf
+ * fuse_reply_iov
+ * fuse_reply_data
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param size number of bytes to read
+ * @param off offset to read from
+ * @param fi file information
+ */
+ void (*read) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info *fi);
+
+ /**
+ * Write data
+ *
+ * Write should return exactly the number of bytes requested
+ * except on error. An exception to this is when the file has
+ * been opened in 'direct_io' mode, in which case the return value
+ * of the write system call will reflect the return value of this
+ * operation.
+ *
+ * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
+ * expected to reset the setuid and setgid bits.
+ *
+ * fi->fh will contain the value set by the open method, or will
+ * be undefined if the open method didn't set any value.
+ *
+ * Valid replies:
+ * fuse_reply_write
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param buf data to write
+ * @param size number of bytes to write
+ * @param off offset to write to
+ * @param fi file information
+ */
+ void (*write) (fuse_req_t req, fuse_ino_t ino, const char *buf,
+ size_t size, off_t off, struct fuse_file_info *fi);
+
+ /**
+ * Flush method
+ *
+ * This is called on each close() of the opened file.
+ *
+ * Since file descriptors can be duplicated (dup, dup2, fork), for
+ * one open call there may be many flush calls.
+ *
+ * Filesystems shouldn't assume that flush will always be called
+ * after some writes, or that if will be called at all.
+ *
+ * fi->fh will contain the value set by the open method, or will
+ * be undefined if the open method didn't set any value.
+ *
+ * NOTE: the name of the method is misleading, since (unlike
+ * fsync) the filesystem is not forced to flush pending writes.
+ * One reason to flush data is if the filesystem wants to return
+ * write errors during close. However, such use is non-portable
+ * because POSIX does not require [close] to wait for delayed I/O to
+ * complete.
+ *
+ * If the filesystem supports file locking operations (setlk,
+ * getlk) it should remove all locks belonging to 'fi->owner'.
+ *
+ * If this request is answered with an error code of ENOSYS,
+ * this is treated as success and future calls to flush() will
+ * succeed automatically without being send to the filesystem
+ * process.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ *
+ * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html
+ */
+ void (*flush) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi);
+
+ /**
+ * Release an open file
+ *
+ * Release is called when there are no more references to an open
+ * file: all file descriptors are closed and all memory mappings
+ * are unmapped.
+ *
+ * For every open call there will be exactly one release call (unless
+ * the filesystem is force-unmounted).
+ *
+ * The filesystem may reply with an error, but error values are
+ * not returned to close() or munmap() which triggered the
+ * release.
+ *
+ * fi->fh will contain the value set by the open method, or will
+ * be undefined if the open method didn't set any value.
+ * fi->flags will contain the same flags as for open.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ */
+ void (*release) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi);
+
+ /**
+ * Synchronize file contents
+ *
+ * If the datasync parameter is non-zero, then only the user data
+ * should be flushed, not the meta data.
+ *
+ * If this request is answered with an error code of ENOSYS,
+ * this is treated as success and future calls to fsync() will
+ * succeed automatically without being send to the filesystem
+ * process.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param datasync flag indicating if only data should be flushed
+ * @param fi file information
+ */
+ void (*fsync) (fuse_req_t req, fuse_ino_t ino, int datasync,
+ struct fuse_file_info *fi);
+
+ /**
+ * Open a directory
+ *
+ * Filesystem may store an arbitrary file handle (pointer, index,
+ * etc) in fi->fh, and use this in other all other directory
+ * stream operations (readdir, releasedir, fsyncdir).
+ *
+ * If this request is answered with an error code of ENOSYS and
+ * FUSE_CAP_NO_OPENDIR_SUPPORT is set in `fuse_conn_info.capable`,
+ * this is treated as success and future calls to opendir and
+ * releasedir will also succeed without being sent to the filesystem
+ * process. In addition, the kernel will cache readdir results
+ * as if opendir returned FOPEN_KEEP_CACHE | FOPEN_CACHE_DIR.
+ *
+ * Valid replies:
+ * fuse_reply_open
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ */
+ void (*opendir) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi);
+
+ /**
+ * Read directory
+ *
+ * Send a buffer filled using fuse_add_direntry(), with size not
+ * exceeding the requested size. Send an empty buffer on end of
+ * stream.
+ *
+ * fi->fh will contain the value set by the opendir method, or
+ * will be undefined if the opendir method didn't set any value.
+ *
+ * Returning a directory entry from readdir() does not affect
+ * its lookup count.
+ *
+ * If off_t is non-zero, then it will correspond to one of the off_t
+ * values that was previously returned by readdir() for the same
+ * directory handle. In this case, readdir() should skip over entries
+ * coming before the position defined by the off_t value. If entries
+ * are added or removed while the directory handle is open, they filesystem
+ * may still include the entries that have been removed, and may not
+ * report the entries that have been created. However, addition or
+ * removal of entries must never cause readdir() to skip over unrelated
+ * entries or to report them more than once. This means
+ * that off_t can not be a simple index that enumerates the entries
+ * that have been returned but must contain sufficient information to
+ * uniquely determine the next directory entry to return even when the
+ * set of entries is changing.
+ *
+ * The function does not have to report the '.' and '..'
+ * entries, but is allowed to do so. Note that, if readdir does
+ * not return '.' or '..', they will not be implicitly returned,
+ * and this behavior is observable by the caller.
+ *
+ * Valid replies:
+ * fuse_reply_buf
+ * fuse_reply_data
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param size maximum number of bytes to send
+ * @param off offset to continue reading the directory stream
+ * @param fi file information
+ */
+ void (*readdir) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info *fi);
+
+ /**
+ * Release an open directory
+ *
+ * For every opendir call there will be exactly one releasedir
+ * call (unless the filesystem is force-unmounted).
+ *
+ * fi->fh will contain the value set by the opendir method, or
+ * will be undefined if the opendir method didn't set any value.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ */
+ void (*releasedir) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi);
+
+ /**
+ * Synchronize directory contents
+ *
+ * If the datasync parameter is non-zero, then only the directory
+ * contents should be flushed, not the meta data.
+ *
+ * fi->fh will contain the value set by the opendir method, or
+ * will be undefined if the opendir method didn't set any value.
+ *
+ * If this request is answered with an error code of ENOSYS,
+ * this is treated as success and future calls to fsyncdir() will
+ * succeed automatically without being send to the filesystem
+ * process.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param datasync flag indicating if only data should be flushed
+ * @param fi file information
+ */
+ void (*fsyncdir) (fuse_req_t req, fuse_ino_t ino, int datasync,
+ struct fuse_file_info *fi);
+
+ /**
+ * Get file system statistics
+ *
+ * Valid replies:
+ * fuse_reply_statfs
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number, zero means "undefined"
+ */
+ void (*statfs) (fuse_req_t req, fuse_ino_t ino);
+
+ /**
+ * Set an extended attribute
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EOPNOTSUPP, i.e. all
+ * future setxattr() requests will fail with EOPNOTSUPP without being
+ * send to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ */
+ void (*setxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+ const char *value, size_t size, int flags);
+
+ /**
+ * Get an extended attribute
+ *
+ * If size is zero, the size of the value should be sent with
+ * fuse_reply_xattr.
+ *
+ * If the size is non-zero, and the value fits in the buffer, the
+ * value should be sent with fuse_reply_buf.
+ *
+ * If the size is too small for the value, the ERANGE error should
+ * be sent.
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EOPNOTSUPP, i.e. all
+ * future getxattr() requests will fail with EOPNOTSUPP without being
+ * send to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_buf
+ * fuse_reply_data
+ * fuse_reply_xattr
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param name of the extended attribute
+ * @param size maximum size of the value to send
+ */
+ void (*getxattr) (fuse_req_t req, fuse_ino_t ino, const char *name,
+ size_t size);
+
+ /**
+ * List extended attribute names
+ *
+ * If size is zero, the total size of the attribute list should be
+ * sent with fuse_reply_xattr.
+ *
+ * If the size is non-zero, and the null character separated
+ * attribute list fits in the buffer, the list should be sent with
+ * fuse_reply_buf.
+ *
+ * If the size is too small for the list, the ERANGE error should
+ * be sent.
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EOPNOTSUPP, i.e. all
+ * future listxattr() requests will fail with EOPNOTSUPP without being
+ * send to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_buf
+ * fuse_reply_data
+ * fuse_reply_xattr
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param size maximum size of the list to send
+ */
+ void (*listxattr) (fuse_req_t req, fuse_ino_t ino, size_t size);
+
+ /**
+ * Remove an extended attribute
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EOPNOTSUPP, i.e. all
+ * future removexattr() requests will fail with EOPNOTSUPP without being
+ * send to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param name of the extended attribute
+ */
+ void (*removexattr) (fuse_req_t req, fuse_ino_t ino, const char *name);
+
+ /**
+ * Check file access permissions
+ *
+ * This will be called for the access() and chdir() system
+ * calls. If the 'default_permissions' mount option is given,
+ * this method is not called.
+ *
+ * This method is not called under Linux kernel versions 2.4.x
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent success, i.e. this and all future access()
+ * requests will succeed without being send to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param mask requested access mode
+ */
+ void (*access) (fuse_req_t req, fuse_ino_t ino, int mask);
+
+ /**
+ * Create and open a file
+ *
+ * If the file does not exist, first create it with the specified
+ * mode, and then open it.
+ *
+ * See the description of the open handler for more
+ * information.
+ *
+ * If this method is not implemented or under Linux kernel
+ * versions earlier than 2.6.15, the mknod() and open() methods
+ * will be called instead.
+ *
+ * If this request is answered with an error code of ENOSYS, the handler
+ * is treated as not implemented (i.e., for this and future requests the
+ * mknod() and open() handlers will be called instead).
+ *
+ * Valid replies:
+ * fuse_reply_create
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param parent inode number of the parent directory
+ * @param name to create
+ * @param mode file type and mode with which to create the new file
+ * @param fi file information
+ */
+ void (*create) (fuse_req_t req, fuse_ino_t parent, const char *name,
+ mode_t mode, struct fuse_file_info *fi);
+
+ /**
+ * Test for a POSIX file lock
+ *
+ * Valid replies:
+ * fuse_reply_lock
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ * @param lock the region/type to test
+ */
+ void (*getlk) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, struct flock *lock);
+
+ /**
+ * Acquire, modify or release a POSIX file lock
+ *
+ * For POSIX threads (NPTL) there's a 1-1 relation between pid and
+ * owner, but otherwise this is not always the case. For checking
+ * lock ownership, 'fi->owner' must be used. The l_pid field in
+ * 'struct flock' should only be used to fill in this field in
+ * getlk().
+ *
+ * Note: if the locking methods are not implemented, the kernel
+ * will still allow file locking to work locally. Hence these are
+ * only interesting for network filesystems and similar.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ * @param lock the region/type to set
+ * @param sleep locking operation may sleep
+ */
+ void (*setlk) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi,
+ struct flock *lock, int sleep);
+
+ /**
+ * Map block index within file to block index within device
+ *
+ * Note: This makes sense only for block device backed filesystems
+ * mounted with the 'blkdev' option
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure, i.e. all future bmap() requests will
+ * fail with the same error code without being send to the filesystem
+ * process.
+ *
+ * Valid replies:
+ * fuse_reply_bmap
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param blocksize unit of block index
+ * @param idx block index within file
+ */
+ void (*bmap) (fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+ uint64_t idx);
+
+ /**
+ * Ioctl
+ *
+ * Note: For unrestricted ioctls (not allowed for FUSE
+ * servers), data in and out areas can be discovered by giving
+ * iovs and setting FUSE_IOCTL_RETRY in *flags*. For
+ * restricted ioctls, kernel prepares in/out data area
+ * according to the information encoded in cmd.
+ *
+ * Valid replies:
+ * fuse_reply_ioctl_retry
+ * fuse_reply_ioctl
+ * fuse_reply_ioctl_iov
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param cmd ioctl command
+ * @param arg ioctl argument
+ * @param fi file information
+ * @param flags for FUSE_IOCTL_* flags
+ * @param in_buf data fetched from the caller
+ * @param in_bufsz number of fetched bytes
+ * @param out_bufsz maximum size of output data
+ *
+ * Note : the unsigned long request submitted by the application
+ * is truncated to 32 bits.
+ */
+ void (*ioctl) (fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+ void *arg, struct fuse_file_info *fi, unsigned flags,
+ const void *in_buf, size_t in_bufsz, size_t out_bufsz);
+
+ /**
+ * Poll for IO readiness
+ *
+ * Note: If ph is non-NULL, the client should notify
+ * when IO readiness events occur by calling
+ * fuse_lowlevel_notify_poll() with the specified ph.
+ *
+ * Regardless of the number of times poll with a non-NULL ph
+ * is received, single notification is enough to clear all.
+ * Notifying more times incurs overhead but doesn't harm
+ * correctness.
+ *
+ * The callee is responsible for destroying ph with
+ * fuse_pollhandle_destroy() when no longer in use.
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as success (with a kernel-defined default poll-mask) and
+ * future calls to pull() will succeed the same way without being send
+ * to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_poll
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ * @param ph poll handle to be used for notification
+ */
+ void (*poll) (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi,
+ struct fuse_pollhandle *ph);
+
+ /**
+ * Write data made available in a buffer
+ *
+ * This is a more generic version of the ->write() method. If
+ * FUSE_CAP_SPLICE_READ is set in fuse_conn_info.want and the
+ * kernel supports splicing from the fuse device, then the
+ * data will be made available in pipe for supporting zero
+ * copy data transfer.
+ *
+ * buf->count is guaranteed to be one (and thus buf->idx is
+ * always zero). The write_buf handler must ensure that
+ * bufv->off is correctly updated (reflecting the number of
+ * bytes read from bufv->buf[0]).
+ *
+ * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is
+ * expected to reset the setuid and setgid bits.
+ *
+ * Valid replies:
+ * fuse_reply_write
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param bufv buffer containing the data
+ * @param off offset to write to
+ * @param fi file information
+ */
+ void (*write_buf) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_bufvec *bufv, off_t off,
+ struct fuse_file_info *fi);
+
+ /**
+ * Callback function for the retrieve request
+ *
+ * Valid replies:
+ * fuse_reply_none
+ *
+ * @param req request handle
+ * @param cookie user data supplied to fuse_lowlevel_notify_retrieve()
+ * @param ino the inode number supplied to fuse_lowlevel_notify_retrieve()
+ * @param offset the offset supplied to fuse_lowlevel_notify_retrieve()
+ * @param bufv the buffer containing the returned data
+ */
+ void (*retrieve_reply) (fuse_req_t req, void *cookie, fuse_ino_t ino,
+ off_t offset, struct fuse_bufvec *bufv);
+
+ /**
+ * Forget about multiple inodes
+ *
+ * See description of the forget function for more
+ * information.
+ *
+ * Valid replies:
+ * fuse_reply_none
+ *
+ * @param req request handle
+ */
+ void (*forget_multi) (fuse_req_t req, size_t count,
+ struct fuse_forget_data *forgets);
+
+ /**
+ * Acquire, modify or release a BSD file lock
+ *
+ * Note: if the locking methods are not implemented, the kernel
+ * will still allow file locking to work locally. Hence these are
+ * only interesting for network filesystems and similar.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param fi file information
+ * @param op the locking operation, see flock(2)
+ */
+ void (*flock) (fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, int op);
+
+ /**
+ * Allocate requested space. If this function returns success then
+ * subsequent writes to the specified range shall not fail due to the lack
+ * of free space on the file system storage media.
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EOPNOTSUPP, i.e. all
+ * future fallocate() requests will fail with EOPNOTSUPP without being
+ * send to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param offset starting point for allocated region
+ * @param length size of allocated region
+ * @param mode determines the operation to be performed on the given range,
+ * see fallocate(2)
+ */
+ void (*fallocate) (fuse_req_t req, fuse_ino_t ino, int mode,
+ off_t offset, off_t length, struct fuse_file_info *fi);
+
+ /**
+ * Read directory with attributes
+ *
+ * Send a buffer filled using fuse_add_direntry_plus(), with size not
+ * exceeding the requested size. Send an empty buffer on end of
+ * stream.
+ *
+ * fi->fh will contain the value set by the opendir method, or
+ * will be undefined if the opendir method didn't set any value.
+ *
+ * In contrast to readdir() (which does not affect the lookup counts),
+ * the lookup count of every entry returned by readdirplus(), except "."
+ * and "..", is incremented by one.
+ *
+ * Valid replies:
+ * fuse_reply_buf
+ * fuse_reply_data
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino the inode number
+ * @param size maximum number of bytes to send
+ * @param off offset to continue reading the directory stream
+ * @param fi file information
+ */
+ void (*readdirplus) (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off,
+ struct fuse_file_info *fi);
+
+ /**
+ * Copy a range of data from one file to another
+ *
+ * Performs an optimized copy between two file descriptors without the
+ * additional cost of transferring data through the FUSE kernel module
+ * to user space (glibc) and then back into the FUSE filesystem again.
+ *
+ * In case this method is not implemented, glibc falls back to reading
+ * data from the source and writing to the destination. Effectively
+ * doing an inefficient copy of the data.
+ *
+ * If this request is answered with an error code of ENOSYS, this is
+ * treated as a permanent failure with error code EOPNOTSUPP, i.e. all
+ * future copy_file_range() requests will fail with EOPNOTSUPP without
+ * being send to the filesystem process.
+ *
+ * Valid replies:
+ * fuse_reply_write
+ * fuse_reply_err
+ *
+ * @param req request handle
+ * @param ino_in the inode number or the source file
+ * @param off_in starting point from were the data should be read
+ * @param fi_in file information of the source file
+ * @param ino_out the inode number or the destination file
+ * @param off_out starting point where the data should be written
+ * @param fi_out file information of the destination file
+ * @param len maximum size of the data to copy
+ * @param flags passed along with the copy_file_range() syscall
+ */
+ void (*copy_file_range) (fuse_req_t req, fuse_ino_t ino_in,
+ off_t off_in, struct fuse_file_info *fi_in,
+ fuse_ino_t ino_out, off_t off_out,
+ struct fuse_file_info *fi_out, size_t len,
+ int flags);
+};
+
+/**
+ * Reply with an error code or success.
+ *
+ * Possible requests:
+ * all except forget
+ *
+ * Whereever possible, error codes should be chosen from the list of
+ * documented error conditions in the corresponding system calls
+ * manpage.
+ *
+ * An error code of ENOSYS is sometimes treated specially. This is
+ * indicated in the documentation of the affected handler functions.
+ *
+ * The following requests may be answered with a zero error code:
+ * unlink, rmdir, rename, flush, release, fsync, fsyncdir, setxattr,
+ * removexattr, setlk.
+ *
+ * @param req request handle
+ * @param err the positive error value, or zero for success
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_err(fuse_req_t req, int err);
+
+/**
+ * Don't send reply
+ *
+ * Possible requests:
+ * forget
+ * forget_multi
+ * retrieve_reply
+ *
+ * @param req request handle
+ */
+void fuse_reply_none(fuse_req_t req);
+
+/**
+ * Reply with a directory entry
+ *
+ * Possible requests:
+ * lookup, mknod, mkdir, symlink, link
+ *
+ * Side effects:
+ * increments the lookup count on success
+ *
+ * @param req request handle
+ * @param e the entry parameters
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e);
+
+/**
+ * Reply with a directory entry and open parameters
+ *
+ * currently the following members of 'fi' are used:
+ * fh, direct_io, keep_cache
+ *
+ * Possible requests:
+ * create
+ *
+ * Side effects:
+ * increments the lookup count on success
+ *
+ * @param req request handle
+ * @param e the entry parameters
+ * @param fi file information
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+ const struct fuse_file_info *fi);
+
+/**
+ * Reply with attributes
+ *
+ * Possible requests:
+ * getattr, setattr
+ *
+ * @param req request handle
+ * @param attr the attributes
+ * @param attr_timeout validity timeout (in seconds) for the attributes
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+ double attr_timeout);
+
+/**
+ * Reply with the contents of a symbolic link
+ *
+ * Possible requests:
+ * readlink
+ *
+ * @param req request handle
+ * @param link symbolic link contents
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_readlink(fuse_req_t req, const char *link);
+
+/**
+ * Reply with open parameters
+ *
+ * currently the following members of 'fi' are used:
+ * fh, direct_io, keep_cache
+ *
+ * Possible requests:
+ * open, opendir
+ *
+ * @param req request handle
+ * @param fi file information
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi);
+
+/**
+ * Reply with number of bytes written
+ *
+ * Possible requests:
+ * write
+ *
+ * @param req request handle
+ * @param count the number of bytes written
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_write(fuse_req_t req, size_t count);
+
+/**
+ * Reply with data
+ *
+ * Possible requests:
+ * read, readdir, getxattr, listxattr
+ *
+ * @param req request handle
+ * @param buf buffer containing data
+ * @param size the size of data in bytes
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size);
+
+/**
+ * Reply with data copied/moved from buffer(s)
+ *
+ * Zero copy data transfer ("splicing") will be used under
+ * the following circumstances:
+ *
+ * 1. FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.want, and
+ * 2. the kernel supports splicing from the fuse device
+ * (FUSE_CAP_SPLICE_WRITE is set in fuse_conn_info.capable), and
+ * 3. *flags* does not contain FUSE_BUF_NO_SPLICE
+ * 4. The amount of data that is provided in file-descriptor backed
+ * buffers (i.e., buffers for which bufv[n].flags == FUSE_BUF_FD)
+ * is at least twice the page size.
+ *
+ * In order for SPLICE_F_MOVE to be used, the following additional
+ * conditions have to be fulfilled:
+ *
+ * 1. FUSE_CAP_SPLICE_MOVE is set in fuse_conn_info.want, and
+ * 2. the kernel supports it (i.e, FUSE_CAP_SPLICE_MOVE is set in
+ fuse_conn_info.capable), and
+ * 3. *flags* contains FUSE_BUF_SPLICE_MOVE
+ *
+ * Note that, if splice is used, the data is actually spliced twice:
+ * once into a temporary pipe (to prepend header data), and then again
+ * into the kernel. If some of the provided buffers are memory-backed,
+ * the data in them is copied in step one and spliced in step two.
+ *
+ * The FUSE_BUF_SPLICE_FORCE_SPLICE and FUSE_BUF_SPLICE_NONBLOCK flags
+ * are silently ignored.
+ *
+ * Possible requests:
+ * read, readdir, getxattr, listxattr
+ *
+ * Side effects:
+ * when used to return data from a readdirplus() (but not readdir())
+ * call, increments the lookup count of each returned entry by one
+ * on success.
+ *
+ * @param req request handle
+ * @param bufv buffer vector
+ * @param flags flags controlling the copy
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags);
+
+/**
+ * Reply with data vector
+ *
+ * Possible requests:
+ * read, readdir, getxattr, listxattr
+ *
+ * @param req request handle
+ * @param iov the vector containing the data
+ * @param count the size of vector
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count);
+
+/**
+ * Reply with filesystem statistics
+ *
+ * Possible requests:
+ * statfs
+ *
+ * @param req request handle
+ * @param stbuf filesystem statistics
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf);
+
+/**
+ * Reply with needed buffer size
+ *
+ * Possible requests:
+ * getxattr, listxattr
+ *
+ * @param req request handle
+ * @param count the buffer size needed in bytes
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_xattr(fuse_req_t req, size_t count);
+
+/**
+ * Reply with file lock information
+ *
+ * Possible requests:
+ * getlk
+ *
+ * @param req request handle
+ * @param lock the lock information
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_lock(fuse_req_t req, const struct flock *lock);
+
+/**
+ * Reply with block index
+ *
+ * Possible requests:
+ * bmap
+ *
+ * @param req request handle
+ * @param idx block index within device
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_bmap(fuse_req_t req, uint64_t idx);
+
+/* ----------------------------------------------------------- *
+ * Filling a buffer in readdir *
+ * ----------------------------------------------------------- */
+
+/**
+ * Add a directory entry to the buffer
+ *
+ * Buffer needs to be large enough to hold the entry. If it's not,
+ * then the entry is not filled in but the size of the entry is still
+ * returned. The caller can check this by comparing the bufsize
+ * parameter with the returned entry size. If the entry size is
+ * larger than the buffer size, the operation failed.
+ *
+ * From the 'stbuf' argument the st_ino field and bits 12-15 of the
+ * st_mode field are used. The other fields are ignored.
+ *
+ * *off* should be any non-zero value that the filesystem can use to
+ * identify the current point in the directory stream. It does not
+ * need to be the actual physical position. A value of zero is
+ * reserved to mean "from the beginning", and should therefore never
+ * be used (the first call to fuse_add_direntry should be passed the
+ * offset of the second directory entry).
+ *
+ * @param req request handle
+ * @param buf the point where the new entry will be added to the buffer
+ * @param bufsize remaining size of the buffer
+ * @param name the name of the entry
+ * @param stbuf the file attributes
+ * @param off the offset of the next entry
+ * @return the space needed for the entry
+ */
+size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+ const char *name, const struct stat *stbuf,
+ off_t off);
+
+/**
+ * Add a directory entry to the buffer with the attributes
+ *
+ * See documentation of `fuse_add_direntry()` for more details.
+ *
+ * @param req request handle
+ * @param buf the point where the new entry will be added to the buffer
+ * @param bufsize remaining size of the buffer
+ * @param name the name of the entry
+ * @param e the directory entry
+ * @param off the offset of the next entry
+ * @return the space needed for the entry
+ */
+size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+ const char *name,
+ const struct fuse_entry_param *e, off_t off);
+
+/**
+ * Reply to ask for data fetch and output buffer preparation. ioctl
+ * will be retried with the specified input data fetched and output
+ * buffer prepared.
+ *
+ * Possible requests:
+ * ioctl
+ *
+ * @param req request handle
+ * @param in_iov iovec specifying data to fetch from the caller
+ * @param in_count number of entries in in_iov
+ * @param out_iov iovec specifying addresses to write output to
+ * @param out_count number of entries in out_iov
+ * @return zero for success, -errno for failure to send reply
+ */
+int fuse_reply_ioctl_retry(fuse_req_t req,
+ const struct iovec *in_iov, size_t in_count,
+ const struct iovec *out_iov, size_t out_count);
+
+/**
+ * Reply to finish ioctl
+ *
+ * Possible requests:
+ * ioctl
+ *
+ * @param req request handle
+ * @param result result to be passed to the caller
+ * @param buf buffer containing output data
+ * @param size length of output data
+ */
+int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size);
+
+/**
+ * Reply to finish ioctl with iov buffer
+ *
+ * Possible requests:
+ * ioctl
+ *
+ * @param req request handle
+ * @param result result to be passed to the caller
+ * @param iov the vector containing the data
+ * @param count the size of vector
+ */
+int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+ int count);
+
+/**
+ * Reply with poll result event mask
+ *
+ * @param req request handle
+ * @param revents poll result event mask
+ */
+int fuse_reply_poll(fuse_req_t req, unsigned revents);
+
+/* ----------------------------------------------------------- *
+ * Notification *
+ * ----------------------------------------------------------- */
+
+/**
+ * Notify IO readiness event
+ *
+ * For more information, please read comment for poll operation.
+ *
+ * @param ph poll handle to notify IO readiness event for
+ */
+int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph);
+
+/**
+ * Notify to invalidate cache for an inode.
+ *
+ * Added in FUSE protocol version 7.12. If the kernel does not support
+ * this (or a newer) version, the function will return -ENOSYS and do
+ * nothing.
+ *
+ * If the filesystem has writeback caching enabled, invalidating an
+ * inode will first trigger a writeback of all dirty pages. The call
+ * will block until all writeback requests have completed and the
+ * inode has been invalidated. It will, however, not wait for
+ * completion of pending writeback requests that have been issued
+ * before.
+ *
+ * If there are no dirty pages, this function will never block.
+ *
+ * @param se the session object
+ * @param ino the inode number
+ * @param off the offset in the inode where to start invalidating
+ * or negative to invalidate attributes only
+ * @param len the amount of cache to invalidate or 0 for all
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+ off_t off, off_t len);
+
+/**
+ * Notify to invalidate parent attributes and the dentry matching
+ * parent/name
+ *
+ * To avoid a deadlock this function must not be called in the
+ * execution path of a related filesytem operation or within any code
+ * that could hold a lock that could be needed to execute such an
+ * operation. As of kernel 4.18, a "related operation" is a lookup(),
+ * symlink(), mknod(), mkdir(), unlink(), rename(), link() or create()
+ * request for the parent, and a setattr(), unlink(), rmdir(),
+ * rename(), setxattr(), removexattr(), readdir() or readdirplus()
+ * request for the inode itself.
+ *
+ * When called correctly, this function will never block.
+ *
+ * Added in FUSE protocol version 7.12. If the kernel does not support
+ * this (or a newer) version, the function will return -ENOSYS and do
+ * nothing.
+ *
+ * @param se the session object
+ * @param parent inode number
+ * @param name file name
+ * @param namelen strlen() of file name
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+ const char *name, size_t namelen);
+
+/**
+ * This function behaves like fuse_lowlevel_notify_inval_entry() with
+ * the following additional effect (at least as of Linux kernel 4.8):
+ *
+ * If the provided *child* inode matches the inode that is currently
+ * associated with the cached dentry, and if there are any inotify
+ * watches registered for the dentry, then the watchers are informed
+ * that the dentry has been deleted.
+ *
+ * To avoid a deadlock this function must not be called while
+ * executing a related filesytem operation or while holding a lock
+ * that could be needed to execute such an operation (see the
+ * description of fuse_lowlevel_notify_inval_entry() for more
+ * details).
+ *
+ * When called correctly, this function will never block.
+ *
+ * Added in FUSE protocol version 7.18. If the kernel does not support
+ * this (or a newer) version, the function will return -ENOSYS and do
+ * nothing.
+ *
+ * @param se the session object
+ * @param parent inode number
+ * @param child inode number
+ * @param name file name
+ * @param namelen strlen() of file name
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_delete(struct fuse_session *se,
+ fuse_ino_t parent, fuse_ino_t child,
+ const char *name, size_t namelen);
+
+/**
+ * Store data to the kernel buffers
+ *
+ * Synchronously store data in the kernel buffers belonging to the
+ * given inode. The stored data is marked up-to-date (no read will be
+ * performed against it, unless it's invalidated or evicted from the
+ * cache).
+ *
+ * If the stored data overflows the current file size, then the size
+ * is extended, similarly to a write(2) on the filesystem.
+ *
+ * If this function returns an error, then the store wasn't fully
+ * completed, but it may have been partially completed.
+ *
+ * Added in FUSE protocol version 7.15. If the kernel does not support
+ * this (or a newer) version, the function will return -ENOSYS and do
+ * nothing.
+ *
+ * @param se the session object
+ * @param ino the inode number
+ * @param offset the starting offset into the file to store to
+ * @param bufv buffer vector
+ * @param flags flags controlling the copy
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+ off_t offset, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags);
+/**
+ * Retrieve data from the kernel buffers
+ *
+ * Retrieve data in the kernel buffers belonging to the given inode.
+ * If successful then the retrieve_reply() method will be called with
+ * the returned data.
+ *
+ * Only present pages are returned in the retrieve reply. Retrieving
+ * stops when it finds a non-present page and only data prior to that
+ * is returned.
+ *
+ * If this function returns an error, then the retrieve will not be
+ * completed and no reply will be sent.
+ *
+ * This function doesn't change the dirty state of pages in the kernel
+ * buffer. For dirty pages the write() method will be called
+ * regardless of having been retrieved previously.
+ *
+ * Added in FUSE protocol version 7.15. If the kernel does not support
+ * this (or a newer) version, the function will return -ENOSYS and do
+ * nothing.
+ *
+ * @param se the session object
+ * @param ino the inode number
+ * @param size the number of bytes to retrieve
+ * @param offset the starting offset into the file to retrieve from
+ * @param cookie user data to supply to the reply callback
+ * @return zero for success, -errno for failure
+ */
+int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+ size_t size, off_t offset, void *cookie);
+
+
+/* ----------------------------------------------------------- *
+ * Utility functions *
+ * ----------------------------------------------------------- */
+
+/**
+ * Get the userdata from the request
+ *
+ * @param req request handle
+ * @return the user data passed to fuse_session_new()
+ */
+void *fuse_req_userdata(fuse_req_t req);
+
+/**
+ * Get the context from the request
+ *
+ * The pointer returned by this function will only be valid for the
+ * request's lifetime
+ *
+ * @param req request handle
+ * @return the context structure
+ */
+const struct fuse_ctx *fuse_req_ctx(fuse_req_t req);
+
+/**
+ * Get the current supplementary group IDs for the specified request
+ *
+ * Similar to the getgroups(2) system call, except the return value is
+ * always the total number of group IDs, even if it is larger than the
+ * specified size.
+ *
+ * The current fuse kernel module in linux (as of 2.6.30) doesn't pass
+ * the group list to userspace, hence this function needs to parse
+ * "/proc/$TID/task/$TID/status" to get the group IDs.
+ *
+ * This feature may not be supported on all operating systems. In
+ * such a case this function will return -ENOSYS.
+ *
+ * @param req request handle
+ * @param size size of given array
+ * @param list array of group IDs to be filled in
+ * @return the total number of supplementary group IDs or -errno on failure
+ */
+int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[]);
+
+/**
+ * Callback function for an interrupt
+ *
+ * @param req interrupted request
+ * @param data user data
+ */
+typedef void (*fuse_interrupt_func_t)(fuse_req_t req, void *data);
+
+/**
+ * Register/unregister callback for an interrupt
+ *
+ * If an interrupt has already happened, then the callback function is
+ * called from within this function, hence it's not possible for
+ * interrupts to be lost.
+ *
+ * @param req request handle
+ * @param func the callback function or NULL for unregister
+ * @param data user data passed to the callback function
+ */
+void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func,
+ void *data);
+
+/**
+ * Check if a request has already been interrupted
+ *
+ * @param req request handle
+ * @return 1 if the request has been interrupted, 0 otherwise
+ */
+int fuse_req_interrupted(fuse_req_t req);
+
+
+/* ----------------------------------------------------------- *
+ * Inquiry functions *
+ * ----------------------------------------------------------- */
+
+/**
+ * Print low-level version information to stdout.
+ */
+void fuse_lowlevel_version(void);
+
+/**
+ * Print available low-level options to stdout. This is not an
+ * exhaustive list, but includes only those options that may be of
+ * interest to an end-user of a file system.
+ */
+void fuse_lowlevel_help(void);
+
+/**
+ * Print available options for `fuse_parse_cmdline()`.
+ */
+void fuse_cmdline_help(void);
+
+/* ----------------------------------------------------------- *
+ * Filesystem setup & teardown *
+ * ----------------------------------------------------------- */
+
+struct fuse_cmdline_opts {
+ int singlethread;
+ int foreground;
+ int debug;
+ int nodefault_subtype;
+ char *mountpoint;
+ int show_version;
+ int show_help;
+ int clone_fd;
+ unsigned int max_idle_threads;
+};
+
+/**
+ * Utility function to parse common options for simple file systems
+ * using the low-level API. A help text that describes the available
+ * options can be printed with `fuse_cmdline_help`. A single
+ * non-option argument is treated as the mountpoint. Multiple
+ * non-option arguments will result in an error.
+ *
+ * If neither -o subtype= or -o fsname= options are given, a new
+ * subtype option will be added and set to the basename of the program
+ * (the fsname will remain unset, and then defaults to "fuse").
+ *
+ * Known options will be removed from *args*, unknown options will
+ * remain.
+ *
+ * @param args argument vector (input+output)
+ * @param opts output argument for parsed options
+ * @return 0 on success, -1 on failure
+ */
+int fuse_parse_cmdline(struct fuse_args *args,
+ struct fuse_cmdline_opts *opts);
+
+/**
+ * Create a low level session.
+ *
+ * Returns a session structure suitable for passing to
+ * fuse_session_mount() and fuse_session_loop().
+ *
+ * This function accepts most file-system independent mount options
+ * (like context, nodev, ro - see mount(8)), as well as the general
+ * fuse mount options listed in mount.fuse(8) (e.g. -o allow_root and
+ * -o default_permissions, but not ``-o use_ino``). Instead of `-o
+ * debug`, debugging may also enabled with `-d` or `--debug`.
+ *
+ * If not all options are known, an error message is written to stderr
+ * and the function returns NULL.
+ *
+ * Option parsing skips argv[0], which is assumed to contain the
+ * program name. To prevent accidentally passing an option in
+ * argv[0], this element must always be present (even if no options
+ * are specified). It may be set to the empty string ('\0') if no
+ * reasonable value can be provided.
+ *
+ * @param args argument vector
+ * @param op the (low-level) filesystem operations
+ * @param op_size sizeof(struct fuse_lowlevel_ops)
+ * @param userdata user data
+ *
+ * @return the fuse session on success, NULL on failure
+ **/
+struct fuse_session *fuse_session_new(struct fuse_args *args,
+ const struct fuse_lowlevel_ops *op,
+ size_t op_size, void *userdata);
+
+/**
+ * Mount a FUSE file system.
+ *
+ * @param mountpoint the mount point path
+ * @param se session object
+ *
+ * @return 0 on success, -1 on failure.
+ **/
+int fuse_session_mount(struct fuse_session *se, const char *mountpoint);
+
+/**
+ * Enter a single threaded, blocking event loop.
+ *
+ * When the event loop terminates because the connection to the FUSE
+ * kernel module has been closed, this function returns zero. This
+ * happens when the filesystem is unmounted regularly (by the
+ * filesystem owner or root running the umount(8) or fusermount(1)
+ * command), or if connection is explicitly severed by writing ``1``
+ * to the``abort`` file in ``/sys/fs/fuse/connections/NNN``. The only
+ * way to distinguish between these two conditions is to check if the
+ * filesystem is still mounted after the session loop returns.
+ *
+ * When some error occurs during request processing, the function
+ * returns a negated errno(3) value.
+ *
+ * If the loop has been terminated because of a signal handler
+ * installed by fuse_set_signal_handlers(), this function returns the
+ * (positive) signal value that triggered the exit.
+ *
+ * @param se the session
+ * @return 0, -errno, or a signal value
+ */
+int fuse_session_loop(struct fuse_session *se);
+
+/**
+ * Enter a multi-threaded event loop.
+ *
+ * For a description of the return value and the conditions when the
+ * event loop exits, refer to the documentation of
+ * fuse_session_loop().
+ *
+ * @param se the session
+ * @param config session loop configuration
+ * @return see fuse_session_loop()
+ */
+#if FUSE_USE_VERSION < 32
+int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+#define fuse_session_loop_mt(se, clone_fd) fuse_session_loop_mt_31(se, clone_fd)
+#else
+int fuse_session_loop_mt(struct fuse_session *se, struct fuse_loop_config *config);
+#endif
+
+/**
+ * Flag a session as terminated.
+ *
+ * This function is invoked by the POSIX signal handlers, when
+ * registered using fuse_set_signal_handlers(). It will cause any
+ * running event loops to terminate on the next opportunity.
+ *
+ * @param se the session
+ */
+void fuse_session_exit(struct fuse_session *se);
+
+/**
+ * Reset the terminated flag of a session
+ *
+ * @param se the session
+ */
+void fuse_session_reset(struct fuse_session *se);
+
+/**
+ * Query the terminated flag of a session
+ *
+ * @param se the session
+ * @return 1 if exited, 0 if not exited
+ */
+int fuse_session_exited(struct fuse_session *se);
+
+/**
+ * Ensure that file system is unmounted.
+ *
+ * In regular operation, the file system is typically unmounted by the
+ * user calling umount(8) or fusermount(1), which then terminates the
+ * FUSE session loop. However, the session loop may also terminate as
+ * a result of an explicit call to fuse_session_exit() (e.g. by a
+ * signal handler installed by fuse_set_signal_handler()). In this
+ * case the filesystem remains mounted, but any attempt to access it
+ * will block (while the filesystem process is still running) or give
+ * an ESHUTDOWN error (after the filesystem process has terminated).
+ *
+ * If the communication channel with the FUSE kernel module is still
+ * open (i.e., if the session loop was terminated by an explicit call
+ * to fuse_session_exit()), this function will close it and unmount
+ * the filesystem. If the communication channel has been closed by the
+ * kernel, this method will do (almost) nothing.
+ *
+ * NOTE: The above semantics mean that if the connection to the kernel
+ * is terminated via the ``/sys/fs/fuse/connections/NNN/abort`` file,
+ * this method will *not* unmount the filesystem.
+ *
+ * @param se the session
+ */
+void fuse_session_unmount(struct fuse_session *se);
+
+/**
+ * Destroy a session
+ *
+ * @param se the session
+ */
+void fuse_session_destroy(struct fuse_session *se);
+
+/* ----------------------------------------------------------- *
+ * Custom event loop support *
+ * ----------------------------------------------------------- */
+
+/**
+ * Return file descriptor for communication with kernel.
+ *
+ * The file selector can be used to integrate FUSE with a custom event
+ * loop. Whenever data is available for reading on the provided fd,
+ * the event loop should call `fuse_session_receive_buf` followed by
+ * `fuse_session_process_buf` to process the request.
+ *
+ * The returned file descriptor is valid until `fuse_session_unmount`
+ * is called.
+ *
+ * @param se the session
+ * @return a file descriptor
+ */
+int fuse_session_fd(struct fuse_session *se);
+
+/**
+ * Process a raw request supplied in a generic buffer
+ *
+ * The fuse_buf may contain a memory buffer or a pipe file descriptor.
+ *
+ * @param se the session
+ * @param buf the fuse_buf containing the request
+ */
+void fuse_session_process_buf(struct fuse_session *se,
+ const struct fuse_buf *buf);
+
+/**
+ * Read a raw request from the kernel into the supplied buffer.
+ *
+ * Depending on file system options, system capabilities, and request
+ * size the request is either read into a memory buffer or spliced
+ * into a temporary pipe.
+ *
+ * @param se the session
+ * @param buf the fuse_buf to store the request in
+ * @return the actual size of the raw request, or -errno on error
+ */
+int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FUSE_LOWLEVEL_H_ */
diff --git a/include/fuse_opt.h b/include/fuse_opt.h
new file mode 100644
index 0000000..d8573e7
--- /dev/null
+++ b/include/fuse_opt.h
@@ -0,0 +1,271 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#ifndef FUSE_OPT_H_
+#define FUSE_OPT_H_
+
+/** @file
+ *
+ * This file defines the option parsing interface of FUSE
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Option description
+ *
+ * This structure describes a single option, and action associated
+ * with it, in case it matches.
+ *
+ * More than one such match may occur, in which case the action for
+ * each match is executed.
+ *
+ * There are three possible actions in case of a match:
+ *
+ * i) An integer (int or unsigned) variable determined by 'offset' is
+ * set to 'value'
+ *
+ * ii) The processing function is called, with 'value' as the key
+ *
+ * iii) An integer (any) or string (char *) variable determined by
+ * 'offset' is set to the value of an option parameter
+ *
+ * 'offset' should normally be either set to
+ *
+ * - 'offsetof(struct foo, member)' actions i) and iii)
+ *
+ * - -1 action ii)
+ *
+ * The 'offsetof()' macro is defined in the <stddef.h> header.
+ *
+ * The template determines which options match, and also have an
+ * effect on the action. Normally the action is either i) or ii), but
+ * if a format is present in the template, then action iii) is
+ * performed.
+ *
+ * The types of templates are:
+ *
+ * 1) "-x", "-foo", "--foo", "--foo-bar", etc. These match only
+ * themselves. Invalid values are "--" and anything beginning
+ * with "-o"
+ *
+ * 2) "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or
+ * the relevant option in a comma separated option list
+ *
+ * 3) "bar=", "--foo=", etc. These are variations of 1) and 2)
+ * which have a parameter
+ *
+ * 4) "bar=%s", "--foo=%lu", etc. Same matching as above but perform
+ * action iii).
+ *
+ * 5) "-x ", etc. Matches either "-xparam" or "-x param" as
+ * two separate arguments
+ *
+ * 6) "-x %s", etc. Combination of 4) and 5)
+ *
+ * If the format is "%s", memory is allocated for the string unlike with
+ * scanf(). The previous value (if non-NULL) stored at the this location is
+ * freed.
+ */
+struct fuse_opt {
+ /** Matching template and optional parameter formatting */
+ const char *templ;
+
+ /**
+ * Offset of variable within 'data' parameter of fuse_opt_parse()
+ * or -1
+ */
+ unsigned long offset;
+
+ /**
+ * Value to set the variable to, or to be passed as 'key' to the
+ * processing function. Ignored if template has a format
+ */
+ int value;
+};
+
+/**
+ * Key option. In case of a match, the processing function will be
+ * called with the specified key.
+ */
+#define FUSE_OPT_KEY(templ, key) { templ, -1U, key }
+
+/**
+ * Last option. An array of 'struct fuse_opt' must end with a NULL
+ * template value
+ */
+#define FUSE_OPT_END { NULL, 0, 0 }
+
+/**
+ * Argument list
+ */
+struct fuse_args {
+ /** Argument count */
+ int argc;
+
+ /** Argument vector. NULL terminated */
+ char **argv;
+
+ /** Is 'argv' allocated? */
+ int allocated;
+};
+
+/**
+ * Initializer for 'struct fuse_args'
+ */
+#define FUSE_ARGS_INIT(argc, argv) { argc, argv, 0 }
+
+/**
+ * Key value passed to the processing function if an option did not
+ * match any template
+ */
+#define FUSE_OPT_KEY_OPT -1
+
+/**
+ * Key value passed to the processing function for all non-options
+ *
+ * Non-options are the arguments beginning with a character other than
+ * '-' or all arguments after the special '--' option
+ */
+#define FUSE_OPT_KEY_NONOPT -2
+
+/**
+ * Special key value for options to keep
+ *
+ * Argument is not passed to processing function, but behave as if the
+ * processing function returned 1
+ */
+#define FUSE_OPT_KEY_KEEP -3
+
+/**
+ * Special key value for options to discard
+ *
+ * Argument is not passed to processing function, but behave as if the
+ * processing function returned zero
+ */
+#define FUSE_OPT_KEY_DISCARD -4
+
+/**
+ * Processing function
+ *
+ * This function is called if
+ * - option did not match any 'struct fuse_opt'
+ * - argument is a non-option
+ * - option did match and offset was set to -1
+ *
+ * The 'arg' parameter will always contain the whole argument or
+ * option including the parameter if exists. A two-argument option
+ * ("-x foo") is always converted to single argument option of the
+ * form "-xfoo" before this function is called.
+ *
+ * Options of the form '-ofoo' are passed to this function without the
+ * '-o' prefix.
+ *
+ * The return value of this function determines whether this argument
+ * is to be inserted into the output argument vector, or discarded.
+ *
+ * @param data is the user data passed to the fuse_opt_parse() function
+ * @param arg is the whole argument or option
+ * @param key determines why the processing function was called
+ * @param outargs the current output argument list
+ * @return -1 on error, 0 if arg is to be discarded, 1 if arg should be kept
+ */
+typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key,
+ struct fuse_args *outargs);
+
+/**
+ * Option parsing function
+ *
+ * If 'args' was returned from a previous call to fuse_opt_parse() or
+ * it was constructed from
+ *
+ * A NULL 'args' is equivalent to an empty argument vector
+ *
+ * A NULL 'opts' is equivalent to an 'opts' array containing a single
+ * end marker
+ *
+ * A NULL 'proc' is equivalent to a processing function always
+ * returning '1'
+ *
+ * @param args is the input and output argument list
+ * @param data is the user data
+ * @param opts is the option description array
+ * @param proc is the processing function
+ * @return -1 on error, 0 on success
+ */
+int fuse_opt_parse(struct fuse_args *args, void *data,
+ const struct fuse_opt opts[], fuse_opt_proc_t proc);
+
+/**
+ * Add an option to a comma separated option list
+ *
+ * @param opts is a pointer to an option list, may point to a NULL value
+ * @param opt is the option to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_add_opt(char **opts, const char *opt);
+
+/**
+ * Add an option, escaping commas, to a comma separated option list
+ *
+ * @param opts is a pointer to an option list, may point to a NULL value
+ * @param opt is the option to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_add_opt_escaped(char **opts, const char *opt);
+
+/**
+ * Add an argument to a NULL terminated argument vector
+ *
+ * @param args is the structure containing the current argument list
+ * @param arg is the new argument to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_add_arg(struct fuse_args *args, const char *arg);
+
+/**
+ * Add an argument at the specified position in a NULL terminated
+ * argument vector
+ *
+ * Adds the argument to the N-th position. This is useful for adding
+ * options at the beginning of the array which must not come after the
+ * special '--' option.
+ *
+ * @param args is the structure containing the current argument list
+ * @param pos is the position at which to add the argument
+ * @param arg is the new argument to add
+ * @return -1 on allocation error, 0 on success
+ */
+int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg);
+
+/**
+ * Free the contents of argument list
+ *
+ * The structure itself is not freed
+ *
+ * @param args is the structure containing the argument list
+ */
+void fuse_opt_free_args(struct fuse_args *args);
+
+
+/**
+ * Check if an option matches
+ *
+ * @param opts is the option description array
+ * @param opt is the option to match
+ * @return 1 if a match is found, 0 if not
+ */
+int fuse_opt_match(const struct fuse_opt opts[], const char *opt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FUSE_OPT_H_ */
diff --git a/include/meson.build b/include/meson.build
new file mode 100644
index 0000000..55cb6f7
--- /dev/null
+++ b/include/meson.build
@@ -0,0 +1,4 @@
+libfuse_headers = [ 'fuse.h', 'fuse_common.h', 'fuse_lowlevel.h',
+ 'fuse_opt.h', 'cuse_lowlevel.h' ]
+
+install_headers(libfuse_headers, subdir: 'fuse3')
diff --git a/lib/android_config.h b/lib/android_config.h
new file mode 100644
index 0000000..b05b316
--- /dev/null
+++ b/lib/android_config.h
@@ -0,0 +1,2 @@
+#define pthread_setcancelstate(x, y)
+#define pthread_cancel(c)
diff --git a/lib/buffer.c b/lib/buffer.c
new file mode 100644
index 0000000..5ab9b87
--- /dev/null
+++ b/lib/buffer.c
@@ -0,0 +1,321 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2010 Miklos Szeredi <miklos@szeredi.hu>
+
+ Functions for dealing with `struct fuse_buf` and `struct
+ fuse_bufvec`.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_lowlevel.h"
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+
+size_t fuse_buf_size(const struct fuse_bufvec *bufv)
+{
+ size_t i;
+ size_t size = 0;
+
+ for (i = 0; i < bufv->count; i++) {
+ if (bufv->buf[i].size == SIZE_MAX)
+ size = SIZE_MAX;
+ else
+ size += bufv->buf[i].size;
+ }
+
+ return size;
+}
+
+static size_t min_size(size_t s1, size_t s2)
+{
+ return s1 < s2 ? s1 : s2;
+}
+
+static ssize_t fuse_buf_write(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len)
+{
+ ssize_t res = 0;
+ size_t copied = 0;
+
+ while (len) {
+ if (dst->flags & FUSE_BUF_FD_SEEK) {
+ res = pwrite(dst->fd, (char *)src->mem + src_off, len,
+ dst->pos + dst_off);
+ } else {
+ res = write(dst->fd, (char *)src->mem + src_off, len);
+ }
+ if (res == -1) {
+ if (!copied)
+ return -errno;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+ if (!(dst->flags & FUSE_BUF_FD_RETRY))
+ break;
+
+ src_off += res;
+ dst_off += res;
+ len -= res;
+ }
+
+ return copied;
+}
+
+static ssize_t fuse_buf_read(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len)
+{
+ ssize_t res = 0;
+ size_t copied = 0;
+
+ while (len) {
+ if (src->flags & FUSE_BUF_FD_SEEK) {
+ res = pread(src->fd, (char *)dst->mem + dst_off, len,
+ src->pos + src_off);
+ } else {
+ res = read(src->fd, (char *)dst->mem + dst_off, len);
+ }
+ if (res == -1) {
+ if (!copied)
+ return -errno;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+ if (!(src->flags & FUSE_BUF_FD_RETRY))
+ break;
+
+ dst_off += res;
+ src_off += res;
+ len -= res;
+ }
+
+ return copied;
+}
+
+static ssize_t fuse_buf_fd_to_fd(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len)
+{
+ char buf[4096];
+ struct fuse_buf tmp = {
+ .size = sizeof(buf),
+ .flags = 0,
+ };
+ ssize_t res;
+ size_t copied = 0;
+
+ tmp.mem = buf;
+
+ while (len) {
+ size_t this_len = min_size(tmp.size, len);
+ size_t read_len;
+
+ res = fuse_buf_read(&tmp, 0, src, src_off, this_len);
+ if (res < 0) {
+ if (!copied)
+ return res;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ read_len = res;
+ res = fuse_buf_write(dst, dst_off, &tmp, 0, read_len);
+ if (res < 0) {
+ if (!copied)
+ return res;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+
+ if (res < this_len)
+ break;
+
+ dst_off += res;
+ src_off += res;
+ len -= res;
+ }
+
+ return copied;
+}
+
+#ifdef HAVE_SPLICE
+static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len, enum fuse_buf_copy_flags flags)
+{
+ int splice_flags = 0;
+ off_t *srcpos = NULL;
+ off_t *dstpos = NULL;
+ off_t srcpos_val;
+ off_t dstpos_val;
+ ssize_t res;
+ size_t copied = 0;
+
+ if (flags & FUSE_BUF_SPLICE_MOVE)
+ splice_flags |= SPLICE_F_MOVE;
+ if (flags & FUSE_BUF_SPLICE_NONBLOCK)
+ splice_flags |= SPLICE_F_NONBLOCK;
+
+ if (src->flags & FUSE_BUF_FD_SEEK) {
+ srcpos_val = src->pos + src_off;
+ srcpos = &srcpos_val;
+ }
+ if (dst->flags & FUSE_BUF_FD_SEEK) {
+ dstpos_val = dst->pos + dst_off;
+ dstpos = &dstpos_val;
+ }
+
+ while (len) {
+ res = splice(src->fd, srcpos, dst->fd, dstpos, len,
+ splice_flags);
+ if (res == -1) {
+ if (copied)
+ break;
+
+ if (errno != EINVAL || (flags & FUSE_BUF_FORCE_SPLICE))
+ return -errno;
+
+ /* Maybe splice is not supported for this combination */
+ return fuse_buf_fd_to_fd(dst, dst_off, src, src_off,
+ len);
+ }
+ if (res == 0)
+ break;
+
+ copied += res;
+ if (!(src->flags & FUSE_BUF_FD_RETRY) &&
+ !(dst->flags & FUSE_BUF_FD_RETRY)) {
+ break;
+ }
+
+ len -= res;
+ }
+
+ return copied;
+}
+#else
+static ssize_t fuse_buf_splice(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len, enum fuse_buf_copy_flags flags)
+{
+ (void) flags;
+
+ return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+}
+#endif
+
+
+static ssize_t fuse_buf_copy_one(const struct fuse_buf *dst, size_t dst_off,
+ const struct fuse_buf *src, size_t src_off,
+ size_t len, enum fuse_buf_copy_flags flags)
+{
+ int src_is_fd = src->flags & FUSE_BUF_IS_FD;
+ int dst_is_fd = dst->flags & FUSE_BUF_IS_FD;
+
+ if (!src_is_fd && !dst_is_fd) {
+ char *dstmem = (char *)dst->mem + dst_off;
+ char *srcmem = (char *)src->mem + src_off;
+
+ if (dstmem != srcmem) {
+ if (dstmem + len <= srcmem || srcmem + len <= dstmem)
+ memcpy(dstmem, srcmem, len);
+ else
+ memmove(dstmem, srcmem, len);
+ }
+
+ return len;
+ } else if (!src_is_fd) {
+ return fuse_buf_write(dst, dst_off, src, src_off, len);
+ } else if (!dst_is_fd) {
+ return fuse_buf_read(dst, dst_off, src, src_off, len);
+ } else if (flags & FUSE_BUF_NO_SPLICE) {
+ return fuse_buf_fd_to_fd(dst, dst_off, src, src_off, len);
+ } else {
+ return fuse_buf_splice(dst, dst_off, src, src_off, len, flags);
+ }
+}
+
+static const struct fuse_buf *fuse_bufvec_current(struct fuse_bufvec *bufv)
+{
+ if (bufv->idx < bufv->count)
+ return &bufv->buf[bufv->idx];
+ else
+ return NULL;
+}
+
+static int fuse_bufvec_advance(struct fuse_bufvec *bufv, size_t len)
+{
+ const struct fuse_buf *buf = fuse_bufvec_current(bufv);
+
+ bufv->off += len;
+ assert(bufv->off <= buf->size);
+ if (bufv->off == buf->size) {
+ assert(bufv->idx < bufv->count);
+ bufv->idx++;
+ if (bufv->idx == bufv->count)
+ return 0;
+ bufv->off = 0;
+ }
+ return 1;
+}
+
+ssize_t fuse_buf_copy(struct fuse_bufvec *dstv, struct fuse_bufvec *srcv,
+ enum fuse_buf_copy_flags flags)
+{
+ size_t copied = 0;
+
+ if (dstv == srcv)
+ return fuse_buf_size(dstv);
+
+ for (;;) {
+ const struct fuse_buf *src = fuse_bufvec_current(srcv);
+ const struct fuse_buf *dst = fuse_bufvec_current(dstv);
+ size_t src_len;
+ size_t dst_len;
+ size_t len;
+ ssize_t res;
+
+ if (src == NULL || dst == NULL)
+ break;
+
+ src_len = src->size - srcv->off;
+ dst_len = dst->size - dstv->off;
+ len = min_size(src_len, dst_len);
+
+ res = fuse_buf_copy_one(dst, dstv->off, src, srcv->off, len, flags);
+ if (res < 0) {
+ if (!copied)
+ return res;
+ break;
+ }
+ copied += res;
+
+ if (!fuse_bufvec_advance(srcv, res) ||
+ !fuse_bufvec_advance(dstv, res))
+ break;
+
+ if (res < len)
+ break;
+ }
+
+ return copied;
+}
diff --git a/lib/config.h b/lib/config.h
new file mode 100644
index 0000000..2e1624d
--- /dev/null
+++ b/lib/config.h
@@ -0,0 +1,42 @@
+/*
+ * Autogenerated by the Meson build system.
+ * Do not edit, your changes will be lost.
+ */
+
+#pragma once
+
+#define HAVE_COPY_FILE_RANGE
+
+#define HAVE_FALLOCATE
+
+#define HAVE_FDATASYNC
+
+#define HAVE_FORK
+
+#define HAVE_FSTATAT
+
+#define HAVE_ICONV
+
+#define HAVE_OPENAT
+
+#define HAVE_PIPE2
+
+#define HAVE_POSIX_FALLOCATE
+
+#define HAVE_READLINKAT
+
+#define HAVE_SETXATTR
+
+#define HAVE_SPLICE
+
+#define HAVE_STRUCT_STAT_ST_ATIM
+
+#undef HAVE_STRUCT_STAT_ST_ATIMESPEC
+
+#define HAVE_UTIMENSAT
+
+#define HAVE_VMSPLICE
+
+#define PACKAGE_VERSION "3.6.1"
+
+#include "android_config.h"
diff --git a/lib/cuse_lowlevel.c b/lib/cuse_lowlevel.c
new file mode 100644
index 0000000..019b20f
--- /dev/null
+++ b/lib/cuse_lowlevel.c
@@ -0,0 +1,366 @@
+/*
+ CUSE: Character device in Userspace
+ Copyright (C) 2008 SUSE Linux Products GmbH
+ Copyright (C) 2008 Tejun Heo <teheo@suse.de>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "cuse_lowlevel.h"
+#include "fuse_kernel.h"
+#include "fuse_i.h"
+#include "fuse_opt.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <errno.h>
+#include <unistd.h>
+
+struct cuse_data {
+ struct cuse_lowlevel_ops clop;
+ unsigned max_read;
+ unsigned dev_major;
+ unsigned dev_minor;
+ unsigned flags;
+ unsigned dev_info_len;
+ char dev_info[];
+};
+
+static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
+{
+ return &req->se->cuse_data->clop;
+}
+
+static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ (void)ino;
+ req_clop(req)->open(req, fi);
+}
+
+static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t off, struct fuse_file_info *fi)
+{
+ (void)ino;
+ req_clop(req)->read(req, size, off, fi);
+}
+
+static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+ size_t size, off_t off, struct fuse_file_info *fi)
+{
+ (void)ino;
+ req_clop(req)->write(req, buf, size, off, fi);
+}
+
+static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ (void)ino;
+ req_clop(req)->flush(req, fi);
+}
+
+static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ (void)ino;
+ req_clop(req)->release(req, fi);
+}
+
+static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+ struct fuse_file_info *fi)
+{
+ (void)ino;
+ req_clop(req)->fsync(req, datasync, fi);
+}
+
+static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
+ struct fuse_file_info *fi, unsigned int flags,
+ const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+{
+ (void)ino;
+ req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
+ out_bufsz);
+}
+
+static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+{
+ (void)ino;
+ req_clop(req)->poll(req, fi, ph);
+}
+
+static size_t cuse_pack_info(int argc, const char **argv, char *buf)
+{
+ size_t size = 0;
+ int i;
+
+ for (i = 0; i < argc; i++) {
+ size_t len;
+
+ len = strlen(argv[i]) + 1;
+ size += len;
+ if (buf) {
+ memcpy(buf, argv[i], len);
+ buf += len;
+ }
+ }
+
+ return size;
+}
+
+static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
+ const struct cuse_lowlevel_ops *clop)
+{
+ struct cuse_data *cd;
+ size_t dev_info_len;
+
+ dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
+ NULL);
+
+ if (dev_info_len > CUSE_INIT_INFO_MAX) {
+ fprintf(stderr, "cuse: dev_info (%zu) too large, limit=%u\n",
+ dev_info_len, CUSE_INIT_INFO_MAX);
+ return NULL;
+ }
+
+ cd = calloc(1, sizeof(*cd) + dev_info_len);
+ if (!cd) {
+ fprintf(stderr, "cuse: failed to allocate cuse_data\n");
+ return NULL;
+ }
+
+ memcpy(&cd->clop, clop, sizeof(cd->clop));
+ cd->max_read = 131072;
+ cd->dev_major = ci->dev_major;
+ cd->dev_minor = ci->dev_minor;
+ cd->dev_info_len = dev_info_len;
+ cd->flags = ci->flags;
+ cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
+
+ return cd;
+}
+
+struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
+ const struct cuse_info *ci,
+ const struct cuse_lowlevel_ops *clop,
+ void *userdata)
+{
+ struct fuse_lowlevel_ops lop;
+ struct cuse_data *cd;
+ struct fuse_session *se;
+
+ cd = cuse_prep_data(ci, clop);
+ if (!cd)
+ return NULL;
+
+ memset(&lop, 0, sizeof(lop));
+ lop.init = clop->init;
+ lop.destroy = clop->destroy;
+ lop.open = clop->open ? cuse_fll_open : NULL;
+ lop.read = clop->read ? cuse_fll_read : NULL;
+ lop.write = clop->write ? cuse_fll_write : NULL;
+ lop.flush = clop->flush ? cuse_fll_flush : NULL;
+ lop.release = clop->release ? cuse_fll_release : NULL;
+ lop.fsync = clop->fsync ? cuse_fll_fsync : NULL;
+ lop.ioctl = clop->ioctl ? cuse_fll_ioctl : NULL;
+ lop.poll = clop->poll ? cuse_fll_poll : NULL;
+
+ se = fuse_session_new(args, &lop, sizeof(lop), userdata);
+ if (!se) {
+ free(cd);
+ return NULL;
+ }
+ se->cuse_data = cd;
+
+ return se;
+}
+
+static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
+ char *dev_info, unsigned dev_info_len)
+{
+ struct iovec iov[3];
+
+ iov[1].iov_base = arg;
+ iov[1].iov_len = sizeof(struct cuse_init_out);
+ iov[2].iov_base = dev_info;
+ iov[2].iov_len = dev_info_len;
+
+ return fuse_send_reply_iov_nofree(req, 0, iov, 3);
+}
+
+void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+ struct cuse_init_out outarg;
+ struct fuse_session *se = req->se;
+ struct cuse_data *cd = se->cuse_data;
+ size_t bufsize = se->bufsize;
+ struct cuse_lowlevel_ops *clop = req_clop(req);
+
+ (void) nodeid;
+ if (se->debug) {
+ fprintf(stderr, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
+ fprintf(stderr, "flags=0x%08x\n", arg->flags);
+ }
+ se->conn.proto_major = arg->major;
+ se->conn.proto_minor = arg->minor;
+ se->conn.capable = 0;
+ se->conn.want = 0;
+
+ if (arg->major < 7) {
+ fprintf(stderr, "cuse: unsupported protocol version: %u.%u\n",
+ arg->major, arg->minor);
+ fuse_reply_err(req, EPROTO);
+ return;
+ }
+
+ if (bufsize < FUSE_MIN_READ_BUFFER) {
+ fprintf(stderr, "cuse: warning: buffer size too small: %zu\n",
+ bufsize);
+ bufsize = FUSE_MIN_READ_BUFFER;
+ }
+
+ bufsize -= 4096;
+ if (bufsize < se->conn.max_write)
+ se->conn.max_write = bufsize;
+
+ se->got_init = 1;
+ if (se->op.init)
+ se->op.init(se->userdata, &se->conn);
+
+ memset(&outarg, 0, sizeof(outarg));
+ outarg.major = FUSE_KERNEL_VERSION;
+ outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+ outarg.flags = cd->flags;
+ outarg.max_read = cd->max_read;
+ outarg.max_write = se->conn.max_write;
+ outarg.dev_major = cd->dev_major;
+ outarg.dev_minor = cd->dev_minor;
+
+ if (se->debug) {
+ fprintf(stderr, " CUSE_INIT: %u.%u\n",
+ outarg.major, outarg.minor);
+ fprintf(stderr, " flags=0x%08x\n", outarg.flags);
+ fprintf(stderr, " max_read=0x%08x\n", outarg.max_read);
+ fprintf(stderr, " max_write=0x%08x\n", outarg.max_write);
+ fprintf(stderr, " dev_major=%u\n", outarg.dev_major);
+ fprintf(stderr, " dev_minor=%u\n", outarg.dev_minor);
+ fprintf(stderr, " dev_info: %.*s\n", cd->dev_info_len,
+ cd->dev_info);
+ }
+
+ cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
+
+ if (clop->init_done)
+ clop->init_done(se->userdata);
+
+ fuse_free_req(req);
+}
+
+struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
+ const struct cuse_info *ci,
+ const struct cuse_lowlevel_ops *clop,
+ int *multithreaded, void *userdata)
+{
+ const char *devname = "/dev/cuse";
+ static const struct fuse_opt kill_subtype_opts[] = {
+ FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_DISCARD),
+ FUSE_OPT_END
+ };
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ struct fuse_session *se;
+ struct fuse_cmdline_opts opts;
+ int fd;
+ int res;
+
+ if (fuse_parse_cmdline(&args, &opts) == -1)
+ return NULL;
+ *multithreaded = !opts.singlethread;
+
+ /* Remove subtype= option */
+ res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
+ if (res == -1)
+ goto out1;
+
+ /*
+ * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+ * would ensue.
+ */
+ do {
+ fd = open("/dev/null", O_RDWR);
+ if (fd > 2)
+ close(fd);
+ } while (fd >= 0 && fd <= 2);
+
+ se = cuse_lowlevel_new(&args, ci, clop, userdata);
+ if (se == NULL)
+ goto out1;
+
+ fd = open(devname, O_RDWR);
+ if (fd == -1) {
+ if (errno == ENODEV || errno == ENOENT)
+ fprintf(stderr, "cuse: device not found, try 'modprobe cuse' first\n");
+ else
+ fprintf(stderr, "cuse: failed to open %s: %s\n",
+ devname, strerror(errno));
+ goto err_se;
+ }
+ se->fd = fd;
+
+ res = fuse_set_signal_handlers(se);
+ if (res == -1)
+ goto err_se;
+
+ res = fuse_daemonize(opts.foreground);
+ if (res == -1)
+ goto err_sig;
+
+ return se;
+
+err_sig:
+ fuse_remove_signal_handlers(se);
+err_se:
+ fuse_session_destroy(se);
+out1:
+ free(opts.mountpoint);
+ fuse_opt_free_args(&args);
+ return NULL;
+}
+
+void cuse_lowlevel_teardown(struct fuse_session *se)
+{
+ fuse_remove_signal_handlers(se);
+ fuse_session_destroy(se);
+}
+
+int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
+ const struct cuse_lowlevel_ops *clop, void *userdata)
+{
+ struct fuse_session *se;
+ int multithreaded;
+ int res;
+
+ se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
+ userdata);
+ if (se == NULL)
+ return 1;
+
+ if (multithreaded) {
+ struct fuse_loop_config config;
+ config.clone_fd = 0;
+ config.max_idle_threads = 10;
+ res = fuse_session_loop_mt_32(se, &config);
+ }
+ else
+ res = fuse_session_loop(se);
+
+ cuse_lowlevel_teardown(se);
+ if (res == -1)
+ return 1;
+
+ return 0;
+}
diff --git a/lib/fuse.c b/lib/fuse.c
new file mode 100755
index 0000000..ec6cc59
--- /dev/null
+++ b/lib/fuse.c
@@ -0,0 +1,5076 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Implementation of the high-level FUSE API on top of the low-level
+ API.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+
+/* For pthread_rwlock_t */
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_lowlevel.h"
+#include "fuse_opt.h"
+#include "fuse_misc.h"
+#include "fuse_kernel.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <time.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <errno.h>
+#include <signal.h>
+#include <dlfcn.h>
+#include <assert.h>
+#include <poll.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <sys/time.h>
+#include <sys/mman.h>
+#include <sys/file.h>
+
+#define FUSE_NODE_SLAB 1
+
+#ifndef MAP_ANONYMOUS
+#undef FUSE_NODE_SLAB
+#endif
+
+#ifndef RENAME_EXCHANGE
+#define RENAME_EXCHANGE (1 << 1) /* Exchange source and dest */
+#endif
+
+#define FUSE_DEFAULT_INTR_SIGNAL SIGUSR1
+
+#define FUSE_UNKNOWN_INO 0xffffffff
+#define OFFSET_MAX 0x7fffffffffffffffLL
+
+#define NODE_TABLE_MIN_SIZE 8192
+
+struct fuse_fs {
+ struct fuse_operations op;
+ struct fuse_module *m;
+ void *user_data;
+ int debug;
+};
+
+struct fusemod_so {
+ void *handle;
+ int ctr;
+};
+
+struct lock_queue_element {
+ struct lock_queue_element *next;
+ pthread_cond_t cond;
+ fuse_ino_t nodeid1;
+ const char *name1;
+ char **path1;
+ struct node **wnode1;
+ fuse_ino_t nodeid2;
+ const char *name2;
+ char **path2;
+ struct node **wnode2;
+ int err;
+ bool first_locked : 1;
+ bool second_locked : 1;
+ bool done : 1;
+};
+
+struct node_table {
+ struct node **array;
+ size_t use;
+ size_t size;
+ size_t split;
+};
+
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#define list_entry(ptr, type, member) \
+ container_of(ptr, type, member)
+
+struct list_head {
+ struct list_head *next;
+ struct list_head *prev;
+};
+
+struct node_slab {
+ struct list_head list; /* must be the first member */
+ struct list_head freelist;
+ int used;
+};
+
+struct fuse {
+ struct fuse_session *se;
+ struct node_table name_table;
+ struct node_table id_table;
+ struct list_head lru_table;
+ fuse_ino_t ctr;
+ unsigned int generation;
+ unsigned int hidectr;
+ pthread_mutex_t lock;
+ struct fuse_config conf;
+ int intr_installed;
+ struct fuse_fs *fs;
+ struct lock_queue_element *lockq;
+ int pagesize;
+ struct list_head partial_slabs;
+ struct list_head full_slabs;
+ pthread_t prune_thread;
+};
+
+struct lock {
+ int type;
+ off_t start;
+ off_t end;
+ pid_t pid;
+ uint64_t owner;
+ struct lock *next;
+};
+
+struct node {
+ struct node *name_next;
+ struct node *id_next;
+ fuse_ino_t nodeid;
+ unsigned int generation;
+ int refctr;
+ struct node *parent;
+ char *name;
+ uint64_t nlookup;
+ int open_count;
+ struct timespec stat_updated;
+ struct timespec mtime;
+ off_t size;
+ struct lock *locks;
+ unsigned int is_hidden : 1;
+ unsigned int cache_valid : 1;
+ int treelock;
+ char inline_name[32];
+};
+
+#define TREELOCK_WRITE -1
+#define TREELOCK_WAIT_OFFSET INT_MIN
+
+struct node_lru {
+ struct node node;
+ struct list_head lru;
+ struct timespec forget_time;
+};
+
+struct fuse_direntry {
+ struct stat stat;
+ char *name;
+ struct fuse_direntry *next;
+};
+
+struct fuse_dh {
+ pthread_mutex_t lock;
+ struct fuse *fuse;
+ fuse_req_t req;
+ char *contents;
+ struct fuse_direntry *first;
+ struct fuse_direntry **last;
+ unsigned len;
+ unsigned size;
+ unsigned needlen;
+ int filled;
+ uint64_t fh;
+ int error;
+ fuse_ino_t nodeid;
+};
+
+struct fuse_context_i {
+ struct fuse_context ctx;
+ fuse_req_t req;
+};
+
+/* Defined by FUSE_REGISTER_MODULE() in lib/modules/subdir.c and iconv.c. */
+extern fuse_module_factory_t fuse_module_subdir_factory;
+#ifdef HAVE_ICONV
+extern fuse_module_factory_t fuse_module_iconv_factory;
+#endif
+
+static pthread_key_t fuse_context_key;
+static pthread_mutex_t fuse_context_lock = PTHREAD_MUTEX_INITIALIZER;
+static int fuse_context_ref;
+static struct fuse_module *fuse_modules = NULL;
+
+static int fuse_register_module(const char *name,
+ fuse_module_factory_t factory,
+ struct fusemod_so *so)
+{
+ struct fuse_module *mod;
+
+ mod = calloc(1, sizeof(struct fuse_module));
+ if (!mod) {
+ fprintf(stderr, "fuse: failed to allocate module\n");
+ return -1;
+ }
+ mod->name = strdup(name);
+ if (!mod->name) {
+ fprintf(stderr, "fuse: failed to allocate module name\n");
+ free(mod);
+ return -1;
+ }
+ mod->factory = factory;
+ mod->ctr = 0;
+ mod->so = so;
+ if (mod->so)
+ mod->so->ctr++;
+ mod->next = fuse_modules;
+ fuse_modules = mod;
+
+ return 0;
+}
+
+static void fuse_unregister_module(struct fuse_module *m)
+{
+ struct fuse_module **mp;
+ for (mp = &fuse_modules; *mp; mp = &(*mp)->next) {
+ if (*mp == m) {
+ *mp = (*mp)->next;
+ break;
+ }
+ }
+ free(m->name);
+ free(m);
+}
+
+static int fuse_load_so_module(const char *module)
+{
+ int ret = -1;
+ char *tmp;
+ struct fusemod_so *so;
+ fuse_module_factory_t factory;
+
+ tmp = malloc(strlen(module) + 64);
+ if (!tmp) {
+ fprintf(stderr, "fuse: memory allocation failed\n");
+ return -1;
+ }
+ sprintf(tmp, "libfusemod_%s.so", module);
+ so = calloc(1, sizeof(struct fusemod_so));
+ if (!so) {
+ fprintf(stderr, "fuse: failed to allocate module so\n");
+ goto out;
+ }
+
+ so->handle = dlopen(tmp, RTLD_NOW);
+ if (so->handle == NULL) {
+ fprintf(stderr, "fuse: dlopen(%s) failed: %s\n",
+ tmp, dlerror());
+ goto out_free_so;
+ }
+
+ sprintf(tmp, "fuse_module_%s_factory", module);
+ *(void**)(&factory) = dlsym(so->handle, tmp);
+ if (factory == NULL) {
+ fprintf(stderr, "fuse: symbol <%s> not found in module: %s\n",
+ tmp, dlerror());
+ goto out_dlclose;
+ }
+ ret = fuse_register_module(module, factory, so);
+ if (ret)
+ goto out_dlclose;
+
+out:
+ free(tmp);
+ return ret;
+
+out_dlclose:
+ dlclose(so->handle);
+out_free_so:
+ free(so);
+ goto out;
+}
+
+static struct fuse_module *fuse_find_module(const char *module)
+{
+ struct fuse_module *m;
+ for (m = fuse_modules; m; m = m->next) {
+ if (strcmp(module, m->name) == 0) {
+ m->ctr++;
+ break;
+ }
+ }
+ return m;
+}
+
+static struct fuse_module *fuse_get_module(const char *module)
+{
+ struct fuse_module *m;
+
+ pthread_mutex_lock(&fuse_context_lock);
+ m = fuse_find_module(module);
+ if (!m) {
+ int err = fuse_load_so_module(module);
+ if (!err)
+ m = fuse_find_module(module);
+ }
+ pthread_mutex_unlock(&fuse_context_lock);
+ return m;
+}
+
+static void fuse_put_module(struct fuse_module *m)
+{
+ pthread_mutex_lock(&fuse_context_lock);
+ if (m->so)
+ assert(m->ctr > 0);
+ /* Builtin modules may already have m->ctr == 0 */
+ if (m->ctr > 0)
+ m->ctr--;
+ if (!m->ctr && m->so) {
+ struct fusemod_so *so = m->so;
+ assert(so->ctr > 0);
+ so->ctr--;
+ if (!so->ctr) {
+ struct fuse_module **mp;
+ for (mp = &fuse_modules; *mp;) {
+ if ((*mp)->so == so)
+ fuse_unregister_module(*mp);
+ else
+ mp = &(*mp)->next;
+ }
+ dlclose(so->handle);
+ free(so);
+ }
+ } else if (!m->ctr) {
+ fuse_unregister_module(m);
+ }
+ pthread_mutex_unlock(&fuse_context_lock);
+}
+
+static void init_list_head(struct list_head *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static int list_empty(const struct list_head *head)
+{
+ return head->next == head;
+}
+
+static void list_add(struct list_head *new, struct list_head *prev,
+ struct list_head *next)
+{
+ next->prev = new;
+ new->next = next;
+ new->prev = prev;
+ prev->next = new;
+}
+
+static inline void list_add_head(struct list_head *new, struct list_head *head)
+{
+ list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+ list_add(new, head->prev, head);
+}
+
+static inline void list_del(struct list_head *entry)
+{
+ struct list_head *prev = entry->prev;
+ struct list_head *next = entry->next;
+
+ next->prev = prev;
+ prev->next = next;
+}
+
+static inline int lru_enabled(struct fuse *f)
+{
+ return f->conf.remember > 0;
+}
+
+static struct node_lru *node_lru(struct node *node)
+{
+ return (struct node_lru *) node;
+}
+
+static size_t get_node_size(struct fuse *f)
+{
+ if (lru_enabled(f))
+ return sizeof(struct node_lru);
+ else
+ return sizeof(struct node);
+}
+
+#ifdef FUSE_NODE_SLAB
+static struct node_slab *list_to_slab(struct list_head *head)
+{
+ return (struct node_slab *) head;
+}
+
+static struct node_slab *node_to_slab(struct fuse *f, struct node *node)
+{
+ return (struct node_slab *) (((uintptr_t) node) & ~((uintptr_t) f->pagesize - 1));
+}
+
+static int alloc_slab(struct fuse *f)
+{
+ void *mem;
+ struct node_slab *slab;
+ char *start;
+ size_t num;
+ size_t i;
+ size_t node_size = get_node_size(f);
+
+ mem = mmap(NULL, f->pagesize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ if (mem == MAP_FAILED)
+ return -1;
+
+ slab = mem;
+ init_list_head(&slab->freelist);
+ slab->used = 0;
+ num = (f->pagesize - sizeof(struct node_slab)) / node_size;
+
+ start = (char *) mem + f->pagesize - num * node_size;
+ for (i = 0; i < num; i++) {
+ struct list_head *n;
+
+ n = (struct list_head *) (start + i * node_size);
+ list_add_tail(n, &slab->freelist);
+ }
+ list_add_tail(&slab->list, &f->partial_slabs);
+
+ return 0;
+}
+
+static struct node *alloc_node(struct fuse *f)
+{
+ struct node_slab *slab;
+ struct list_head *node;
+
+ if (list_empty(&f->partial_slabs)) {
+ int res = alloc_slab(f);
+ if (res != 0)
+ return NULL;
+ }
+ slab = list_to_slab(f->partial_slabs.next);
+ slab->used++;
+ node = slab->freelist.next;
+ list_del(node);
+ if (list_empty(&slab->freelist)) {
+ list_del(&slab->list);
+ list_add_tail(&slab->list, &f->full_slabs);
+ }
+ memset(node, 0, sizeof(struct node));
+
+ return (struct node *) node;
+}
+
+static void free_slab(struct fuse *f, struct node_slab *slab)
+{
+ int res;
+
+ list_del(&slab->list);
+ res = munmap(slab, f->pagesize);
+ if (res == -1)
+ fprintf(stderr, "fuse warning: munmap(%p) failed\n", slab);
+}
+
+static void free_node_mem(struct fuse *f, struct node *node)
+{
+ struct node_slab *slab = node_to_slab(f, node);
+ struct list_head *n = (struct list_head *) node;
+
+ slab->used--;
+ if (slab->used) {
+ if (list_empty(&slab->freelist)) {
+ list_del(&slab->list);
+ list_add_tail(&slab->list, &f->partial_slabs);
+ }
+ list_add_head(n, &slab->freelist);
+ } else {
+ free_slab(f, slab);
+ }
+}
+#else
+static struct node *alloc_node(struct fuse *f)
+{
+ return (struct node *) calloc(1, get_node_size(f));
+}
+
+static void free_node_mem(struct fuse *f, struct node *node)
+{
+ (void) f;
+ free(node);
+}
+#endif
+
+static size_t id_hash(struct fuse *f, fuse_ino_t ino)
+{
+ uint64_t hash = ((uint32_t) ino * 2654435761U) % f->id_table.size;
+ uint64_t oldhash = hash % (f->id_table.size / 2);
+
+ if (oldhash >= f->id_table.split)
+ return oldhash;
+ else
+ return hash;
+}
+
+static struct node *get_node_nocheck(struct fuse *f, fuse_ino_t nodeid)
+{
+ size_t hash = id_hash(f, nodeid);
+ struct node *node;
+
+ for (node = f->id_table.array[hash]; node != NULL; node = node->id_next)
+ if (node->nodeid == nodeid)
+ return node;
+
+ return NULL;
+}
+
+static struct node *get_node(struct fuse *f, fuse_ino_t nodeid)
+{
+ struct node *node = get_node_nocheck(f, nodeid);
+ if (!node) {
+ fprintf(stderr, "fuse internal error: node %llu not found\n",
+ (unsigned long long) nodeid);
+ abort();
+ }
+ return node;
+}
+
+static void curr_time(struct timespec *now);
+static double diff_timespec(const struct timespec *t1,
+ const struct timespec *t2);
+
+static void remove_node_lru(struct node *node)
+{
+ struct node_lru *lnode = node_lru(node);
+ list_del(&lnode->lru);
+ init_list_head(&lnode->lru);
+}
+
+static void set_forget_time(struct fuse *f, struct node *node)
+{
+ struct node_lru *lnode = node_lru(node);
+
+ list_del(&lnode->lru);
+ list_add_tail(&lnode->lru, &f->lru_table);
+ curr_time(&lnode->forget_time);
+}
+
+static void free_node(struct fuse *f, struct node *node)
+{
+ if (node->name != node->inline_name)
+ free(node->name);
+ free_node_mem(f, node);
+}
+
+static void node_table_reduce(struct node_table *t)
+{
+ size_t newsize = t->size / 2;
+ void *newarray;
+
+ if (newsize < NODE_TABLE_MIN_SIZE)
+ return;
+
+ newarray = realloc(t->array, sizeof(struct node *) * newsize);
+ if (newarray != NULL)
+ t->array = newarray;
+
+ t->size = newsize;
+ t->split = t->size / 2;
+}
+
+static void remerge_id(struct fuse *f)
+{
+ struct node_table *t = &f->id_table;
+ int iter;
+
+ if (t->split == 0)
+ node_table_reduce(t);
+
+ for (iter = 8; t->split > 0 && iter; iter--) {
+ struct node **upper;
+
+ t->split--;
+ upper = &t->array[t->split + t->size / 2];
+ if (*upper) {
+ struct node **nodep;
+
+ for (nodep = &t->array[t->split]; *nodep;
+ nodep = &(*nodep)->id_next);
+
+ *nodep = *upper;
+ *upper = NULL;
+ break;
+ }
+ }
+}
+
+static void unhash_id(struct fuse *f, struct node *node)
+{
+ struct node **nodep = &f->id_table.array[id_hash(f, node->nodeid)];
+
+ for (; *nodep != NULL; nodep = &(*nodep)->id_next)
+ if (*nodep == node) {
+ *nodep = node->id_next;
+ f->id_table.use--;
+
+ if(f->id_table.use < f->id_table.size / 4)
+ remerge_id(f);
+ return;
+ }
+}
+
+static int node_table_resize(struct node_table *t)
+{
+ size_t newsize = t->size * 2;
+ void *newarray;
+
+ newarray = realloc(t->array, sizeof(struct node *) * newsize);
+ if (newarray == NULL)
+ return -1;
+
+ t->array = newarray;
+ memset(t->array + t->size, 0, t->size * sizeof(struct node *));
+ t->size = newsize;
+ t->split = 0;
+
+ return 0;
+}
+
+static void rehash_id(struct fuse *f)
+{
+ struct node_table *t = &f->id_table;
+ struct node **nodep;
+ struct node **next;
+ size_t hash;
+
+ if (t->split == t->size / 2)
+ return;
+
+ hash = t->split;
+ t->split++;
+ for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+ struct node *node = *nodep;
+ size_t newhash = id_hash(f, node->nodeid);
+
+ if (newhash != hash) {
+ next = nodep;
+ *nodep = node->id_next;
+ node->id_next = t->array[newhash];
+ t->array[newhash] = node;
+ } else {
+ next = &node->id_next;
+ }
+ }
+ if (t->split == t->size / 2)
+ node_table_resize(t);
+}
+
+static void hash_id(struct fuse *f, struct node *node)
+{
+ size_t hash = id_hash(f, node->nodeid);
+ node->id_next = f->id_table.array[hash];
+ f->id_table.array[hash] = node;
+ f->id_table.use++;
+
+ if (f->id_table.use >= f->id_table.size / 2)
+ rehash_id(f);
+}
+
+static size_t name_hash(struct fuse *f, fuse_ino_t parent,
+ const char *name)
+{
+ uint64_t hash = parent;
+ uint64_t oldhash;
+
+ for (; *name; name++)
+ hash = hash * 31 + (unsigned char) *name;
+
+ hash %= f->name_table.size;
+ oldhash = hash % (f->name_table.size / 2);
+ if (oldhash >= f->name_table.split)
+ return oldhash;
+ else
+ return hash;
+}
+
+static void unref_node(struct fuse *f, struct node *node);
+
+static void remerge_name(struct fuse *f)
+{
+ struct node_table *t = &f->name_table;
+ int iter;
+
+ if (t->split == 0)
+ node_table_reduce(t);
+
+ for (iter = 8; t->split > 0 && iter; iter--) {
+ struct node **upper;
+
+ t->split--;
+ upper = &t->array[t->split + t->size / 2];
+ if (*upper) {
+ struct node **nodep;
+
+ for (nodep = &t->array[t->split]; *nodep;
+ nodep = &(*nodep)->name_next);
+
+ *nodep = *upper;
+ *upper = NULL;
+ break;
+ }
+ }
+}
+
+static void unhash_name(struct fuse *f, struct node *node)
+{
+ if (node->name) {
+ size_t hash = name_hash(f, node->parent->nodeid, node->name);
+ struct node **nodep = &f->name_table.array[hash];
+
+ for (; *nodep != NULL; nodep = &(*nodep)->name_next)
+ if (*nodep == node) {
+ *nodep = node->name_next;
+ node->name_next = NULL;
+ unref_node(f, node->parent);
+ if (node->name != node->inline_name)
+ free(node->name);
+ node->name = NULL;
+ node->parent = NULL;
+ f->name_table.use--;
+
+ if (f->name_table.use < f->name_table.size / 4)
+ remerge_name(f);
+ return;
+ }
+ fprintf(stderr,
+ "fuse internal error: unable to unhash node: %llu\n",
+ (unsigned long long) node->nodeid);
+ abort();
+ }
+}
+
+static void rehash_name(struct fuse *f)
+{
+ struct node_table *t = &f->name_table;
+ struct node **nodep;
+ struct node **next;
+ size_t hash;
+
+ if (t->split == t->size / 2)
+ return;
+
+ hash = t->split;
+ t->split++;
+ for (nodep = &t->array[hash]; *nodep != NULL; nodep = next) {
+ struct node *node = *nodep;
+ size_t newhash = name_hash(f, node->parent->nodeid, node->name);
+
+ if (newhash != hash) {
+ next = nodep;
+ *nodep = node->name_next;
+ node->name_next = t->array[newhash];
+ t->array[newhash] = node;
+ } else {
+ next = &node->name_next;
+ }
+ }
+ if (t->split == t->size / 2)
+ node_table_resize(t);
+}
+
+static int hash_name(struct fuse *f, struct node *node, fuse_ino_t parentid,
+ const char *name)
+{
+ size_t hash = name_hash(f, parentid, name);
+ struct node *parent = get_node(f, parentid);
+ if (strlen(name) < sizeof(node->inline_name)) {
+ strcpy(node->inline_name, name);
+ node->name = node->inline_name;
+ } else {
+ node->name = strdup(name);
+ if (node->name == NULL)
+ return -1;
+ }
+
+ parent->refctr ++;
+ node->parent = parent;
+ node->name_next = f->name_table.array[hash];
+ f->name_table.array[hash] = node;
+ f->name_table.use++;
+
+ if (f->name_table.use >= f->name_table.size / 2)
+ rehash_name(f);
+
+ return 0;
+}
+
+static void delete_node(struct fuse *f, struct node *node)
+{
+ if (f->conf.debug)
+ fprintf(stderr, "DELETE: %llu\n",
+ (unsigned long long) node->nodeid);
+
+ assert(node->treelock == 0);
+ unhash_name(f, node);
+ if (lru_enabled(f))
+ remove_node_lru(node);
+ unhash_id(f, node);
+ free_node(f, node);
+}
+
+static void unref_node(struct fuse *f, struct node *node)
+{
+ assert(node->refctr > 0);
+ node->refctr --;
+ if (!node->refctr)
+ delete_node(f, node);
+}
+
+static fuse_ino_t next_id(struct fuse *f)
+{
+ do {
+ f->ctr = (f->ctr + 1) & 0xffffffff;
+ if (!f->ctr)
+ f->generation ++;
+ } while (f->ctr == 0 || f->ctr == FUSE_UNKNOWN_INO ||
+ get_node_nocheck(f, f->ctr) != NULL);
+ return f->ctr;
+}
+
+static struct node *lookup_node(struct fuse *f, fuse_ino_t parent,
+ const char *name)
+{
+ size_t hash = name_hash(f, parent, name);
+ struct node *node;
+
+ for (node = f->name_table.array[hash]; node != NULL; node = node->name_next)
+ if (node->parent->nodeid == parent &&
+ strcmp(node->name, name) == 0)
+ return node;
+
+ return NULL;
+}
+
+static void inc_nlookup(struct node *node)
+{
+ if (!node->nlookup)
+ node->refctr++;
+ node->nlookup++;
+}
+
+static struct node *find_node(struct fuse *f, fuse_ino_t parent,
+ const char *name)
+{
+ struct node *node;
+
+ pthread_mutex_lock(&f->lock);
+ if (!name)
+ node = get_node(f, parent);
+ else
+ node = lookup_node(f, parent, name);
+ if (node == NULL) {
+ node = alloc_node(f);
+ if (node == NULL)
+ goto out_err;
+
+ node->nodeid = next_id(f);
+ node->generation = f->generation;
+ if (f->conf.remember)
+ inc_nlookup(node);
+
+ if (hash_name(f, node, parent, name) == -1) {
+ free_node(f, node);
+ node = NULL;
+ goto out_err;
+ }
+ hash_id(f, node);
+ if (lru_enabled(f)) {
+ struct node_lru *lnode = node_lru(node);
+ init_list_head(&lnode->lru);
+ }
+ } else if (lru_enabled(f) && node->nlookup == 1) {
+ remove_node_lru(node);
+ }
+ inc_nlookup(node);
+out_err:
+ pthread_mutex_unlock(&f->lock);
+ return node;
+}
+
+static int lookup_path_in_cache(struct fuse *f,
+ const char *path, fuse_ino_t *inop)
+{
+ char *tmp = strdup(path);
+ if (!tmp)
+ return -ENOMEM;
+
+ pthread_mutex_lock(&f->lock);
+ fuse_ino_t ino = FUSE_ROOT_ID;
+
+ int err = 0;
+ char *save_ptr;
+ char *path_element = strtok_r(tmp, "/", &save_ptr);
+ while (path_element != NULL) {
+ struct node *node = lookup_node(f, ino, path_element);
+ if (node == NULL) {
+ err = -ENOENT;
+ break;
+ }
+ ino = node->nodeid;
+ path_element = strtok_r(NULL, "/", &save_ptr);
+ }
+ pthread_mutex_unlock(&f->lock);
+ free(tmp);
+
+ if (!err)
+ *inop = ino;
+ return err;
+}
+
+static char *add_name(char **buf, unsigned *bufsize, char *s, const char *name)
+{
+ size_t len = strlen(name);
+
+ if (s - len <= *buf) {
+ unsigned pathlen = *bufsize - (s - *buf);
+ unsigned newbufsize = *bufsize;
+ char *newbuf;
+
+ while (newbufsize < pathlen + len + 1) {
+ if (newbufsize >= 0x80000000)
+ newbufsize = 0xffffffff;
+ else
+ newbufsize *= 2;
+ }
+
+ newbuf = realloc(*buf, newbufsize);
+ if (newbuf == NULL)
+ return NULL;
+
+ *buf = newbuf;
+ s = newbuf + newbufsize - pathlen;
+ memmove(s, newbuf + *bufsize - pathlen, pathlen);
+ *bufsize = newbufsize;
+ }
+ s -= len;
+ strncpy(s, name, len);
+ s--;
+ *s = '/';
+
+ return s;
+}
+
+static void unlock_path(struct fuse *f, fuse_ino_t nodeid, struct node *wnode,
+ struct node *end)
+{
+ struct node *node;
+
+ if (wnode) {
+ assert(wnode->treelock == TREELOCK_WRITE);
+ wnode->treelock = 0;
+ }
+
+ for (node = get_node(f, nodeid);
+ node != end && node->nodeid != FUSE_ROOT_ID; node = node->parent) {
+ assert(node->treelock != 0);
+ assert(node->treelock != TREELOCK_WAIT_OFFSET);
+ assert(node->treelock != TREELOCK_WRITE);
+ node->treelock--;
+ if (node->treelock == TREELOCK_WAIT_OFFSET)
+ node->treelock = 0;
+ }
+}
+
+static int try_get_path(struct fuse *f, fuse_ino_t nodeid, const char *name,
+ char **path, struct node **wnodep, bool need_lock)
+{
+ unsigned bufsize = 256;
+ char *buf;
+ char *s;
+ struct node *node;
+ struct node *wnode = NULL;
+ int err;
+
+ *path = NULL;
+
+ err = -ENOMEM;
+ buf = malloc(bufsize);
+ if (buf == NULL)
+ goto out_err;
+
+ s = buf + bufsize - 1;
+ *s = '\0';
+
+ if (name != NULL) {
+ s = add_name(&buf, &bufsize, s, name);
+ err = -ENOMEM;
+ if (s == NULL)
+ goto out_free;
+ }
+
+ if (wnodep) {
+ assert(need_lock);
+ wnode = lookup_node(f, nodeid, name);
+ if (wnode) {
+ if (wnode->treelock != 0) {
+ if (wnode->treelock > 0)
+ wnode->treelock += TREELOCK_WAIT_OFFSET;
+ err = -EAGAIN;
+ goto out_free;
+ }
+ wnode->treelock = TREELOCK_WRITE;
+ }
+ }
+
+ for (node = get_node(f, nodeid); node->nodeid != FUSE_ROOT_ID;
+ node = node->parent) {
+ err = -ENOENT;
+ if (node->name == NULL || node->parent == NULL)
+ goto out_unlock;
+
+ err = -ENOMEM;
+ s = add_name(&buf, &bufsize, s, node->name);
+ if (s == NULL)
+ goto out_unlock;
+
+ if (need_lock) {
+ err = -EAGAIN;
+ if (node->treelock < 0)
+ goto out_unlock;
+
+ node->treelock++;
+ }
+ }
+
+ if (s[0])
+ memmove(buf, s, bufsize - (s - buf));
+ else
+ strcpy(buf, "/");
+
+ *path = buf;
+ if (wnodep)
+ *wnodep = wnode;
+
+ return 0;
+
+ out_unlock:
+ if (need_lock)
+ unlock_path(f, nodeid, wnode, node);
+ out_free:
+ free(buf);
+
+ out_err:
+ return err;
+}
+
+static void queue_element_unlock(struct fuse *f, struct lock_queue_element *qe)
+{
+ struct node *wnode;
+
+ if (qe->first_locked) {
+ wnode = qe->wnode1 ? *qe->wnode1 : NULL;
+ unlock_path(f, qe->nodeid1, wnode, NULL);
+ qe->first_locked = false;
+ }
+ if (qe->second_locked) {
+ wnode = qe->wnode2 ? *qe->wnode2 : NULL;
+ unlock_path(f, qe->nodeid2, wnode, NULL);
+ qe->second_locked = false;
+ }
+}
+
+static void queue_element_wakeup(struct fuse *f, struct lock_queue_element *qe)
+{
+ int err;
+ bool first = (qe == f->lockq);
+
+ if (!qe->path1) {
+ /* Just waiting for it to be unlocked */
+ if (get_node(f, qe->nodeid1)->treelock == 0)
+ pthread_cond_signal(&qe->cond);
+
+ return;
+ }
+
+ if (!qe->first_locked) {
+ err = try_get_path(f, qe->nodeid1, qe->name1, qe->path1,
+ qe->wnode1, true);
+ if (!err)
+ qe->first_locked = true;
+ else if (err != -EAGAIN)
+ goto err_unlock;
+ }
+ if (!qe->second_locked && qe->path2) {
+ err = try_get_path(f, qe->nodeid2, qe->name2, qe->path2,
+ qe->wnode2, true);
+ if (!err)
+ qe->second_locked = true;
+ else if (err != -EAGAIN)
+ goto err_unlock;
+ }
+
+ if (qe->first_locked && (qe->second_locked || !qe->path2)) {
+ err = 0;
+ goto done;
+ }
+
+ /*
+ * Only let the first element be partially locked otherwise there could
+ * be a deadlock.
+ *
+ * But do allow the first element to be partially locked to prevent
+ * starvation.
+ */
+ if (!first)
+ queue_element_unlock(f, qe);
+
+ /* keep trying */
+ return;
+
+err_unlock:
+ queue_element_unlock(f, qe);
+done:
+ qe->err = err;
+ qe->done = true;
+ pthread_cond_signal(&qe->cond);
+}
+
+static void wake_up_queued(struct fuse *f)
+{
+ struct lock_queue_element *qe;
+
+ for (qe = f->lockq; qe != NULL; qe = qe->next)
+ queue_element_wakeup(f, qe);
+}
+
+static void debug_path(struct fuse *f, const char *msg, fuse_ino_t nodeid,
+ const char *name, bool wr)
+{
+ if (f->conf.debug) {
+ struct node *wnode = NULL;
+
+ if (wr)
+ wnode = lookup_node(f, nodeid, name);
+
+ if (wnode) {
+ fprintf(stderr, "%s %llu (w)\n",
+ msg, (unsigned long long) wnode->nodeid);
+ } else {
+ fprintf(stderr, "%s %llu\n",
+ msg, (unsigned long long) nodeid);
+ }
+ }
+}
+
+static void queue_path(struct fuse *f, struct lock_queue_element *qe)
+{
+ struct lock_queue_element **qp;
+
+ qe->done = false;
+ qe->first_locked = false;
+ qe->second_locked = false;
+ pthread_cond_init(&qe->cond, NULL);
+ qe->next = NULL;
+ for (qp = &f->lockq; *qp != NULL; qp = &(*qp)->next);
+ *qp = qe;
+}
+
+static void dequeue_path(struct fuse *f, struct lock_queue_element *qe)
+{
+ struct lock_queue_element **qp;
+
+ pthread_cond_destroy(&qe->cond);
+ for (qp = &f->lockq; *qp != qe; qp = &(*qp)->next);
+ *qp = qe->next;
+}
+
+static int wait_path(struct fuse *f, struct lock_queue_element *qe)
+{
+ queue_path(f, qe);
+
+ do {
+ pthread_cond_wait(&qe->cond, &f->lock);
+ } while (!qe->done);
+
+ dequeue_path(f, qe);
+
+ return qe->err;
+}
+
+static int get_path_common(struct fuse *f, fuse_ino_t nodeid, const char *name,
+ char **path, struct node **wnode)
+{
+ int err;
+
+ pthread_mutex_lock(&f->lock);
+ err = try_get_path(f, nodeid, name, path, wnode, true);
+ if (err == -EAGAIN) {
+ struct lock_queue_element qe = {
+ .nodeid1 = nodeid,
+ .name1 = name,
+ .path1 = path,
+ .wnode1 = wnode,
+ };
+ debug_path(f, "QUEUE PATH", nodeid, name, !!wnode);
+ err = wait_path(f, &qe);
+ debug_path(f, "DEQUEUE PATH", nodeid, name, !!wnode);
+ }
+ pthread_mutex_unlock(&f->lock);
+
+ return err;
+}
+
+static int get_path(struct fuse *f, fuse_ino_t nodeid, char **path)
+{
+ return get_path_common(f, nodeid, NULL, path, NULL);
+}
+
+static int get_path_nullok(struct fuse *f, fuse_ino_t nodeid, char **path)
+{
+ int err = 0;
+
+ if (f->conf.nullpath_ok) {
+ *path = NULL;
+ } else {
+ err = get_path_common(f, nodeid, NULL, path, NULL);
+ if (err == -ENOENT)
+ err = 0;
+ }
+
+ return err;
+}
+
+static int get_path_name(struct fuse *f, fuse_ino_t nodeid, const char *name,
+ char **path)
+{
+ return get_path_common(f, nodeid, name, path, NULL);
+}
+
+static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name,
+ char **path, struct node **wnode)
+{
+ return get_path_common(f, nodeid, name, path, wnode);
+}
+
+#if defined(__FreeBSD__)
+#define CHECK_DIR_LOOP
+#endif
+
+#if defined(CHECK_DIR_LOOP)
+static int check_dir_loop(struct fuse *f,
+ fuse_ino_t nodeid1, const char *name1,
+ fuse_ino_t nodeid2, const char *name2)
+{
+ struct node *node, *node1, *node2;
+ fuse_ino_t id1, id2;
+
+ node1 = lookup_node(f, nodeid1, name1);
+ id1 = node1 ? node1->nodeid : nodeid1;
+
+ node2 = lookup_node(f, nodeid2, name2);
+ id2 = node2 ? node2->nodeid : nodeid2;
+
+ for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID;
+ node = node->parent) {
+ if (node->name == NULL || node->parent == NULL)
+ break;
+
+ if (node->nodeid != id2 && node->nodeid == id1)
+ return -EINVAL;
+ }
+
+ if (node2)
+ {
+ for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID;
+ node = node->parent) {
+ if (node->name == NULL || node->parent == NULL)
+ break;
+
+ if (node->nodeid != id1 && node->nodeid == id2)
+ return -ENOTEMPTY;
+ }
+ }
+
+ return 0;
+}
+#endif
+
+static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+ fuse_ino_t nodeid2, const char *name2,
+ char **path1, char **path2,
+ struct node **wnode1, struct node **wnode2)
+{
+ int err;
+
+ /* FIXME: locking two paths needs deadlock checking */
+ err = try_get_path(f, nodeid1, name1, path1, wnode1, true);
+ if (!err) {
+ err = try_get_path(f, nodeid2, name2, path2, wnode2, true);
+ if (err) {
+ struct node *wn1 = wnode1 ? *wnode1 : NULL;
+
+ unlock_path(f, nodeid1, wn1, NULL);
+ free(*path1);
+ }
+ }
+ return err;
+}
+
+static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1,
+ fuse_ino_t nodeid2, const char *name2,
+ char **path1, char **path2,
+ struct node **wnode1, struct node **wnode2)
+{
+ int err;
+
+ pthread_mutex_lock(&f->lock);
+
+#if defined(CHECK_DIR_LOOP)
+ if (name1)
+ {
+ // called during rename; perform dir loop check
+ err = check_dir_loop(f, nodeid1, name1, nodeid2, name2);
+ if (err)
+ goto out_unlock;
+ }
+#endif
+
+ err = try_get_path2(f, nodeid1, name1, nodeid2, name2,
+ path1, path2, wnode1, wnode2);
+ if (err == -EAGAIN) {
+ struct lock_queue_element qe = {
+ .nodeid1 = nodeid1,
+ .name1 = name1,
+ .path1 = path1,
+ .wnode1 = wnode1,
+ .nodeid2 = nodeid2,
+ .name2 = name2,
+ .path2 = path2,
+ .wnode2 = wnode2,
+ };
+
+ debug_path(f, "QUEUE PATH1", nodeid1, name1, !!wnode1);
+ debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+ err = wait_path(f, &qe);
+ debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1);
+ debug_path(f, " PATH2", nodeid2, name2, !!wnode2);
+ }
+
+#if defined(CHECK_DIR_LOOP)
+out_unlock:
+#endif
+ pthread_mutex_unlock(&f->lock);
+
+ return err;
+}
+
+static void free_path_wrlock(struct fuse *f, fuse_ino_t nodeid,
+ struct node *wnode, char *path)
+{
+ pthread_mutex_lock(&f->lock);
+ unlock_path(f, nodeid, wnode, NULL);
+ if (f->lockq)
+ wake_up_queued(f);
+ pthread_mutex_unlock(&f->lock);
+ free(path);
+}
+
+static void free_path(struct fuse *f, fuse_ino_t nodeid, char *path)
+{
+ if (path)
+ free_path_wrlock(f, nodeid, NULL, path);
+}
+
+static void free_path2(struct fuse *f, fuse_ino_t nodeid1, fuse_ino_t nodeid2,
+ struct node *wnode1, struct node *wnode2,
+ char *path1, char *path2)
+{
+ pthread_mutex_lock(&f->lock);
+ unlock_path(f, nodeid1, wnode1, NULL);
+ unlock_path(f, nodeid2, wnode2, NULL);
+ wake_up_queued(f);
+ pthread_mutex_unlock(&f->lock);
+ free(path1);
+ free(path2);
+}
+
+static void forget_node(struct fuse *f, fuse_ino_t nodeid, uint64_t nlookup)
+{
+ struct node *node;
+ if (nodeid == FUSE_ROOT_ID)
+ return;
+ pthread_mutex_lock(&f->lock);
+ node = get_node(f, nodeid);
+
+ /*
+ * Node may still be locked due to interrupt idiocy in open,
+ * create and opendir
+ */
+ while (node->nlookup == nlookup && node->treelock) {
+ struct lock_queue_element qe = {
+ .nodeid1 = nodeid,
+ };
+
+ debug_path(f, "QUEUE PATH (forget)", nodeid, NULL, false);
+ queue_path(f, &qe);
+
+ do {
+ pthread_cond_wait(&qe.cond, &f->lock);
+ } while (node->nlookup == nlookup && node->treelock);
+
+ dequeue_path(f, &qe);
+ debug_path(f, "DEQUEUE_PATH (forget)", nodeid, NULL, false);
+ }
+
+ assert(node->nlookup >= nlookup);
+ node->nlookup -= nlookup;
+ if (!node->nlookup) {
+ unref_node(f, node);
+ } else if (lru_enabled(f) && node->nlookup == 1) {
+ set_forget_time(f, node);
+ }
+ pthread_mutex_unlock(&f->lock);
+}
+
+static void unlink_node(struct fuse *f, struct node *node)
+{
+ if (f->conf.remember) {
+ assert(node->nlookup > 1);
+ node->nlookup--;
+ }
+ unhash_name(f, node);
+}
+
+static void remove_node(struct fuse *f, fuse_ino_t dir, const char *name)
+{
+ struct node *node;
+
+ pthread_mutex_lock(&f->lock);
+ node = lookup_node(f, dir, name);
+ if (node != NULL)
+ unlink_node(f, node);
+ pthread_mutex_unlock(&f->lock);
+}
+
+static int rename_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+ fuse_ino_t newdir, const char *newname, int hide)
+{
+ struct node *node;
+ struct node *newnode;
+ int err = 0;
+
+ pthread_mutex_lock(&f->lock);
+ node = lookup_node(f, olddir, oldname);
+ newnode = lookup_node(f, newdir, newname);
+ if (node == NULL)
+ goto out;
+
+ if (newnode != NULL) {
+ if (hide) {
+ fprintf(stderr, "fuse: hidden file got created during hiding\n");
+ err = -EBUSY;
+ goto out;
+ }
+ unlink_node(f, newnode);
+ }
+
+ unhash_name(f, node);
+ if (hash_name(f, node, newdir, newname) == -1) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ if (hide)
+ node->is_hidden = 1;
+
+out:
+ pthread_mutex_unlock(&f->lock);
+ return err;
+}
+
+static int exchange_node(struct fuse *f, fuse_ino_t olddir, const char *oldname,
+ fuse_ino_t newdir, const char *newname)
+{
+ struct node *oldnode;
+ struct node *newnode;
+ int err;
+
+ pthread_mutex_lock(&f->lock);
+ oldnode = lookup_node(f, olddir, oldname);
+ newnode = lookup_node(f, newdir, newname);
+
+ if (oldnode)
+ unhash_name(f, oldnode);
+ if (newnode)
+ unhash_name(f, newnode);
+
+ err = -ENOMEM;
+ if (oldnode) {
+ if (hash_name(f, oldnode, newdir, newname) == -1)
+ goto out;
+ }
+ if (newnode) {
+ if (hash_name(f, newnode, olddir, oldname) == -1)
+ goto out;
+ }
+ err = 0;
+out:
+ pthread_mutex_unlock(&f->lock);
+ return err;
+}
+
+static void set_stat(struct fuse *f, fuse_ino_t nodeid, struct stat *stbuf)
+{
+ if (!f->conf.use_ino)
+ stbuf->st_ino = nodeid;
+ if (f->conf.set_mode)
+ stbuf->st_mode = (stbuf->st_mode & S_IFMT) |
+ (0777 & ~f->conf.umask);
+ if (f->conf.set_uid)
+ stbuf->st_uid = f->conf.uid;
+ if (f->conf.set_gid)
+ stbuf->st_gid = f->conf.gid;
+}
+
+static struct fuse *req_fuse(fuse_req_t req)
+{
+ return (struct fuse *) fuse_req_userdata(req);
+}
+
+static void fuse_intr_sighandler(int sig)
+{
+ (void) sig;
+ /* Nothing to do */
+}
+
+struct fuse_intr_data {
+ pthread_t id;
+ pthread_cond_t cond;
+ int finished;
+};
+
+static void fuse_interrupt(fuse_req_t req, void *d_)
+{
+ struct fuse_intr_data *d = d_;
+ struct fuse *f = req_fuse(req);
+
+ if (d->id == pthread_self())
+ return;
+
+ pthread_mutex_lock(&f->lock);
+ while (!d->finished) {
+ struct timeval now;
+ struct timespec timeout;
+
+ pthread_kill(d->id, f->conf.intr_signal);
+ gettimeofday(&now, NULL);
+ timeout.tv_sec = now.tv_sec + 1;
+ timeout.tv_nsec = now.tv_usec * 1000;
+ pthread_cond_timedwait(&d->cond, &f->lock, &timeout);
+ }
+ pthread_mutex_unlock(&f->lock);
+}
+
+static void fuse_do_finish_interrupt(struct fuse *f, fuse_req_t req,
+ struct fuse_intr_data *d)
+{
+ pthread_mutex_lock(&f->lock);
+ d->finished = 1;
+ pthread_cond_broadcast(&d->cond);
+ pthread_mutex_unlock(&f->lock);
+ fuse_req_interrupt_func(req, NULL, NULL);
+ pthread_cond_destroy(&d->cond);
+}
+
+static void fuse_do_prepare_interrupt(fuse_req_t req, struct fuse_intr_data *d)
+{
+ d->id = pthread_self();
+ pthread_cond_init(&d->cond, NULL);
+ d->finished = 0;
+ fuse_req_interrupt_func(req, fuse_interrupt, d);
+}
+
+static inline void fuse_finish_interrupt(struct fuse *f, fuse_req_t req,
+ struct fuse_intr_data *d)
+{
+ if (f->conf.intr)
+ fuse_do_finish_interrupt(f, req, d);
+}
+
+static inline void fuse_prepare_interrupt(struct fuse *f, fuse_req_t req,
+ struct fuse_intr_data *d)
+{
+ if (f->conf.intr)
+ fuse_do_prepare_interrupt(req, d);
+}
+
+static const char* file_info_string(struct fuse_file_info *fi,
+ char* buf, size_t len)
+{
+ if(fi == NULL)
+ return "NULL";
+ snprintf(buf, len, "%llu", (unsigned long long) fi->fh);
+ return buf;
+}
+
+int fuse_fs_getattr(struct fuse_fs *fs, const char *path, struct stat *buf,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.getattr) {
+ if (fs->debug) {
+ char buf[10];
+ fprintf(stderr, "getattr[%s] %s\n",
+ file_info_string(fi, buf, sizeof(buf)),
+ path);
+ }
+ return fs->op.getattr(path, buf, fi);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_rename(struct fuse_fs *fs, const char *oldpath,
+ const char *newpath, unsigned int flags)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.rename) {
+ if (fs->debug)
+ fprintf(stderr, "rename %s %s 0x%x\n", oldpath, newpath,
+ flags);
+
+ return fs->op.rename(oldpath, newpath, flags);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_unlink(struct fuse_fs *fs, const char *path)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.unlink) {
+ if (fs->debug)
+ fprintf(stderr, "unlink %s\n", path);
+
+ return fs->op.unlink(path);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_rmdir(struct fuse_fs *fs, const char *path)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.rmdir) {
+ if (fs->debug)
+ fprintf(stderr, "rmdir %s\n", path);
+
+ return fs->op.rmdir(path);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_symlink(struct fuse_fs *fs, const char *linkname, const char *path)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.symlink) {
+ if (fs->debug)
+ fprintf(stderr, "symlink %s %s\n", linkname, path);
+
+ return fs->op.symlink(linkname, path);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_link(struct fuse_fs *fs, const char *oldpath, const char *newpath)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.link) {
+ if (fs->debug)
+ fprintf(stderr, "link %s %s\n", oldpath, newpath);
+
+ return fs->op.link(oldpath, newpath);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_release(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.release) {
+ if (fs->debug)
+ fprintf(stderr, "release%s[%llu] flags: 0x%x\n",
+ fi->flush ? "+flush" : "",
+ (unsigned long long) fi->fh, fi->flags);
+
+ return fs->op.release(path, fi);
+ } else {
+ return 0;
+ }
+}
+
+int fuse_fs_opendir(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.opendir) {
+ int err;
+
+ if (fs->debug)
+ fprintf(stderr, "opendir flags: 0x%x %s\n", fi->flags,
+ path);
+
+ err = fs->op.opendir(path, fi);
+
+ if (fs->debug && !err)
+ fprintf(stderr, " opendir[%llu] flags: 0x%x %s\n",
+ (unsigned long long) fi->fh, fi->flags, path);
+
+ return err;
+ } else {
+ return 0;
+ }
+}
+
+int fuse_fs_open(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.open) {
+ int err;
+
+ if (fs->debug)
+ fprintf(stderr, "open flags: 0x%x %s\n", fi->flags,
+ path);
+
+ err = fs->op.open(path, fi);
+
+ if (fs->debug && !err)
+ fprintf(stderr, " open[%llu] flags: 0x%x %s\n",
+ (unsigned long long) fi->fh, fi->flags, path);
+
+ return err;
+ } else {
+ return 0;
+ }
+}
+
+static void fuse_free_buf(struct fuse_bufvec *buf)
+{
+ if (buf != NULL) {
+ size_t i;
+
+ for (i = 0; i < buf->count; i++)
+ if (!(buf->buf[i].flags & FUSE_BUF_IS_FD))
+ free(buf->buf[i].mem);
+ free(buf);
+ }
+}
+
+int fuse_fs_read_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec **bufp, size_t size, off_t off,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.read || fs->op.read_buf) {
+ int res;
+
+ if (fs->debug)
+ fprintf(stderr,
+ "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+ (unsigned long long) fi->fh,
+ size, (unsigned long long) off, fi->flags);
+
+ if (fs->op.read_buf) {
+ res = fs->op.read_buf(path, bufp, size, off, fi);
+ } else {
+ struct fuse_bufvec *buf;
+ void *mem;
+
+ buf = malloc(sizeof(struct fuse_bufvec));
+ if (buf == NULL)
+ return -ENOMEM;
+
+ mem = malloc(size);
+ if (mem == NULL) {
+ free(buf);
+ return -ENOMEM;
+ }
+ *buf = FUSE_BUFVEC_INIT(size);
+ buf->buf[0].mem = mem;
+ *bufp = buf;
+
+ res = fs->op.read(path, mem, size, off, fi);
+ if (res >= 0)
+ buf->buf[0].size = res;
+ }
+
+ if (fs->debug && res >= 0)
+ fprintf(stderr, " read[%llu] %zu bytes from %llu\n",
+ (unsigned long long) fi->fh,
+ fuse_buf_size(*bufp),
+ (unsigned long long) off);
+ if (res >= 0 && fuse_buf_size(*bufp) > size)
+ fprintf(stderr, "fuse: read too many bytes\n");
+
+ if (res < 0)
+ return res;
+
+ return 0;
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_read(struct fuse_fs *fs, const char *path, char *mem, size_t size,
+ off_t off, struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.read || fs->op.read_buf) {
+ int res;
+
+ if (fs->debug)
+ fprintf(stderr,
+ "read[%llu] %zu bytes from %llu flags: 0x%x\n",
+ (unsigned long long) fi->fh,
+ size, (unsigned long long) off, fi->flags);
+
+ if (fs->op.read_buf) {
+ struct fuse_bufvec *buf = NULL;
+
+ res = fs->op.read_buf(path, &buf, size, off, fi);
+ if (res == 0) {
+ struct fuse_bufvec dst = FUSE_BUFVEC_INIT(size);
+
+ dst.buf[0].mem = mem;
+ res = fuse_buf_copy(&dst, buf, 0);
+ }
+ fuse_free_buf(buf);
+ } else {
+ res = fs->op.read(path, mem, size, off, fi);
+ }
+
+ if (fs->debug && res >= 0)
+ fprintf(stderr, " read[%llu] %u bytes from %llu\n",
+ (unsigned long long) fi->fh,
+ res,
+ (unsigned long long) off);
+ if (res >= 0 && res > (int) size)
+ fprintf(stderr, "fuse: read too many bytes\n");
+
+ return res;
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_write_buf(struct fuse_fs *fs, const char *path,
+ struct fuse_bufvec *buf, off_t off,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.write_buf || fs->op.write) {
+ int res;
+ size_t size = fuse_buf_size(buf);
+
+ assert(buf->idx == 0 && buf->off == 0);
+ if (fs->debug)
+ fprintf(stderr,
+ "write%s[%llu] %zu bytes to %llu flags: 0x%x\n",
+ fi->writepage ? "page" : "",
+ (unsigned long long) fi->fh,
+ size,
+ (unsigned long long) off,
+ fi->flags);
+
+ if (fs->op.write_buf) {
+ res = fs->op.write_buf(path, buf, off, fi);
+ } else {
+ void *mem = NULL;
+ struct fuse_buf *flatbuf;
+ struct fuse_bufvec tmp = FUSE_BUFVEC_INIT(size);
+
+ if (buf->count == 1 &&
+ !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+ flatbuf = &buf->buf[0];
+ } else {
+ res = -ENOMEM;
+ mem = malloc(size);
+ if (mem == NULL)
+ goto out;
+
+ tmp.buf[0].mem = mem;
+ res = fuse_buf_copy(&tmp, buf, 0);
+ if (res <= 0)
+ goto out_free;
+
+ tmp.buf[0].size = res;
+ flatbuf = &tmp.buf[0];
+ }
+
+ res = fs->op.write(path, flatbuf->mem, flatbuf->size,
+ off, fi);
+out_free:
+ free(mem);
+ }
+out:
+ if (fs->debug && res >= 0)
+ fprintf(stderr, " write%s[%llu] %u bytes to %llu\n",
+ fi->writepage ? "page" : "",
+ (unsigned long long) fi->fh, res,
+ (unsigned long long) off);
+ if (res > (int) size)
+ fprintf(stderr, "fuse: wrote too many bytes\n");
+
+ return res;
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_write(struct fuse_fs *fs, const char *path, const char *mem,
+ size_t size, off_t off, struct fuse_file_info *fi)
+{
+ struct fuse_bufvec bufv = FUSE_BUFVEC_INIT(size);
+
+ bufv.buf[0].mem = (void *) mem;
+
+ return fuse_fs_write_buf(fs, path, &bufv, off, fi);
+}
+
+int fuse_fs_fsync(struct fuse_fs *fs, const char *path, int datasync,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.fsync) {
+ if (fs->debug)
+ fprintf(stderr, "fsync[%llu] datasync: %i\n",
+ (unsigned long long) fi->fh, datasync);
+
+ return fs->op.fsync(path, datasync, fi);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_fsyncdir(struct fuse_fs *fs, const char *path, int datasync,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.fsyncdir) {
+ if (fs->debug)
+ fprintf(stderr, "fsyncdir[%llu] datasync: %i\n",
+ (unsigned long long) fi->fh, datasync);
+
+ return fs->op.fsyncdir(path, datasync, fi);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_flush(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.flush) {
+ if (fs->debug)
+ fprintf(stderr, "flush[%llu]\n",
+ (unsigned long long) fi->fh);
+
+ return fs->op.flush(path, fi);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_statfs(struct fuse_fs *fs, const char *path, struct statvfs *buf)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.statfs) {
+ if (fs->debug)
+ fprintf(stderr, "statfs %s\n", path);
+
+ return fs->op.statfs(path, buf);
+ } else {
+ buf->f_namemax = 255;
+ buf->f_bsize = 512;
+ return 0;
+ }
+}
+
+int fuse_fs_releasedir(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.releasedir) {
+ if (fs->debug)
+ fprintf(stderr, "releasedir[%llu] flags: 0x%x\n",
+ (unsigned long long) fi->fh, fi->flags);
+
+ return fs->op.releasedir(path, fi);
+ } else {
+ return 0;
+ }
+}
+
+int fuse_fs_readdir(struct fuse_fs *fs, const char *path, void *buf,
+ fuse_fill_dir_t filler, off_t off,
+ struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.readdir) {
+ if (fs->debug) {
+ fprintf(stderr, "readdir%s[%llu] from %llu\n",
+ (flags & FUSE_READDIR_PLUS) ? "plus" : "",
+ (unsigned long long) fi->fh,
+ (unsigned long long) off);
+ }
+
+ return fs->op.readdir(path, buf, filler, off, fi, flags);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_create(struct fuse_fs *fs, const char *path, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.create) {
+ int err;
+
+ if (fs->debug)
+ fprintf(stderr,
+ "create flags: 0x%x %s 0%o umask=0%03o\n",
+ fi->flags, path, mode,
+ fuse_get_context()->umask);
+
+ err = fs->op.create(path, mode, fi);
+
+ if (fs->debug && !err)
+ fprintf(stderr, " create[%llu] flags: 0x%x %s\n",
+ (unsigned long long) fi->fh, fi->flags, path);
+
+ return err;
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_lock(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, int cmd, struct flock *lock)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.lock) {
+ if (fs->debug)
+ fprintf(stderr, "lock[%llu] %s %s start: %llu len: %llu pid: %llu\n",
+ (unsigned long long) fi->fh,
+ (cmd == F_GETLK ? "F_GETLK" :
+ (cmd == F_SETLK ? "F_SETLK" :
+ (cmd == F_SETLKW ? "F_SETLKW" : "???"))),
+ (lock->l_type == F_RDLCK ? "F_RDLCK" :
+ (lock->l_type == F_WRLCK ? "F_WRLCK" :
+ (lock->l_type == F_UNLCK ? "F_UNLCK" :
+ "???"))),
+ (unsigned long long) lock->l_start,
+ (unsigned long long) lock->l_len,
+ (unsigned long long) lock->l_pid);
+
+ return fs->op.lock(path, fi, cmd, lock);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_flock(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, int op)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.flock) {
+ if (fs->debug) {
+ int xop = op & ~LOCK_NB;
+
+ fprintf(stderr, "lock[%llu] %s%s\n",
+ (unsigned long long) fi->fh,
+ xop == LOCK_SH ? "LOCK_SH" :
+ (xop == LOCK_EX ? "LOCK_EX" :
+ (xop == LOCK_UN ? "LOCK_UN" : "???")),
+ (op & LOCK_NB) ? "|LOCK_NB" : "");
+ }
+ return fs->op.flock(path, fi, op);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_chown(struct fuse_fs *fs, const char *path, uid_t uid,
+ gid_t gid, struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.chown) {
+ if (fs->debug) {
+ char buf[10];
+ fprintf(stderr, "chown[%s] %s %lu %lu\n",
+ file_info_string(fi, buf, sizeof(buf)),
+ path, (unsigned long) uid, (unsigned long) gid);
+ }
+ return fs->op.chown(path, uid, gid, fi);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_truncate(struct fuse_fs *fs, const char *path, off_t size,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.truncate) {
+ if (fs->debug) {
+ char buf[10];
+ fprintf(stderr, "truncate[%s] %llu\n",
+ file_info_string(fi, buf, sizeof(buf)),
+ (unsigned long long) size);
+ }
+ return fs->op.truncate(path, size, fi);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_utimens(struct fuse_fs *fs, const char *path,
+ const struct timespec tv[2], struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.utimens) {
+ if (fs->debug) {
+ char buf[10];
+ fprintf(stderr, "utimens[%s] %s %li.%09lu %li.%09lu\n",
+ file_info_string(fi, buf, sizeof(buf)),
+ path, tv[0].tv_sec, tv[0].tv_nsec,
+ tv[1].tv_sec, tv[1].tv_nsec);
+ }
+ return fs->op.utimens(path, tv, fi);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_access(struct fuse_fs *fs, const char *path, int mask)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.access) {
+ if (fs->debug)
+ fprintf(stderr, "access %s 0%o\n", path, mask);
+
+ return fs->op.access(path, mask);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_readlink(struct fuse_fs *fs, const char *path, char *buf,
+ size_t len)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.readlink) {
+ if (fs->debug)
+ fprintf(stderr, "readlink %s %lu\n", path,
+ (unsigned long) len);
+
+ return fs->op.readlink(path, buf, len);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_mknod(struct fuse_fs *fs, const char *path, mode_t mode,
+ dev_t rdev)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.mknod) {
+ if (fs->debug)
+ fprintf(stderr, "mknod %s 0%o 0x%llx umask=0%03o\n",
+ path, mode, (unsigned long long) rdev,
+ fuse_get_context()->umask);
+
+ return fs->op.mknod(path, mode, rdev);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_mkdir(struct fuse_fs *fs, const char *path, mode_t mode)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.mkdir) {
+ if (fs->debug)
+ fprintf(stderr, "mkdir %s 0%o umask=0%03o\n",
+ path, mode, fuse_get_context()->umask);
+
+ return fs->op.mkdir(path, mode);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_setxattr(struct fuse_fs *fs, const char *path, const char *name,
+ const char *value, size_t size, int flags)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.setxattr) {
+ if (fs->debug)
+ fprintf(stderr, "setxattr %s %s %lu 0x%x\n",
+ path, name, (unsigned long) size, flags);
+
+ return fs->op.setxattr(path, name, value, size, flags);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_getxattr(struct fuse_fs *fs, const char *path, const char *name,
+ char *value, size_t size)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.getxattr) {
+ if (fs->debug)
+ fprintf(stderr, "getxattr %s %s %lu\n",
+ path, name, (unsigned long) size);
+
+ return fs->op.getxattr(path, name, value, size);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_listxattr(struct fuse_fs *fs, const char *path, char *list,
+ size_t size)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.listxattr) {
+ if (fs->debug)
+ fprintf(stderr, "listxattr %s %lu\n",
+ path, (unsigned long) size);
+
+ return fs->op.listxattr(path, list, size);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_bmap(struct fuse_fs *fs, const char *path, size_t blocksize,
+ uint64_t *idx)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.bmap) {
+ if (fs->debug)
+ fprintf(stderr, "bmap %s blocksize: %lu index: %llu\n",
+ path, (unsigned long) blocksize,
+ (unsigned long long) *idx);
+
+ return fs->op.bmap(path, blocksize, idx);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_removexattr(struct fuse_fs *fs, const char *path, const char *name)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.removexattr) {
+ if (fs->debug)
+ fprintf(stderr, "removexattr %s %s\n", path, name);
+
+ return fs->op.removexattr(path, name);
+ } else {
+ return -ENOSYS;
+ }
+}
+
+int fuse_fs_ioctl(struct fuse_fs *fs, const char *path, unsigned int cmd,
+ void *arg, struct fuse_file_info *fi, unsigned int flags,
+ void *data)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.ioctl) {
+ if (fs->debug)
+ fprintf(stderr, "ioctl[%llu] 0x%x flags: 0x%x\n",
+ (unsigned long long) fi->fh, cmd, flags);
+
+ return fs->op.ioctl(path, cmd, arg, fi, flags, data);
+ } else
+ return -ENOSYS;
+}
+
+int fuse_fs_poll(struct fuse_fs *fs, const char *path,
+ struct fuse_file_info *fi, struct fuse_pollhandle *ph,
+ unsigned *reventsp)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.poll) {
+ int res;
+
+ if (fs->debug)
+ fprintf(stderr, "poll[%llu] ph: %p, events 0x%x\n",
+ (unsigned long long) fi->fh, ph,
+ fi->poll_events);
+
+ res = fs->op.poll(path, fi, ph, reventsp);
+
+ if (fs->debug && !res)
+ fprintf(stderr, " poll[%llu] revents: 0x%x\n",
+ (unsigned long long) fi->fh, *reventsp);
+
+ return res;
+ } else
+ return -ENOSYS;
+}
+
+int fuse_fs_fallocate(struct fuse_fs *fs, const char *path, int mode,
+ off_t offset, off_t length, struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.fallocate) {
+ if (fs->debug)
+ fprintf(stderr, "fallocate %s mode %x, offset: %llu, length: %llu\n",
+ path,
+ mode,
+ (unsigned long long) offset,
+ (unsigned long long) length);
+
+ return fs->op.fallocate(path, mode, offset, length, fi);
+ } else
+ return -ENOSYS;
+}
+
+ssize_t fuse_fs_copy_file_range(struct fuse_fs *fs, const char *path_in,
+ struct fuse_file_info *fi_in, off_t off_in,
+ const char *path_out,
+ struct fuse_file_info *fi_out, off_t off_out,
+ size_t len, int flags)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.copy_file_range) {
+ if (fs->debug)
+ fprintf(stderr, "copy_file_range from %s:%llu to "
+ "%s:%llu, length: %llu\n",
+ path_in,
+ (unsigned long long) off_in,
+ path_out,
+ (unsigned long long) off_out,
+ (unsigned long long) len);
+
+ return fs->op.copy_file_range(path_in, fi_in, off_in, path_out,
+ fi_out, off_out, len, flags);
+ } else
+ return -ENOSYS;
+}
+
+static int is_open(struct fuse *f, fuse_ino_t dir, const char *name)
+{
+ struct node *node;
+ int isopen = 0;
+ pthread_mutex_lock(&f->lock);
+ node = lookup_node(f, dir, name);
+ if (node && node->open_count > 0)
+ isopen = 1;
+ pthread_mutex_unlock(&f->lock);
+ return isopen;
+}
+
+static char *hidden_name(struct fuse *f, fuse_ino_t dir, const char *oldname,
+ char *newname, size_t bufsize)
+{
+ struct stat buf;
+ struct node *node;
+ struct node *newnode;
+ char *newpath;
+ int res;
+ int failctr = 10;
+
+ do {
+ pthread_mutex_lock(&f->lock);
+ node = lookup_node(f, dir, oldname);
+ if (node == NULL) {
+ pthread_mutex_unlock(&f->lock);
+ return NULL;
+ }
+ do {
+ f->hidectr ++;
+ snprintf(newname, bufsize, ".fuse_hidden%08x%08x",
+ (unsigned int) node->nodeid, f->hidectr);
+ newnode = lookup_node(f, dir, newname);
+ } while(newnode);
+
+ res = try_get_path(f, dir, newname, &newpath, NULL, false);
+ pthread_mutex_unlock(&f->lock);
+ if (res)
+ break;
+
+ memset(&buf, 0, sizeof(buf));
+ res = fuse_fs_getattr(f->fs, newpath, &buf, NULL);
+ if (res == -ENOENT)
+ break;
+ free(newpath);
+ newpath = NULL;
+ } while(res == 0 && --failctr);
+
+ return newpath;
+}
+
+static int hide_node(struct fuse *f, const char *oldpath,
+ fuse_ino_t dir, const char *oldname)
+{
+ char newname[64];
+ char *newpath;
+ int err = -EBUSY;
+
+ newpath = hidden_name(f, dir, oldname, newname, sizeof(newname));
+ if (newpath) {
+ err = fuse_fs_rename(f->fs, oldpath, newpath, 0);
+ if (!err)
+ err = rename_node(f, dir, oldname, dir, newname, 1);
+ free(newpath);
+ }
+ return err;
+}
+
+static int mtime_eq(const struct stat *stbuf, const struct timespec *ts)
+{
+ return stbuf->st_mtime == ts->tv_sec &&
+ ST_MTIM_NSEC(stbuf) == ts->tv_nsec;
+}
+
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC CLOCK_REALTIME
+#endif
+
+static void curr_time(struct timespec *now)
+{
+ static clockid_t clockid = CLOCK_MONOTONIC;
+ int res = clock_gettime(clockid, now);
+ if (res == -1 && errno == EINVAL) {
+ clockid = CLOCK_REALTIME;
+ res = clock_gettime(clockid, now);
+ }
+ if (res == -1) {
+ perror("fuse: clock_gettime");
+ abort();
+ }
+}
+
+static void update_stat(struct node *node, const struct stat *stbuf)
+{
+ if (node->cache_valid && (!mtime_eq(stbuf, &node->mtime) ||
+ stbuf->st_size != node->size))
+ node->cache_valid = 0;
+ node->mtime.tv_sec = stbuf->st_mtime;
+ node->mtime.tv_nsec = ST_MTIM_NSEC(stbuf);
+ node->size = stbuf->st_size;
+ curr_time(&node->stat_updated);
+}
+
+static int do_lookup(struct fuse *f, fuse_ino_t nodeid, const char *name,
+ struct fuse_entry_param *e)
+{
+ struct node *node;
+
+ node = find_node(f, nodeid, name);
+ if (node == NULL)
+ return -ENOMEM;
+
+ e->ino = node->nodeid;
+ e->generation = node->generation;
+ e->entry_timeout = f->conf.entry_timeout;
+ e->attr_timeout = f->conf.attr_timeout;
+ if (f->conf.auto_cache) {
+ pthread_mutex_lock(&f->lock);
+ update_stat(node, &e->attr);
+ pthread_mutex_unlock(&f->lock);
+ }
+ set_stat(f, e->ino, &e->attr);
+ return 0;
+}
+
+static int lookup_path(struct fuse *f, fuse_ino_t nodeid,
+ const char *name, const char *path,
+ struct fuse_entry_param *e, struct fuse_file_info *fi)
+{
+ int res;
+
+ memset(e, 0, sizeof(struct fuse_entry_param));
+ res = fuse_fs_getattr(f->fs, path, &e->attr, fi);
+ if (res == 0) {
+ res = do_lookup(f, nodeid, name, e);
+ if (res == 0 && f->conf.debug) {
+ fprintf(stderr, " NODEID: %llu\n",
+ (unsigned long long) e->ino);
+ }
+ }
+ return res;
+}
+
+static struct fuse_context_i *fuse_get_context_internal(void)
+{
+ return (struct fuse_context_i *) pthread_getspecific(fuse_context_key);
+}
+
+static struct fuse_context_i *fuse_create_context(struct fuse *f)
+{
+ struct fuse_context_i *c = fuse_get_context_internal();
+ if (c == NULL) {
+ c = (struct fuse_context_i *)
+ calloc(1, sizeof(struct fuse_context_i));
+ if (c == NULL) {
+ /* This is hard to deal with properly, so just
+ abort. If memory is so low that the
+ context cannot be allocated, there's not
+ much hope for the filesystem anyway */
+ fprintf(stderr, "fuse: failed to allocate thread specific data\n");
+ abort();
+ }
+ pthread_setspecific(fuse_context_key, c);
+ } else {
+ memset(c, 0, sizeof(*c));
+ }
+ c->ctx.fuse = f;
+
+ return c;
+}
+
+static void fuse_freecontext(void *data)
+{
+ free(data);
+}
+
+static int fuse_create_context_key(void)
+{
+ int err = 0;
+ pthread_mutex_lock(&fuse_context_lock);
+ if (!fuse_context_ref) {
+ err = pthread_key_create(&fuse_context_key, fuse_freecontext);
+ if (err) {
+ fprintf(stderr, "fuse: failed to create thread specific key: %s\n",
+ strerror(err));
+ pthread_mutex_unlock(&fuse_context_lock);
+ return -1;
+ }
+ }
+ fuse_context_ref++;
+ pthread_mutex_unlock(&fuse_context_lock);
+ return 0;
+}
+
+static void fuse_delete_context_key(void)
+{
+ pthread_mutex_lock(&fuse_context_lock);
+ fuse_context_ref--;
+ if (!fuse_context_ref) {
+ free(pthread_getspecific(fuse_context_key));
+ pthread_key_delete(fuse_context_key);
+ }
+ pthread_mutex_unlock(&fuse_context_lock);
+}
+
+static struct fuse *req_fuse_prepare(fuse_req_t req)
+{
+ struct fuse_context_i *c = fuse_create_context(req_fuse(req));
+ const struct fuse_ctx *ctx = fuse_req_ctx(req);
+ c->req = req;
+ c->ctx.uid = ctx->uid;
+ c->ctx.gid = ctx->gid;
+ c->ctx.pid = ctx->pid;
+ c->ctx.umask = ctx->umask;
+ return c->ctx.fuse;
+}
+
+static inline void reply_err(fuse_req_t req, int err)
+{
+ /* fuse_reply_err() uses non-negated errno values */
+ fuse_reply_err(req, -err);
+}
+
+static void reply_entry(fuse_req_t req, const struct fuse_entry_param *e,
+ int err)
+{
+ if (!err) {
+ struct fuse *f = req_fuse(req);
+ if (fuse_reply_entry(req, e) == -ENOENT) {
+ /* Skip forget for negative result */
+ if (e->ino != 0)
+ forget_node(f, e->ino, 1);
+ }
+ } else
+ reply_err(req, err);
+}
+
+void fuse_fs_init(struct fuse_fs *fs, struct fuse_conn_info *conn,
+ struct fuse_config *cfg)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (!fs->op.write_buf)
+ conn->want &= ~FUSE_CAP_SPLICE_READ;
+ if (!fs->op.lock)
+ conn->want &= ~FUSE_CAP_POSIX_LOCKS;
+ if (!fs->op.flock)
+ conn->want &= ~FUSE_CAP_FLOCK_LOCKS;
+ if (fs->op.init)
+ fs->user_data = fs->op.init(conn, cfg);
+}
+
+static void fuse_lib_init(void *data, struct fuse_conn_info *conn)
+{
+ struct fuse *f = (struct fuse *) data;
+
+ fuse_create_context(f);
+ if(conn->capable & FUSE_CAP_EXPORT_SUPPORT)
+ conn->want |= FUSE_CAP_EXPORT_SUPPORT;
+ fuse_fs_init(f->fs, conn, &f->conf);
+}
+
+void fuse_fs_destroy(struct fuse_fs *fs)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.destroy)
+ fs->op.destroy(fs->user_data);
+ if (fs->m)
+ fuse_put_module(fs->m);
+ free(fs);
+}
+
+static void fuse_lib_destroy(void *data)
+{
+ struct fuse *f = (struct fuse *) data;
+
+ fuse_create_context(f);
+ fuse_fs_destroy(f->fs);
+ f->fs = NULL;
+}
+
+static void fuse_lib_lookup(fuse_req_t req, fuse_ino_t parent,
+ const char *name)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_entry_param e;
+ char *path;
+ int err;
+ struct node *dot = NULL;
+
+ if (name[0] == '.') {
+ int len = strlen(name);
+
+ if (len == 1 || (name[1] == '.' && len == 2)) {
+ pthread_mutex_lock(&f->lock);
+ if (len == 1) {
+ if (f->conf.debug)
+ fprintf(stderr, "LOOKUP-DOT\n");
+ dot = get_node_nocheck(f, parent);
+ if (dot == NULL) {
+ pthread_mutex_unlock(&f->lock);
+ reply_entry(req, &e, -ESTALE);
+ return;
+ }
+ dot->refctr++;
+ } else {
+ if (f->conf.debug)
+ fprintf(stderr, "LOOKUP-DOTDOT\n");
+ parent = get_node(f, parent)->parent->nodeid;
+ }
+ pthread_mutex_unlock(&f->lock);
+ name = NULL;
+ }
+ }
+
+ err = get_path_name(f, parent, name, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ if (f->conf.debug)
+ fprintf(stderr, "LOOKUP %s\n", path);
+ fuse_prepare_interrupt(f, req, &d);
+ err = lookup_path(f, parent, name, path, &e, NULL);
+ if (err == -ENOENT && f->conf.negative_timeout != 0.0) {
+ e.ino = 0;
+ e.entry_timeout = f->conf.negative_timeout;
+ err = 0;
+ }
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, parent, path);
+ }
+ if (dot) {
+ pthread_mutex_lock(&f->lock);
+ unref_node(f, dot);
+ pthread_mutex_unlock(&f->lock);
+ }
+ reply_entry(req, &e, err);
+}
+
+static void do_forget(struct fuse *f, fuse_ino_t ino, uint64_t nlookup)
+{
+ if (f->conf.debug)
+ fprintf(stderr, "FORGET %llu/%llu\n", (unsigned long long)ino,
+ (unsigned long long) nlookup);
+ forget_node(f, ino, nlookup);
+}
+
+static void fuse_lib_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+{
+ do_forget(req_fuse(req), ino, nlookup);
+ fuse_reply_none(req);
+}
+
+static void fuse_lib_forget_multi(fuse_req_t req, size_t count,
+ struct fuse_forget_data *forgets)
+{
+ struct fuse *f = req_fuse(req);
+ size_t i;
+
+ for (i = 0; i < count; i++)
+ do_forget(f, forgets[i].ino, forgets[i].nlookup);
+
+ fuse_reply_none(req);
+}
+
+
+static void fuse_lib_getattr(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct stat buf;
+ char *path;
+ int err;
+
+ memset(&buf, 0, sizeof(buf));
+
+ if (fi != NULL)
+ err = get_path_nullok(f, ino, &path);
+ else
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_getattr(f->fs, path, &buf, fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ if (!err) {
+ struct node *node;
+
+ pthread_mutex_lock(&f->lock);
+ node = get_node(f, ino);
+ if (node->is_hidden && buf.st_nlink > 0)
+ buf.st_nlink--;
+ if (f->conf.auto_cache)
+ update_stat(node, &buf);
+ pthread_mutex_unlock(&f->lock);
+ set_stat(f, ino, &buf);
+ fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+ } else
+ reply_err(req, err);
+}
+
+int fuse_fs_chmod(struct fuse_fs *fs, const char *path, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ fuse_get_context()->private_data = fs->user_data;
+ if (fs->op.chmod) {
+ if (fs->debug) {
+ char buf[10];
+ fprintf(stderr, "chmod[%s] %s %llo\n",
+ file_info_string(fi, buf, sizeof(buf)),
+ path, (unsigned long long) mode);
+ }
+ return fs->op.chmod(path, mode, fi);
+ }
+ else
+ return -ENOSYS;
+}
+
+static void fuse_lib_setattr(fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+ int valid, struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct stat buf;
+ char *path;
+ int err;
+
+ memset(&buf, 0, sizeof(buf));
+ if (fi != NULL)
+ err = get_path_nullok(f, ino, &path);
+ else
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = 0;
+ if (!err && (valid & FUSE_SET_ATTR_MODE))
+ err = fuse_fs_chmod(f->fs, path, attr->st_mode, fi);
+ if (!err && (valid & (FUSE_SET_ATTR_UID | FUSE_SET_ATTR_GID))) {
+ uid_t uid = (valid & FUSE_SET_ATTR_UID) ?
+ attr->st_uid : (uid_t) -1;
+ gid_t gid = (valid & FUSE_SET_ATTR_GID) ?
+ attr->st_gid : (gid_t) -1;
+ err = fuse_fs_chown(f->fs, path, uid, gid, fi);
+ }
+ if (!err && (valid & FUSE_SET_ATTR_SIZE)) {
+ err = fuse_fs_truncate(f->fs, path,
+ attr->st_size, fi);
+ }
+#ifdef HAVE_UTIMENSAT
+ if (!err &&
+ (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME))) {
+ struct timespec tv[2];
+
+ tv[0].tv_sec = 0;
+ tv[1].tv_sec = 0;
+ tv[0].tv_nsec = UTIME_OMIT;
+ tv[1].tv_nsec = UTIME_OMIT;
+
+ if (valid & FUSE_SET_ATTR_ATIME_NOW)
+ tv[0].tv_nsec = UTIME_NOW;
+ else if (valid & FUSE_SET_ATTR_ATIME)
+ tv[0] = attr->st_atim;
+
+ if (valid & FUSE_SET_ATTR_MTIME_NOW)
+ tv[1].tv_nsec = UTIME_NOW;
+ else if (valid & FUSE_SET_ATTR_MTIME)
+ tv[1] = attr->st_mtim;
+
+ err = fuse_fs_utimens(f->fs, path, tv, fi);
+ } else
+#endif
+ if (!err &&
+ (valid & (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) ==
+ (FUSE_SET_ATTR_ATIME | FUSE_SET_ATTR_MTIME)) {
+ struct timespec tv[2];
+ tv[0].tv_sec = attr->st_atime;
+ tv[0].tv_nsec = ST_ATIM_NSEC(attr);
+ tv[1].tv_sec = attr->st_mtime;
+ tv[1].tv_nsec = ST_MTIM_NSEC(attr);
+ err = fuse_fs_utimens(f->fs, path, tv, fi);
+ }
+ if (!err) {
+ err = fuse_fs_getattr(f->fs, path, &buf, fi);
+ }
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ if (!err) {
+ if (f->conf.auto_cache) {
+ pthread_mutex_lock(&f->lock);
+ update_stat(get_node(f, ino), &buf);
+ pthread_mutex_unlock(&f->lock);
+ }
+ set_stat(f, ino, &buf);
+ fuse_reply_attr(req, &buf, f->conf.attr_timeout);
+ } else
+ reply_err(req, err);
+}
+
+static void fuse_lib_access(fuse_req_t req, fuse_ino_t ino, int mask)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_access(f->fs, path, mask);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_readlink(fuse_req_t req, fuse_ino_t ino)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char linkname[PATH_MAX + 1];
+ char *path;
+ int err;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_readlink(f->fs, path, linkname, sizeof(linkname));
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ if (!err) {
+ linkname[PATH_MAX] = '\0';
+ fuse_reply_readlink(req, linkname);
+ } else
+ reply_err(req, err);
+}
+
+static void fuse_lib_mknod(fuse_req_t req, fuse_ino_t parent, const char *name,
+ mode_t mode, dev_t rdev)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_entry_param e;
+ char *path;
+ int err;
+
+ err = get_path_name(f, parent, name, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = -ENOSYS;
+ if (S_ISREG(mode)) {
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.flags = O_CREAT | O_EXCL | O_WRONLY;
+ err = fuse_fs_create(f->fs, path, mode, &fi);
+ if (!err) {
+ err = lookup_path(f, parent, name, path, &e,
+ &fi);
+ fuse_fs_release(f->fs, path, &fi);
+ }
+ }
+ if (err == -ENOSYS) {
+ err = fuse_fs_mknod(f->fs, path, mode, rdev);
+ if (!err)
+ err = lookup_path(f, parent, name, path, &e,
+ NULL);
+ }
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, parent, path);
+ }
+ reply_entry(req, &e, err);
+}
+
+static void fuse_lib_mkdir(fuse_req_t req, fuse_ino_t parent, const char *name,
+ mode_t mode)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_entry_param e;
+ char *path;
+ int err;
+
+ err = get_path_name(f, parent, name, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_mkdir(f->fs, path, mode);
+ if (!err)
+ err = lookup_path(f, parent, name, path, &e, NULL);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, parent, path);
+ }
+ reply_entry(req, &e, err);
+}
+
+static void fuse_lib_unlink(fuse_req_t req, fuse_ino_t parent,
+ const char *name)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct node *wnode;
+ char *path;
+ int err;
+
+ err = get_path_wrlock(f, parent, name, &path, &wnode);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ if (!f->conf.hard_remove && is_open(f, parent, name)) {
+ err = hide_node(f, path, parent, name);
+ } else {
+ err = fuse_fs_unlink(f->fs, path);
+ if (!err)
+ remove_node(f, parent, name);
+ }
+ fuse_finish_interrupt(f, req, &d);
+ free_path_wrlock(f, parent, wnode, path);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_rmdir(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct node *wnode;
+ char *path;
+ int err;
+
+ err = get_path_wrlock(f, parent, name, &path, &wnode);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_rmdir(f->fs, path);
+ fuse_finish_interrupt(f, req, &d);
+ if (!err)
+ remove_node(f, parent, name);
+ free_path_wrlock(f, parent, wnode, path);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_symlink(fuse_req_t req, const char *linkname,
+ fuse_ino_t parent, const char *name)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_entry_param e;
+ char *path;
+ int err;
+
+ err = get_path_name(f, parent, name, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_symlink(f->fs, linkname, path);
+ if (!err)
+ err = lookup_path(f, parent, name, path, &e, NULL);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, parent, path);
+ }
+ reply_entry(req, &e, err);
+}
+
+static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir,
+ const char *oldname, fuse_ino_t newdir,
+ const char *newname, unsigned int flags)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *oldpath;
+ char *newpath;
+ struct node *wnode1;
+ struct node *wnode2;
+ int err;
+
+ err = get_path2(f, olddir, oldname, newdir, newname,
+ &oldpath, &newpath, &wnode1, &wnode2);
+ if (!err) {
+ struct fuse_intr_data d;
+ err = 0;
+ fuse_prepare_interrupt(f, req, &d);
+ if (!f->conf.hard_remove && !(flags & RENAME_EXCHANGE) &&
+ is_open(f, newdir, newname))
+ err = hide_node(f, newpath, newdir, newname);
+ if (!err) {
+ err = fuse_fs_rename(f->fs, oldpath, newpath, flags);
+ if (!err) {
+ if (flags & RENAME_EXCHANGE) {
+ err = exchange_node(f, olddir, oldname,
+ newdir, newname);
+ } else {
+ err = rename_node(f, olddir, oldname,
+ newdir, newname, 0);
+ }
+ }
+ }
+ fuse_finish_interrupt(f, req, &d);
+ free_path2(f, olddir, newdir, wnode1, wnode2, oldpath, newpath);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent,
+ const char *newname)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_entry_param e;
+ char *oldpath;
+ char *newpath;
+ int err;
+
+ err = get_path2(f, ino, NULL, newparent, newname,
+ &oldpath, &newpath, NULL, NULL);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_link(f->fs, oldpath, newpath);
+ if (!err)
+ err = lookup_path(f, newparent, newname, newpath,
+ &e, NULL);
+ fuse_finish_interrupt(f, req, &d);
+ free_path2(f, ino, newparent, NULL, NULL, oldpath, newpath);
+ }
+ reply_entry(req, &e, err);
+}
+
+static void fuse_do_release(struct fuse *f, fuse_ino_t ino, const char *path,
+ struct fuse_file_info *fi)
+{
+ struct node *node;
+ int unlink_hidden = 0;
+
+ fuse_fs_release(f->fs, path, fi);
+
+ pthread_mutex_lock(&f->lock);
+ node = get_node(f, ino);
+ assert(node->open_count > 0);
+ --node->open_count;
+ if (node->is_hidden && !node->open_count) {
+ unlink_hidden = 1;
+ node->is_hidden = 0;
+ }
+ pthread_mutex_unlock(&f->lock);
+
+ if(unlink_hidden) {
+ if (path) {
+ fuse_fs_unlink(f->fs, path);
+ } else if (f->conf.nullpath_ok) {
+ char *unlinkpath;
+
+ if (get_path(f, ino, &unlinkpath) == 0)
+ fuse_fs_unlink(f->fs, unlinkpath);
+
+ free_path(f, ino, unlinkpath);
+ }
+ }
+}
+
+static void fuse_lib_create(fuse_req_t req, fuse_ino_t parent,
+ const char *name, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ struct fuse_entry_param e;
+ char *path;
+ int err;
+
+ err = get_path_name(f, parent, name, &path);
+ if (!err) {
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_create(f->fs, path, mode, fi);
+ if (!err) {
+ err = lookup_path(f, parent, name, path, &e, fi);
+ if (err)
+ fuse_fs_release(f->fs, path, fi);
+ else if (!S_ISREG(e.attr.st_mode)) {
+ err = -EIO;
+ fuse_fs_release(f->fs, path, fi);
+ forget_node(f, e.ino, 1);
+ } else {
+ if (f->conf.direct_io)
+ fi->direct_io = 1;
+ if (f->conf.kernel_cache)
+ fi->keep_cache = 1;
+
+ }
+ }
+ fuse_finish_interrupt(f, req, &d);
+ }
+ if (!err) {
+ pthread_mutex_lock(&f->lock);
+ get_node(f, e.ino)->open_count++;
+ pthread_mutex_unlock(&f->lock);
+ if (fuse_reply_create(req, &e, fi) == -ENOENT) {
+ /* The open syscall was interrupted, so it
+ must be cancelled */
+ fuse_do_release(f, e.ino, path, fi);
+ forget_node(f, e.ino, 1);
+ }
+ } else {
+ reply_err(req, err);
+ }
+
+ free_path(f, parent, path);
+}
+
+static double diff_timespec(const struct timespec *t1,
+ const struct timespec *t2)
+{
+ return (t1->tv_sec - t2->tv_sec) +
+ ((double) t1->tv_nsec - (double) t2->tv_nsec) / 1000000000.0;
+}
+
+static void open_auto_cache(struct fuse *f, fuse_ino_t ino, const char *path,
+ struct fuse_file_info *fi)
+{
+ struct node *node;
+
+ pthread_mutex_lock(&f->lock);
+ node = get_node(f, ino);
+ if (node->cache_valid) {
+ struct timespec now;
+
+ curr_time(&now);
+ if (diff_timespec(&now, &node->stat_updated) >
+ f->conf.ac_attr_timeout) {
+ struct stat stbuf;
+ int err;
+ pthread_mutex_unlock(&f->lock);
+ err = fuse_fs_getattr(f->fs, path, &stbuf, fi);
+ pthread_mutex_lock(&f->lock);
+ if (!err)
+ update_stat(node, &stbuf);
+ else
+ node->cache_valid = 0;
+ }
+ }
+ if (node->cache_valid)
+ fi->keep_cache = 1;
+
+ node->cache_valid = 1;
+ pthread_mutex_unlock(&f->lock);
+}
+
+static void fuse_lib_open(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path;
+ int err;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_open(f->fs, path, fi);
+ if (!err) {
+ if (f->conf.direct_io)
+ fi->direct_io = 1;
+ if (f->conf.kernel_cache)
+ fi->keep_cache = 1;
+
+ if (f->conf.auto_cache)
+ open_auto_cache(f, ino, path, fi);
+ }
+ fuse_finish_interrupt(f, req, &d);
+ }
+ if (!err) {
+ pthread_mutex_lock(&f->lock);
+ get_node(f, ino)->open_count++;
+ pthread_mutex_unlock(&f->lock);
+ if (fuse_reply_open(req, fi) == -ENOENT) {
+ /* The open syscall was interrupted, so it
+ must be cancelled */
+ fuse_do_release(f, ino, path, fi);
+ }
+ } else
+ reply_err(req, err);
+
+ free_path(f, ino, path);
+}
+
+static void fuse_lib_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t off, struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_bufvec *buf = NULL;
+ char *path;
+ int res;
+
+ res = get_path_nullok(f, ino, &path);
+ if (res == 0) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ res = fuse_fs_read_buf(f->fs, path, &buf, size, off, fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+
+ if (res == 0)
+ fuse_reply_data(req, buf, FUSE_BUF_SPLICE_MOVE);
+ else
+ reply_err(req, res);
+
+ fuse_free_buf(buf);
+}
+
+static void fuse_lib_write_buf(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_bufvec *buf, off_t off,
+ struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int res;
+
+ res = get_path_nullok(f, ino, &path);
+ if (res == 0) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ res = fuse_fs_write_buf(f->fs, path, buf, off, fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+
+ if (res >= 0)
+ fuse_reply_write(req, res);
+ else
+ reply_err(req, res);
+}
+
+static void fuse_lib_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
+ struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_fsync(f->fs, path, datasync, fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static struct fuse_dh *get_dirhandle(const struct fuse_file_info *llfi,
+ struct fuse_file_info *fi)
+{
+ struct fuse_dh *dh = (struct fuse_dh *) (uintptr_t) llfi->fh;
+ memset(fi, 0, sizeof(struct fuse_file_info));
+ fi->fh = dh->fh;
+ return dh;
+}
+
+static void fuse_lib_opendir(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *llfi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ struct fuse_dh *dh;
+ struct fuse_file_info fi;
+ char *path;
+ int err;
+
+ dh = (struct fuse_dh *) malloc(sizeof(struct fuse_dh));
+ if (dh == NULL) {
+ reply_err(req, -ENOMEM);
+ return;
+ }
+ memset(dh, 0, sizeof(struct fuse_dh));
+ dh->fuse = f;
+ dh->contents = NULL;
+ dh->first = NULL;
+ dh->len = 0;
+ dh->filled = 0;
+ dh->nodeid = ino;
+ fuse_mutex_init(&dh->lock);
+
+ llfi->fh = (uintptr_t) dh;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.flags = llfi->flags;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_opendir(f->fs, path, &fi);
+ fuse_finish_interrupt(f, req, &d);
+ dh->fh = fi.fh;
+ }
+ if (!err) {
+ if (fuse_reply_open(req, llfi) == -ENOENT) {
+ /* The opendir syscall was interrupted, so it
+ must be cancelled */
+ fuse_fs_releasedir(f->fs, path, &fi);
+ pthread_mutex_destroy(&dh->lock);
+ free(dh);
+ }
+ } else {
+ reply_err(req, err);
+ pthread_mutex_destroy(&dh->lock);
+ free(dh);
+ }
+ free_path(f, ino, path);
+}
+
+static int extend_contents(struct fuse_dh *dh, unsigned minsize)
+{
+ if (minsize > dh->size) {
+ char *newptr;
+ unsigned newsize = dh->size;
+ if (!newsize)
+ newsize = 1024;
+ while (newsize < minsize) {
+ if (newsize >= 0x80000000)
+ newsize = 0xffffffff;
+ else
+ newsize *= 2;
+ }
+
+ newptr = (char *) realloc(dh->contents, newsize);
+ if (!newptr) {
+ dh->error = -ENOMEM;
+ return -1;
+ }
+ dh->contents = newptr;
+ dh->size = newsize;
+ }
+ return 0;
+}
+
+static int fuse_add_direntry_to_dh(struct fuse_dh *dh, const char *name,
+ struct stat *st)
+{
+ struct fuse_direntry *de;
+
+ de = malloc(sizeof(struct fuse_direntry));
+ if (!de) {
+ dh->error = -ENOMEM;
+ return -1;
+ }
+ de->name = strdup(name);
+ if (!de->name) {
+ dh->error = -ENOMEM;
+ free(de);
+ return -1;
+ }
+ de->stat = *st;
+ de->next = NULL;
+
+ *dh->last = de;
+ dh->last = &de->next;
+
+ return 0;
+}
+
+static fuse_ino_t lookup_nodeid(struct fuse *f, fuse_ino_t parent,
+ const char *name)
+{
+ struct node *node;
+ fuse_ino_t res = FUSE_UNKNOWN_INO;
+
+ pthread_mutex_lock(&f->lock);
+ node = lookup_node(f, parent, name);
+ if (node)
+ res = node->nodeid;
+ pthread_mutex_unlock(&f->lock);
+
+ return res;
+}
+
+static int fill_dir(void *dh_, const char *name, const struct stat *statp,
+ off_t off, enum fuse_fill_dir_flags flags)
+{
+ struct fuse_dh *dh = (struct fuse_dh *) dh_;
+ struct stat stbuf;
+
+ if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+ dh->error = -EIO;
+ return 1;
+ }
+
+ if (statp)
+ stbuf = *statp;
+ else {
+ memset(&stbuf, 0, sizeof(stbuf));
+ stbuf.st_ino = FUSE_UNKNOWN_INO;
+ }
+
+ if (!dh->fuse->conf.use_ino) {
+ stbuf.st_ino = FUSE_UNKNOWN_INO;
+ if (dh->fuse->conf.readdir_ino) {
+ stbuf.st_ino = (ino_t)
+ lookup_nodeid(dh->fuse, dh->nodeid, name);
+ }
+ }
+
+ if (off) {
+ size_t newlen;
+
+ if (dh->filled) {
+ dh->error = -EIO;
+ return 1;
+ }
+
+ if (dh->first) {
+ dh->error = -EIO;
+ return 1;
+ }
+
+ if (extend_contents(dh, dh->needlen) == -1)
+ return 1;
+
+ newlen = dh->len +
+ fuse_add_direntry(dh->req, dh->contents + dh->len,
+ dh->needlen - dh->len, name,
+ &stbuf, off);
+ if (newlen > dh->needlen)
+ return 1;
+
+ dh->len = newlen;
+ } else {
+ dh->filled = 1;
+
+ if (fuse_add_direntry_to_dh(dh, name, &stbuf) == -1)
+ return 1;
+ }
+ return 0;
+}
+
+static int is_dot_or_dotdot(const char *name)
+{
+ return name[0] == '.' && (name[1] == '\0' ||
+ (name[1] == '.' && name[2] == '\0'));
+}
+
+static int fill_dir_plus(void *dh_, const char *name, const struct stat *statp,
+ off_t off, enum fuse_fill_dir_flags flags)
+{
+ struct fuse_dh *dh = (struct fuse_dh *) dh_;
+ struct fuse_entry_param e = {
+ /* ino=0 tells the kernel to ignore readdirplus stat info */
+ .ino = 0,
+ };
+ struct fuse *f = dh->fuse;
+ int res;
+
+ if ((flags & ~FUSE_FILL_DIR_PLUS) != 0) {
+ dh->error = -EIO;
+ return 1;
+ }
+
+ if (off && statp && (flags & FUSE_FILL_DIR_PLUS)) {
+ e.attr = *statp;
+
+ if (!is_dot_or_dotdot(name)) {
+ res = do_lookup(f, dh->nodeid, name, &e);
+ if (res) {
+ dh->error = res;
+ return 1;
+ }
+ }
+ } else {
+ e.attr.st_ino = FUSE_UNKNOWN_INO;
+ if (!f->conf.use_ino && f->conf.readdir_ino) {
+ e.attr.st_ino = (ino_t)
+ lookup_nodeid(f, dh->nodeid, name);
+ }
+ }
+
+ if (off) {
+ size_t newlen;
+
+ if (dh->filled) {
+ dh->error = -EIO;
+ return 1;
+ }
+
+ if (dh->first) {
+ dh->error = -EIO;
+ return 1;
+ }
+ if (extend_contents(dh, dh->needlen) == -1)
+ return 1;
+
+ newlen = dh->len +
+ fuse_add_direntry_plus(dh->req, dh->contents + dh->len,
+ dh->needlen - dh->len, name,
+ &e, off);
+ if (newlen > dh->needlen)
+ return 1;
+ dh->len = newlen;
+ } else {
+ dh->filled = 1;
+
+ if (fuse_add_direntry_to_dh(dh, name, &e.attr) == -1)
+ return 1;
+ }
+
+ return 0;
+}
+
+static void free_direntries(struct fuse_direntry *de)
+{
+ while (de) {
+ struct fuse_direntry *next = de->next;
+ free(de->name);
+ free(de);
+ de = next;
+ }
+}
+
+static int readdir_fill(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+ size_t size, off_t off, struct fuse_dh *dh,
+ struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags)
+{
+ char *path;
+ int err;
+
+ if (f->fs->op.readdir)
+ err = get_path_nullok(f, ino, &path);
+ else
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_fill_dir_t filler = fill_dir;
+
+ if (flags & FUSE_READDIR_PLUS)
+ filler = fill_dir_plus;
+
+ free_direntries(dh->first);
+ dh->first = NULL;
+ dh->last = &dh->first;
+ dh->len = 0;
+ dh->error = 0;
+ dh->needlen = size;
+ dh->filled = 0;
+ dh->req = req;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_readdir(f->fs, path, dh, filler, off, fi, flags);
+ fuse_finish_interrupt(f, req, &d);
+ dh->req = NULL;
+ if (!err)
+ err = dh->error;
+ if (err)
+ dh->filled = 0;
+ free_path(f, ino, path);
+ }
+ return err;
+}
+
+static int readdir_fill_from_list(fuse_req_t req, struct fuse_dh *dh,
+ off_t off, enum fuse_readdir_flags flags)
+{
+ off_t pos;
+ struct fuse_direntry *de = dh->first;
+
+ dh->len = 0;
+
+ if (extend_contents(dh, dh->needlen) == -1)
+ return dh->error;
+
+ for (pos = 0; pos < off; pos++) {
+ if (!de)
+ break;
+
+ de = de->next;
+ }
+ while (de) {
+ char *p = dh->contents + dh->len;
+ unsigned rem = dh->needlen - dh->len;
+ unsigned thislen;
+ unsigned newlen;
+ pos++;
+
+ if (flags & FUSE_READDIR_PLUS) {
+ struct fuse_entry_param e = {
+ .ino = 0,
+ .attr = de->stat,
+ };
+ thislen = fuse_add_direntry_plus(req, p, rem,
+ de->name, &e, pos);
+ } else {
+ thislen = fuse_add_direntry(req, p, rem,
+ de->name, &de->stat, pos);
+ }
+ newlen = dh->len + thislen;
+ if (newlen > dh->needlen)
+ break;
+ dh->len = newlen;
+ de = de->next;
+ }
+ return 0;
+}
+
+static void fuse_readdir_common(fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t off, struct fuse_file_info *llfi,
+ enum fuse_readdir_flags flags)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_file_info fi;
+ struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+ int err;
+
+ pthread_mutex_lock(&dh->lock);
+ /* According to SUS, directory contents need to be refreshed on
+ rewinddir() */
+ if (!off)
+ dh->filled = 0;
+
+ if (!dh->filled) {
+ err = readdir_fill(f, req, ino, size, off, dh, &fi, flags);
+ if (err) {
+ reply_err(req, err);
+ goto out;
+ }
+ }
+ if (dh->filled) {
+ dh->needlen = size;
+ err = readdir_fill_from_list(req, dh, off, flags);
+ if (err) {
+ reply_err(req, err);
+ goto out;
+ }
+ }
+ fuse_reply_buf(req, dh->contents, dh->len);
+out:
+ pthread_mutex_unlock(&dh->lock);
+}
+
+static void fuse_lib_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t off, struct fuse_file_info *llfi)
+{
+ fuse_readdir_common(req, ino, size, off, llfi, 0);
+}
+
+static void fuse_lib_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+ off_t off, struct fuse_file_info *llfi)
+{
+ fuse_readdir_common(req, ino, size, off, llfi, FUSE_READDIR_PLUS);
+}
+
+static void fuse_lib_releasedir(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *llfi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ struct fuse_file_info fi;
+ struct fuse_dh *dh = get_dirhandle(llfi, &fi);
+ char *path;
+
+ get_path_nullok(f, ino, &path);
+
+ fuse_prepare_interrupt(f, req, &d);
+ fuse_fs_releasedir(f->fs, path, &fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+
+ pthread_mutex_lock(&dh->lock);
+ pthread_mutex_unlock(&dh->lock);
+ pthread_mutex_destroy(&dh->lock);
+ free_direntries(dh->first);
+ free(dh->contents);
+ free(dh);
+ reply_err(req, 0);
+}
+
+static void fuse_lib_fsyncdir(fuse_req_t req, fuse_ino_t ino, int datasync,
+ struct fuse_file_info *llfi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_file_info fi;
+ char *path;
+ int err;
+
+ get_dirhandle(llfi, &fi);
+
+ err = get_path_nullok(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_fsyncdir(f->fs, path, datasync, &fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_statfs(fuse_req_t req, fuse_ino_t ino)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct statvfs buf;
+ char *path = NULL;
+ int err = 0;
+
+ memset(&buf, 0, sizeof(buf));
+ if (ino)
+ err = get_path(f, ino, &path);
+
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_statfs(f->fs, path ? path : "/", &buf);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+
+ if (!err)
+ fuse_reply_statfs(req, &buf);
+ else
+ reply_err(req, err);
+}
+
+static void fuse_lib_setxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+ const char *value, size_t size, int flags)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_setxattr(f->fs, path, name, value, size, flags);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static int common_getxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+ const char *name, char *value, size_t size)
+{
+ int err;
+ char *path;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_getxattr(f->fs, path, name, value, size);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ return err;
+}
+
+static void fuse_lib_getxattr(fuse_req_t req, fuse_ino_t ino, const char *name,
+ size_t size)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ int res;
+
+ if (size) {
+ char *value = (char *) malloc(size);
+ if (value == NULL) {
+ reply_err(req, -ENOMEM);
+ return;
+ }
+ res = common_getxattr(f, req, ino, name, value, size);
+ if (res > 0)
+ fuse_reply_buf(req, value, res);
+ else
+ reply_err(req, res);
+ free(value);
+ } else {
+ res = common_getxattr(f, req, ino, name, NULL, 0);
+ if (res >= 0)
+ fuse_reply_xattr(req, res);
+ else
+ reply_err(req, res);
+ }
+}
+
+static int common_listxattr(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+ char *list, size_t size)
+{
+ char *path;
+ int err;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_listxattr(f->fs, path, list, size);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ return err;
+}
+
+static void fuse_lib_listxattr(fuse_req_t req, fuse_ino_t ino, size_t size)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ int res;
+
+ if (size) {
+ char *list = (char *) malloc(size);
+ if (list == NULL) {
+ reply_err(req, -ENOMEM);
+ return;
+ }
+ res = common_listxattr(f, req, ino, list, size);
+ if (res > 0)
+ fuse_reply_buf(req, list, res);
+ else
+ reply_err(req, res);
+ free(list);
+ } else {
+ res = common_listxattr(f, req, ino, NULL, 0);
+ if (res >= 0)
+ fuse_reply_xattr(req, res);
+ else
+ reply_err(req, res);
+ }
+}
+
+static void fuse_lib_removexattr(fuse_req_t req, fuse_ino_t ino,
+ const char *name)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_removexattr(f->fs, path, name);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static struct lock *locks_conflict(struct node *node, const struct lock *lock)
+{
+ struct lock *l;
+
+ for (l = node->locks; l; l = l->next)
+ if (l->owner != lock->owner &&
+ lock->start <= l->end && l->start <= lock->end &&
+ (l->type == F_WRLCK || lock->type == F_WRLCK))
+ break;
+
+ return l;
+}
+
+static void delete_lock(struct lock **lockp)
+{
+ struct lock *l = *lockp;
+ *lockp = l->next;
+ free(l);
+}
+
+static void insert_lock(struct lock **pos, struct lock *lock)
+{
+ lock->next = *pos;
+ *pos = lock;
+}
+
+static int locks_insert(struct node *node, struct lock *lock)
+{
+ struct lock **lp;
+ struct lock *newl1 = NULL;
+ struct lock *newl2 = NULL;
+
+ if (lock->type != F_UNLCK || lock->start != 0 ||
+ lock->end != OFFSET_MAX) {
+ newl1 = malloc(sizeof(struct lock));
+ newl2 = malloc(sizeof(struct lock));
+
+ if (!newl1 || !newl2) {
+ free(newl1);
+ free(newl2);
+ return -ENOLCK;
+ }
+ }
+
+ for (lp = &node->locks; *lp;) {
+ struct lock *l = *lp;
+ if (l->owner != lock->owner)
+ goto skip;
+
+ if (lock->type == l->type) {
+ if (l->end < lock->start - 1)
+ goto skip;
+ if (lock->end < l->start - 1)
+ break;
+ if (l->start <= lock->start && lock->end <= l->end)
+ goto out;
+ if (l->start < lock->start)
+ lock->start = l->start;
+ if (lock->end < l->end)
+ lock->end = l->end;
+ goto delete;
+ } else {
+ if (l->end < lock->start)
+ goto skip;
+ if (lock->end < l->start)
+ break;
+ if (lock->start <= l->start && l->end <= lock->end)
+ goto delete;
+ if (l->end <= lock->end) {
+ l->end = lock->start - 1;
+ goto skip;
+ }
+ if (lock->start <= l->start) {
+ l->start = lock->end + 1;
+ break;
+ }
+ *newl2 = *l;
+ newl2->start = lock->end + 1;
+ l->end = lock->start - 1;
+ insert_lock(&l->next, newl2);
+ newl2 = NULL;
+ }
+ skip:
+ lp = &l->next;
+ continue;
+
+ delete:
+ delete_lock(lp);
+ }
+ if (lock->type != F_UNLCK) {
+ *newl1 = *lock;
+ insert_lock(lp, newl1);
+ newl1 = NULL;
+ }
+out:
+ free(newl1);
+ free(newl2);
+ return 0;
+}
+
+static void flock_to_lock(struct flock *flock, struct lock *lock)
+{
+ memset(lock, 0, sizeof(struct lock));
+ lock->type = flock->l_type;
+ lock->start = flock->l_start;
+ lock->end =
+ flock->l_len ? flock->l_start + flock->l_len - 1 : OFFSET_MAX;
+ lock->pid = flock->l_pid;
+}
+
+static void lock_to_flock(struct lock *lock, struct flock *flock)
+{
+ flock->l_type = lock->type;
+ flock->l_start = lock->start;
+ flock->l_len =
+ (lock->end == OFFSET_MAX) ? 0 : lock->end - lock->start + 1;
+ flock->l_pid = lock->pid;
+}
+
+static int fuse_flush_common(struct fuse *f, fuse_req_t req, fuse_ino_t ino,
+ const char *path, struct fuse_file_info *fi)
+{
+ struct fuse_intr_data d;
+ struct flock lock;
+ struct lock l;
+ int err;
+ int errlock;
+
+ fuse_prepare_interrupt(f, req, &d);
+ memset(&lock, 0, sizeof(lock));
+ lock.l_type = F_UNLCK;
+ lock.l_whence = SEEK_SET;
+ err = fuse_fs_flush(f->fs, path, fi);
+ errlock = fuse_fs_lock(f->fs, path, fi, F_SETLK, &lock);
+ fuse_finish_interrupt(f, req, &d);
+
+ if (errlock != -ENOSYS) {
+ flock_to_lock(&lock, &l);
+ l.owner = fi->lock_owner;
+ pthread_mutex_lock(&f->lock);
+ locks_insert(get_node(f, ino), &l);
+ pthread_mutex_unlock(&f->lock);
+
+ /* if op.lock() is defined FLUSH is needed regardless
+ of op.flush() */
+ if (err == -ENOSYS)
+ err = 0;
+ }
+ return err;
+}
+
+static void fuse_lib_release(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path;
+ int err = 0;
+
+ get_path_nullok(f, ino, &path);
+ if (fi->flush) {
+ err = fuse_flush_common(f, req, ino, path, fi);
+ if (err == -ENOSYS)
+ err = 0;
+ }
+
+ fuse_prepare_interrupt(f, req, &d);
+ fuse_do_release(f, ino, path, fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+
+ reply_err(req, err);
+}
+
+static void fuse_lib_flush(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ get_path_nullok(f, ino, &path);
+ err = fuse_flush_common(f, req, ino, path, fi);
+ free_path(f, ino, path);
+
+ reply_err(req, err);
+}
+
+static int fuse_lock_common(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, struct flock *lock,
+ int cmd)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, ino, &path);
+ if (!err) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_lock(f->fs, path, fi, cmd, lock);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ return err;
+}
+
+static void fuse_lib_getlk(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, struct flock *lock)
+{
+ int err;
+ struct lock l;
+ struct lock *conflict;
+ struct fuse *f = req_fuse(req);
+
+ flock_to_lock(lock, &l);
+ l.owner = fi->lock_owner;
+ pthread_mutex_lock(&f->lock);
+ conflict = locks_conflict(get_node(f, ino), &l);
+ if (conflict)
+ lock_to_flock(conflict, lock);
+ pthread_mutex_unlock(&f->lock);
+ if (!conflict)
+ err = fuse_lock_common(req, ino, fi, lock, F_GETLK);
+ else
+ err = 0;
+
+ if (!err)
+ fuse_reply_lock(req, lock);
+ else
+ reply_err(req, err);
+}
+
+static void fuse_lib_setlk(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, struct flock *lock,
+ int sleep)
+{
+ int err = fuse_lock_common(req, ino, fi, lock,
+ sleep ? F_SETLKW : F_SETLK);
+ if (!err) {
+ struct fuse *f = req_fuse(req);
+ struct lock l;
+ flock_to_lock(lock, &l);
+ l.owner = fi->lock_owner;
+ pthread_mutex_lock(&f->lock);
+ locks_insert(get_node(f, ino), &l);
+ pthread_mutex_unlock(&f->lock);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_flock(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, int op)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, ino, &path);
+ if (err == 0) {
+ struct fuse_intr_data d;
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_flock(f->fs, path, fi, op);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_bmap(fuse_req_t req, fuse_ino_t ino, size_t blocksize,
+ uint64_t idx)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path;
+ int err;
+
+ err = get_path(f, ino, &path);
+ if (!err) {
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_bmap(f->fs, path, blocksize, &idx);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ if (!err)
+ fuse_reply_bmap(req, idx);
+ else
+ reply_err(req, err);
+}
+
+static void fuse_lib_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd,
+ void *arg, struct fuse_file_info *llfi,
+ unsigned int flags, const void *in_buf,
+ size_t in_bufsz, size_t out_bufsz)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ struct fuse_file_info fi;
+ char *path, *out_buf = NULL;
+ int err;
+
+ err = -EPERM;
+ if (flags & FUSE_IOCTL_UNRESTRICTED)
+ goto err;
+
+ if (flags & FUSE_IOCTL_DIR)
+ get_dirhandle(llfi, &fi);
+ else
+ fi = *llfi;
+
+ if (out_bufsz) {
+ err = -ENOMEM;
+ out_buf = malloc(out_bufsz);
+ if (!out_buf)
+ goto err;
+ }
+
+ assert(!in_bufsz || !out_bufsz || in_bufsz == out_bufsz);
+ if (out_buf && in_bufsz)
+ memcpy(out_buf, in_buf, in_bufsz);
+
+ err = get_path_nullok(f, ino, &path);
+ if (err)
+ goto err;
+
+ fuse_prepare_interrupt(f, req, &d);
+
+ err = fuse_fs_ioctl(f->fs, path, cmd, arg, &fi, flags,
+ out_buf ? out_buf : (void *)in_buf);
+
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+
+ fuse_reply_ioctl(req, err, out_buf, out_bufsz);
+ goto out;
+err:
+ reply_err(req, err);
+out:
+ free(out_buf);
+}
+
+static void fuse_lib_poll(fuse_req_t req, fuse_ino_t ino,
+ struct fuse_file_info *fi, struct fuse_pollhandle *ph)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path;
+ int err;
+ unsigned revents = 0;
+
+ err = get_path_nullok(f, ino, &path);
+ if (!err) {
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_poll(f->fs, path, fi, ph, &revents);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ if (!err)
+ fuse_reply_poll(req, revents);
+ else
+ reply_err(req, err);
+}
+
+static void fuse_lib_fallocate(fuse_req_t req, fuse_ino_t ino, int mode,
+ off_t offset, off_t length, struct fuse_file_info *fi)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path;
+ int err;
+
+ err = get_path_nullok(f, ino, &path);
+ if (!err) {
+ fuse_prepare_interrupt(f, req, &d);
+ err = fuse_fs_fallocate(f->fs, path, mode, offset, length, fi);
+ fuse_finish_interrupt(f, req, &d);
+ free_path(f, ino, path);
+ }
+ reply_err(req, err);
+}
+
+static void fuse_lib_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in,
+ off_t off_in, struct fuse_file_info *fi_in,
+ fuse_ino_t nodeid_out, off_t off_out,
+ struct fuse_file_info *fi_out, size_t len,
+ int flags)
+{
+ struct fuse *f = req_fuse_prepare(req);
+ struct fuse_intr_data d;
+ char *path_in, *path_out;
+ int err;
+ ssize_t res;
+
+ err = get_path_nullok(f, nodeid_in, &path_in);
+ if (err) {
+ reply_err(req, err);
+ return;
+ }
+
+ err = get_path_nullok(f, nodeid_out, &path_out);
+ if (err) {
+ free_path(f, nodeid_in, path_in);
+ reply_err(req, err);
+ return;
+ }
+
+ fuse_prepare_interrupt(f, req, &d);
+ res = fuse_fs_copy_file_range(f->fs, path_in, fi_in, off_in, path_out,
+ fi_out, off_out, len, flags);
+ fuse_finish_interrupt(f, req, &d);
+
+ if (res >= 0)
+ fuse_reply_write(req, res);
+ else
+ reply_err(req, res);
+
+ free_path(f, nodeid_in, path_in);
+ free_path(f, nodeid_out, path_out);
+}
+
+static int clean_delay(struct fuse *f)
+{
+ /*
+ * This is calculating the delay between clean runs. To
+ * reduce the number of cleans we are doing them 10 times
+ * within the remember window.
+ */
+ int min_sleep = 60;
+ int max_sleep = 3600;
+ int sleep_time = f->conf.remember / 10;
+
+ if (sleep_time > max_sleep)
+ return max_sleep;
+ if (sleep_time < min_sleep)
+ return min_sleep;
+ return sleep_time;
+}
+
+int fuse_clean_cache(struct fuse *f)
+{
+ struct node_lru *lnode;
+ struct list_head *curr, *next;
+ struct node *node;
+ struct timespec now;
+
+ pthread_mutex_lock(&f->lock);
+
+ curr_time(&now);
+
+ for (curr = f->lru_table.next; curr != &f->lru_table; curr = next) {
+ double age;
+
+ next = curr->next;
+ lnode = list_entry(curr, struct node_lru, lru);
+ node = &lnode->node;
+
+ age = diff_timespec(&now, &lnode->forget_time);
+ if (age <= f->conf.remember)
+ break;
+
+ assert(node->nlookup == 1);
+
+ /* Don't forget active directories */
+ if (node->refctr > 1)
+ continue;
+
+ node->nlookup = 0;
+ unhash_name(f, node);
+ unref_node(f, node);
+ }
+ pthread_mutex_unlock(&f->lock);
+
+ return clean_delay(f);
+}
+
+static struct fuse_lowlevel_ops fuse_path_ops = {
+ .init = fuse_lib_init,
+ .destroy = fuse_lib_destroy,
+ .lookup = fuse_lib_lookup,
+ .forget = fuse_lib_forget,
+ .forget_multi = fuse_lib_forget_multi,
+ .getattr = fuse_lib_getattr,
+ .setattr = fuse_lib_setattr,
+ .access = fuse_lib_access,
+ .readlink = fuse_lib_readlink,
+ .mknod = fuse_lib_mknod,
+ .mkdir = fuse_lib_mkdir,
+ .unlink = fuse_lib_unlink,
+ .rmdir = fuse_lib_rmdir,
+ .symlink = fuse_lib_symlink,
+ .rename = fuse_lib_rename,
+ .link = fuse_lib_link,
+ .create = fuse_lib_create,
+ .open = fuse_lib_open,
+ .read = fuse_lib_read,
+ .write_buf = fuse_lib_write_buf,
+ .flush = fuse_lib_flush,
+ .release = fuse_lib_release,
+ .fsync = fuse_lib_fsync,
+ .opendir = fuse_lib_opendir,
+ .readdir = fuse_lib_readdir,
+ .readdirplus = fuse_lib_readdirplus,
+ .releasedir = fuse_lib_releasedir,
+ .fsyncdir = fuse_lib_fsyncdir,
+ .statfs = fuse_lib_statfs,
+ .setxattr = fuse_lib_setxattr,
+ .getxattr = fuse_lib_getxattr,
+ .listxattr = fuse_lib_listxattr,
+ .removexattr = fuse_lib_removexattr,
+ .getlk = fuse_lib_getlk,
+ .setlk = fuse_lib_setlk,
+ .flock = fuse_lib_flock,
+ .bmap = fuse_lib_bmap,
+ .ioctl = fuse_lib_ioctl,
+ .poll = fuse_lib_poll,
+ .fallocate = fuse_lib_fallocate,
+ .copy_file_range = fuse_lib_copy_file_range,
+};
+
+int fuse_notify_poll(struct fuse_pollhandle *ph)
+{
+ return fuse_lowlevel_notify_poll(ph);
+}
+
+struct fuse_session *fuse_get_session(struct fuse *f)
+{
+ return f->se;
+}
+
+static int fuse_session_loop_remember(struct fuse *f)
+{
+ struct fuse_session *se = f->se;
+ int res = 0;
+ struct timespec now;
+ time_t next_clean;
+ struct pollfd fds = {
+ .fd = se->fd,
+ .events = POLLIN
+ };
+ struct fuse_buf fbuf = {
+ .mem = NULL,
+ };
+
+ curr_time(&now);
+ next_clean = now.tv_sec;
+ while (!fuse_session_exited(se)) {
+ unsigned timeout;
+
+ curr_time(&now);
+ if (now.tv_sec < next_clean)
+ timeout = next_clean - now.tv_sec;
+ else
+ timeout = 0;
+
+ res = poll(&fds, 1, timeout * 1000);
+ if (res == -1) {
+ if (errno == -EINTR)
+ continue;
+ else
+ break;
+ } else if (res > 0) {
+ res = fuse_session_receive_buf_int(se, &fbuf, NULL);
+
+ if (res == -EINTR)
+ continue;
+ if (res <= 0)
+ break;
+
+ fuse_session_process_buf_int(se, &fbuf, NULL);
+ } else {
+ timeout = fuse_clean_cache(f);
+ curr_time(&now);
+ next_clean = now.tv_sec + timeout;
+ }
+ }
+
+ free(fbuf.mem);
+ fuse_session_reset(se);
+ return res < 0 ? -1 : 0;
+}
+
+int fuse_loop(struct fuse *f)
+{
+ if (!f)
+ return -1;
+
+ if (lru_enabled(f))
+ return fuse_session_loop_remember(f);
+
+ return fuse_session_loop(f->se);
+}
+
+FUSE_SYMVER(".symver fuse_loop_mt_32,fuse_loop_mt@@FUSE_3.2");
+int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config)
+{
+ if (f == NULL)
+ return -1;
+
+ int res = fuse_start_cleanup_thread(f);
+ if (res)
+ return -1;
+
+ res = fuse_session_loop_mt_32(fuse_get_session(f), config);
+ fuse_stop_cleanup_thread(f);
+ return res;
+}
+
+int fuse_loop_mt_31(struct fuse *f, int clone_fd);
+FUSE_SYMVER(".symver fuse_loop_mt_31,fuse_loop_mt@FUSE_3.0");
+int fuse_loop_mt_31(struct fuse *f, int clone_fd)
+{
+ struct fuse_loop_config config;
+ config.clone_fd = clone_fd;
+ config.max_idle_threads = 10;
+ return fuse_loop_mt_32(f, &config);
+}
+
+void fuse_exit(struct fuse *f)
+{
+ fuse_session_exit(f->se);
+}
+
+struct fuse_context *fuse_get_context(void)
+{
+ struct fuse_context_i *c = fuse_get_context_internal();
+
+ if (c)
+ return &c->ctx;
+ else
+ return NULL;
+}
+
+int fuse_getgroups(int size, gid_t list[])
+{
+ struct fuse_context_i *c = fuse_get_context_internal();
+ if (!c)
+ return -EINVAL;
+
+ return fuse_req_getgroups(c->req, size, list);
+}
+
+int fuse_interrupted(void)
+{
+ struct fuse_context_i *c = fuse_get_context_internal();
+
+ if (c)
+ return fuse_req_interrupted(c->req);
+ else
+ return 0;
+}
+
+int fuse_invalidate_path(struct fuse *f, const char *path) {
+ fuse_ino_t ino;
+ int err = lookup_path_in_cache(f, path, &ino);
+ if (err) {
+ return err;
+ }
+
+ return fuse_lowlevel_notify_inval_inode(f->se, ino, 0, 0);
+}
+
+#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
+static const struct fuse_opt fuse_lib_opts[] = {
+ FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
+ FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
+ FUSE_LIB_OPT("debug", debug, 1),
+ FUSE_LIB_OPT("-d", debug, 1),
+ FUSE_LIB_OPT("kernel_cache", kernel_cache, 1),
+ FUSE_LIB_OPT("auto_cache", auto_cache, 1),
+ FUSE_LIB_OPT("noauto_cache", auto_cache, 0),
+ FUSE_LIB_OPT("umask=", set_mode, 1),
+ FUSE_LIB_OPT("umask=%o", umask, 0),
+ FUSE_LIB_OPT("uid=", set_uid, 1),
+ FUSE_LIB_OPT("uid=%d", uid, 0),
+ FUSE_LIB_OPT("gid=", set_gid, 1),
+ FUSE_LIB_OPT("gid=%d", gid, 0),
+ FUSE_LIB_OPT("entry_timeout=%lf", entry_timeout, 0),
+ FUSE_LIB_OPT("attr_timeout=%lf", attr_timeout, 0),
+ FUSE_LIB_OPT("ac_attr_timeout=%lf", ac_attr_timeout, 0),
+ FUSE_LIB_OPT("ac_attr_timeout=", ac_attr_timeout_set, 1),
+ FUSE_LIB_OPT("negative_timeout=%lf", negative_timeout, 0),
+ FUSE_LIB_OPT("noforget", remember, -1),
+ FUSE_LIB_OPT("remember=%u", remember, 0),
+ FUSE_LIB_OPT("modules=%s", modules, 0),
+ FUSE_OPT_END
+};
+
+static int fuse_lib_opt_proc(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ (void) arg; (void) outargs; (void) data; (void) key;
+
+ /* Pass through unknown options */
+ return 1;
+}
+
+
+static const struct fuse_opt fuse_help_opts[] = {
+ FUSE_LIB_OPT("modules=%s", modules, 1),
+ FUSE_OPT_KEY("modules=%s", FUSE_OPT_KEY_KEEP),
+ FUSE_OPT_END
+};
+
+static void print_module_help(const char *name,
+ fuse_module_factory_t *fac)
+{
+ struct fuse_args a = FUSE_ARGS_INIT(0, NULL);
+ if (fuse_opt_add_arg(&a, "") == -1 ||
+ fuse_opt_add_arg(&a, "-h") == -1)
+ return;
+ printf("\nOptions for %s module:\n", name);
+ (*fac)(&a, NULL);
+ fuse_opt_free_args(&a);
+}
+
+void fuse_lib_help(struct fuse_args *args)
+{
+ /* These are not all options, but only the ones that
+ may be of interest to an end-user */
+ printf(
+" -o kernel_cache cache files in kernel\n"
+" -o [no]auto_cache enable caching based on modification times (off)\n"
+" -o umask=M set file permissions (octal)\n"
+" -o uid=N set file owner\n"
+" -o gid=N set file group\n"
+" -o entry_timeout=T cache timeout for names (1.0s)\n"
+" -o negative_timeout=T cache timeout for deleted names (0.0s)\n"
+" -o attr_timeout=T cache timeout for attributes (1.0s)\n"
+" -o ac_attr_timeout=T auto cache timeout for attributes (attr_timeout)\n"
+" -o noforget never forget cached inodes\n"
+" -o remember=T remember cached inodes for T seconds (0s)\n"
+" -o modules=M1[:M2...] names of modules to push onto filesystem stack\n");
+
+
+ /* Print low-level help */
+ fuse_lowlevel_help();
+
+ /* Print help for builtin modules */
+ print_module_help("subdir", &fuse_module_subdir_factory);
+#ifdef HAVE_ICONV
+ print_module_help("iconv", &fuse_module_iconv_factory);
+#endif
+
+ /* Parse command line options in case we need to
+ activate more modules */
+ struct fuse_config conf = { .modules = NULL };
+ if (fuse_opt_parse(args, &conf, fuse_help_opts,
+ fuse_lib_opt_proc) == -1
+ || !conf.modules)
+ return;
+
+ char *module;
+ char *next;
+ struct fuse_module *m;
+
+ // Iterate over all modules
+ for (module = conf.modules; module; module = next) {
+ char *p;
+ for (p = module; *p && *p != ':'; p++);
+ next = *p ? p + 1 : NULL;
+ *p = '\0';
+
+ m = fuse_get_module(module);
+ if (m)
+ print_module_help(module, &m->factory);
+ }
+}
+
+
+
+static int fuse_init_intr_signal(int signum, int *installed)
+{
+ struct sigaction old_sa;
+
+ if (sigaction(signum, NULL, &old_sa) == -1) {
+ perror("fuse: cannot get old signal handler");
+ return -1;
+ }
+
+ if (old_sa.sa_handler == SIG_DFL) {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_handler = fuse_intr_sighandler;
+ sigemptyset(&sa.sa_mask);
+
+ if (sigaction(signum, &sa, NULL) == -1) {
+ perror("fuse: cannot set interrupt signal handler");
+ return -1;
+ }
+ *installed = 1;
+ }
+ return 0;
+}
+
+static void fuse_restore_intr_signal(int signum)
+{
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_handler = SIG_DFL;
+ sigaction(signum, &sa, NULL);
+}
+
+
+static int fuse_push_module(struct fuse *f, const char *module,
+ struct fuse_args *args)
+{
+ struct fuse_fs *fs[2] = { f->fs, NULL };
+ struct fuse_fs *newfs;
+ struct fuse_module *m = fuse_get_module(module);
+
+ if (!m)
+ return -1;
+
+ newfs = m->factory(args, fs);
+ if (!newfs) {
+ fuse_put_module(m);
+ return -1;
+ }
+ newfs->m = m;
+ f->fs = newfs;
+ return 0;
+}
+
+struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+ void *user_data)
+{
+ struct fuse_fs *fs;
+
+ if (sizeof(struct fuse_operations) < op_size) {
+ fprintf(stderr, "fuse: warning: library too old, some operations may not not work\n");
+ op_size = sizeof(struct fuse_operations);
+ }
+
+ fs = (struct fuse_fs *) calloc(1, sizeof(struct fuse_fs));
+ if (!fs) {
+ fprintf(stderr, "fuse: failed to allocate fuse_fs object\n");
+ return NULL;
+ }
+
+ fs->user_data = user_data;
+ if (op)
+ memcpy(&fs->op, op, op_size);
+ return fs;
+}
+
+static int node_table_init(struct node_table *t)
+{
+ t->size = NODE_TABLE_MIN_SIZE;
+ t->array = (struct node **) calloc(1, sizeof(struct node *) * t->size);
+ if (t->array == NULL) {
+ fprintf(stderr, "fuse: memory allocation failed\n");
+ return -1;
+ }
+ t->use = 0;
+ t->split = 0;
+
+ return 0;
+}
+
+static void *fuse_prune_nodes(void *fuse)
+{
+ struct fuse *f = fuse;
+ int sleep_time;
+
+ while(1) {
+ sleep_time = fuse_clean_cache(f);
+ sleep(sleep_time);
+ }
+ return NULL;
+}
+
+int fuse_start_cleanup_thread(struct fuse *f)
+{
+ if (lru_enabled(f))
+ return fuse_start_thread(&f->prune_thread, fuse_prune_nodes, f);
+
+ return 0;
+}
+
+void fuse_stop_cleanup_thread(struct fuse *f)
+{
+ if (lru_enabled(f)) {
+ pthread_mutex_lock(&f->lock);
+ pthread_cancel(f->prune_thread);
+ pthread_mutex_unlock(&f->lock);
+ pthread_join(f->prune_thread, NULL);
+ }
+}
+
+
+FUSE_SYMVER(".symver fuse_new_31,fuse_new@@FUSE_3.1");
+struct fuse *fuse_new_31(struct fuse_args *args,
+ const struct fuse_operations *op,
+ size_t op_size, void *user_data)
+{
+ struct fuse *f;
+ struct node *root;
+ struct fuse_fs *fs;
+ struct fuse_lowlevel_ops llop = fuse_path_ops;
+
+ f = (struct fuse *) calloc(1, sizeof(struct fuse));
+ if (f == NULL) {
+ fprintf(stderr, "fuse: failed to allocate fuse object\n");
+ goto out;
+ }
+
+ f->conf.entry_timeout = 1.0;
+ f->conf.attr_timeout = 1.0;
+ f->conf.negative_timeout = 0.0;
+ f->conf.intr_signal = FUSE_DEFAULT_INTR_SIGNAL;
+
+ /* Parse options */
+ if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+ fuse_lib_opt_proc) == -1)
+ goto out_free;
+
+ pthread_mutex_lock(&fuse_context_lock);
+ static int builtin_modules_registered = 0;
+ /* Have the builtin modules already been registered? */
+ if (builtin_modules_registered == 0) {
+ /* If not, register them. */
+ fuse_register_module("subdir", fuse_module_subdir_factory, NULL);
+#ifdef HAVE_ICONV
+ fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+#endif
+ builtin_modules_registered= 1;
+ }
+ pthread_mutex_unlock(&fuse_context_lock);
+
+ if (fuse_create_context_key() == -1)
+ goto out_free;
+
+ fs = fuse_fs_new(op, op_size, user_data);
+ if (!fs)
+ goto out_delete_context_key;
+
+ f->fs = fs;
+
+ /* Oh f**k, this is ugly! */
+ if (!fs->op.lock) {
+ llop.getlk = NULL;
+ llop.setlk = NULL;
+ }
+
+ f->pagesize = getpagesize();
+ init_list_head(&f->partial_slabs);
+ init_list_head(&f->full_slabs);
+ init_list_head(&f->lru_table);
+
+ if (f->conf.modules) {
+ char *module;
+ char *next;
+
+ for (module = f->conf.modules; module; module = next) {
+ char *p;
+ for (p = module; *p && *p != ':'; p++);
+ next = *p ? p + 1 : NULL;
+ *p = '\0';
+ if (module[0] &&
+ fuse_push_module(f, module, args) == -1)
+ goto out_free_fs;
+ }
+ }
+
+ if (!f->conf.ac_attr_timeout_set)
+ f->conf.ac_attr_timeout = f->conf.attr_timeout;
+
+#if defined(__FreeBSD__) || defined(__NetBSD__)
+ /*
+ * In FreeBSD, we always use these settings as inode numbers
+ * are needed to make getcwd(3) work.
+ */
+ f->conf.readdir_ino = 1;
+#endif
+
+ f->se = fuse_session_new(args, &llop, sizeof(llop), f);
+ if (f->se == NULL)
+ goto out_free_fs;
+
+ if (f->conf.debug) {
+ fprintf(stderr, "nullpath_ok: %i\n", f->conf.nullpath_ok);
+ }
+
+ /* Trace topmost layer by default */
+ f->fs->debug = f->conf.debug;
+ f->ctr = 0;
+ f->generation = 0;
+ if (node_table_init(&f->name_table) == -1)
+ goto out_free_session;
+
+ if (node_table_init(&f->id_table) == -1)
+ goto out_free_name_table;
+
+ fuse_mutex_init(&f->lock);
+
+ root = alloc_node(f);
+ if (root == NULL) {
+ fprintf(stderr, "fuse: memory allocation failed\n");
+ goto out_free_id_table;
+ }
+ if (lru_enabled(f)) {
+ struct node_lru *lnode = node_lru(root);
+ init_list_head(&lnode->lru);
+ }
+
+ strcpy(root->inline_name, "/");
+ root->name = root->inline_name;
+
+ if (f->conf.intr &&
+ fuse_init_intr_signal(f->conf.intr_signal,
+ &f->intr_installed) == -1)
+ goto out_free_root;
+
+ root->parent = NULL;
+ root->nodeid = FUSE_ROOT_ID;
+ inc_nlookup(root);
+ hash_id(f, root);
+
+ return f;
+
+out_free_root:
+ free(root);
+out_free_id_table:
+ free(f->id_table.array);
+out_free_name_table:
+ free(f->name_table.array);
+out_free_session:
+ fuse_session_destroy(f->se);
+out_free_fs:
+ if (f->fs->m)
+ fuse_put_module(f->fs->m);
+ free(f->fs);
+ free(f->conf.modules);
+out_delete_context_key:
+ fuse_delete_context_key();
+out_free:
+ free(f);
+out:
+ return NULL;
+}
+
+/* Emulates 3.0-style fuse_new(), which processes --help */
+struct fuse *fuse_new_30(struct fuse_args *args, const struct fuse_operations *op,
+ size_t op_size, void *private_data);
+FUSE_SYMVER(".symver fuse_new_30,fuse_new@FUSE_3.0");
+struct fuse *fuse_new_30(struct fuse_args *args,
+ const struct fuse_operations *op,
+ size_t op_size, void *user_data)
+{
+ struct fuse_config conf;
+
+ memset(&conf, 0, sizeof(conf));
+
+ const struct fuse_opt opts[] = {
+ FUSE_LIB_OPT("-h", show_help, 1),
+ FUSE_LIB_OPT("--help", show_help, 1),
+ FUSE_OPT_END
+ };
+
+ if (fuse_opt_parse(args, &conf, opts,
+ fuse_lib_opt_proc) == -1)
+ return NULL;
+
+ if (conf.show_help) {
+ fuse_lib_help(args);
+ return NULL;
+ } else
+ return fuse_new_31(args, op, op_size, user_data);
+}
+
+void fuse_destroy(struct fuse *f)
+{
+ size_t i;
+
+ if (f->conf.intr && f->intr_installed)
+ fuse_restore_intr_signal(f->conf.intr_signal);
+
+ if (f->fs) {
+ fuse_create_context(f);
+
+ for (i = 0; i < f->id_table.size; i++) {
+ struct node *node;
+
+ for (node = f->id_table.array[i]; node != NULL;
+ node = node->id_next) {
+ if (node->is_hidden) {
+ char *path;
+ if (try_get_path(f, node->nodeid, NULL, &path, NULL, false) == 0) {
+ fuse_fs_unlink(f->fs, path);
+ free(path);
+ }
+ }
+ }
+ }
+ }
+ for (i = 0; i < f->id_table.size; i++) {
+ struct node *node;
+ struct node *next;
+
+ for (node = f->id_table.array[i]; node != NULL; node = next) {
+ next = node->id_next;
+ free_node(f, node);
+ f->id_table.use--;
+ }
+ }
+ assert(list_empty(&f->partial_slabs));
+ assert(list_empty(&f->full_slabs));
+
+ while (fuse_modules) {
+ fuse_put_module(fuse_modules);
+ }
+ free(f->id_table.array);
+ free(f->name_table.array);
+ pthread_mutex_destroy(&f->lock);
+ fuse_session_destroy(f->se);
+ free(f->conf.modules);
+ free(f);
+ fuse_delete_context_key();
+}
+
+int fuse_mount(struct fuse *f, const char *mountpoint) {
+ return fuse_session_mount(fuse_get_session(f), mountpoint);
+}
+
+
+void fuse_unmount(struct fuse *f) {
+ fuse_session_unmount(fuse_get_session(f));
+}
+
+int fuse_version(void)
+{
+ return FUSE_VERSION;
+}
+
+const char *fuse_pkgversion(void)
+{
+ return PACKAGE_VERSION;
+}
diff --git a/lib/fuse_i.h b/lib/fuse_i.h
new file mode 100644
index 0000000..d38b630
--- /dev/null
+++ b/lib/fuse_i.h
@@ -0,0 +1,139 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#include "fuse.h"
+#include "fuse_lowlevel.h"
+
+struct mount_opts;
+
+struct fuse_req {
+ struct fuse_session *se;
+ uint64_t unique;
+ int ctr;
+ pthread_mutex_t lock;
+ struct fuse_ctx ctx;
+ struct fuse_chan *ch;
+ int interrupted;
+ unsigned int ioctl_64bit : 1;
+ union {
+ struct {
+ uint64_t unique;
+ } i;
+ struct {
+ fuse_interrupt_func_t func;
+ void *data;
+ } ni;
+ } u;
+ struct fuse_req *next;
+ struct fuse_req *prev;
+};
+
+struct fuse_notify_req {
+ uint64_t unique;
+ void (*reply)(struct fuse_notify_req *, fuse_req_t, fuse_ino_t,
+ const void *, const struct fuse_buf *);
+ struct fuse_notify_req *next;
+ struct fuse_notify_req *prev;
+};
+
+struct fuse_session {
+ char *mountpoint;
+ volatile int exited;
+ int fd;
+ struct mount_opts *mo;
+ int debug;
+ int deny_others;
+ struct fuse_lowlevel_ops op;
+ int got_init;
+ struct cuse_data *cuse_data;
+ void *userdata;
+ uid_t owner;
+ struct fuse_conn_info conn;
+ struct fuse_req list;
+ struct fuse_req interrupts;
+ pthread_mutex_t lock;
+ int got_destroy;
+ pthread_key_t pipe_key;
+ int broken_splice_nonblock;
+ uint64_t notify_ctr;
+ struct fuse_notify_req notify_list;
+ size_t bufsize;
+ int error;
+};
+
+struct fuse_chan {
+ pthread_mutex_t lock;
+ int ctr;
+ int fd;
+};
+
+/**
+ * Filesystem module
+ *
+ * Filesystem modules are registered with the FUSE_REGISTER_MODULE()
+ * macro.
+ *
+ */
+struct fuse_module {
+ char *name;
+ fuse_module_factory_t factory;
+ struct fuse_module *next;
+ struct fusemod_so *so;
+ int ctr;
+};
+
+/* ----------------------------------------------------------- *
+ * Channel interface (when using -o clone_fd) *
+ * ----------------------------------------------------------- */
+
+/**
+ * Obtain counted reference to the channel
+ *
+ * @param ch the channel
+ * @return the channel
+ */
+struct fuse_chan *fuse_chan_get(struct fuse_chan *ch);
+
+/**
+ * Drop counted reference to a channel
+ *
+ * @param ch the channel
+ */
+void fuse_chan_put(struct fuse_chan *ch);
+
+struct mount_opts *parse_mount_opts(struct fuse_args *args);
+void destroy_mount_opts(struct mount_opts *mo);
+void fuse_mount_version(void);
+unsigned get_max_read(struct mount_opts *o);
+void fuse_kern_unmount(const char *mountpoint, int fd);
+int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo);
+
+int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+ int count);
+void fuse_free_req(fuse_req_t req);
+
+void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeide, const void *inarg);
+
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg);
+
+int fuse_session_receive_buf_int(struct fuse_session *se, struct fuse_buf *buf,
+ struct fuse_chan *ch);
+void fuse_session_process_buf_int(struct fuse_session *se,
+ const struct fuse_buf *buf, struct fuse_chan *ch);
+
+struct fuse *fuse_new_31(struct fuse_args *args, const struct fuse_operations *op,
+ size_t op_size, void *private_data);
+int fuse_loop_mt_32(struct fuse *f, struct fuse_loop_config *config);
+int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config);
+
+#define FUSE_MAX_MAX_PAGES 256
+#define FUSE_DEFAULT_MAX_PAGES_PER_REQ 32
+
+/* room needed in buffer to accommodate header */
+#define FUSE_BUFFER_HEADER_SIZE 0x1000
+
diff --git a/lib/fuse_loop.c b/lib/fuse_loop.c
new file mode 100644
index 0000000..e6560aa
--- /dev/null
+++ b/lib/fuse_loop.c
@@ -0,0 +1,46 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Implementation of the single-threaded FUSE session loop.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#include "config.h"
+#include "fuse_lowlevel.h"
+#include "fuse_i.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+int fuse_session_loop(struct fuse_session *se)
+{
+ int res = 0;
+ struct fuse_buf fbuf = {
+ .mem = NULL,
+ };
+
+ while (!fuse_session_exited(se)) {
+ res = fuse_session_receive_buf_int(se, &fbuf, NULL);
+
+ if (res == -EINTR)
+ continue;
+ if (res <= 0)
+ break;
+
+ fuse_session_process_buf_int(se, &fbuf, NULL);
+ }
+
+ free(fbuf.mem);
+ if(res > 0)
+ /* No error, just the length of the most recently read
+ request */
+ res = 0;
+ if(se->error != 0)
+ res = se->error;
+ fuse_session_reset(se);
+ return res;
+}
diff --git a/lib/fuse_loop_mt.c b/lib/fuse_loop_mt.c
new file mode 100644
index 0000000..83f1641
--- /dev/null
+++ b/lib/fuse_loop_mt.c
@@ -0,0 +1,362 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Implementation of the multi-threaded FUSE session loop.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "fuse_lowlevel.h"
+#include "fuse_misc.h"
+#include "fuse_kernel.h"
+#include "fuse_i.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <semaphore.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <assert.h>
+
+/* Environment var controlling the thread stack size */
+#define ENVNAME_THREAD_STACK "FUSE_THREAD_STACK"
+
+struct fuse_worker {
+ struct fuse_worker *prev;
+ struct fuse_worker *next;
+ pthread_t thread_id;
+ size_t bufsize;
+
+ // We need to include fuse_buf so that we can properly free
+ // it when a thread is terminated by pthread_cancel().
+ struct fuse_buf fbuf;
+ struct fuse_chan *ch;
+ struct fuse_mt *mt;
+};
+
+struct fuse_mt {
+ pthread_mutex_t lock;
+ int numworker;
+ int numavail;
+ struct fuse_session *se;
+ struct fuse_worker main;
+ sem_t finish;
+ int exit;
+ int error;
+ int clone_fd;
+ int max_idle;
+};
+
+static struct fuse_chan *fuse_chan_new(int fd)
+{
+ struct fuse_chan *ch = (struct fuse_chan *) malloc(sizeof(*ch));
+ if (ch == NULL) {
+ fprintf(stderr, "fuse: failed to allocate channel\n");
+ return NULL;
+ }
+
+ memset(ch, 0, sizeof(*ch));
+ ch->fd = fd;
+ ch->ctr = 1;
+ fuse_mutex_init(&ch->lock);
+
+ return ch;
+}
+
+struct fuse_chan *fuse_chan_get(struct fuse_chan *ch)
+{
+ assert(ch->ctr > 0);
+ pthread_mutex_lock(&ch->lock);
+ ch->ctr++;
+ pthread_mutex_unlock(&ch->lock);
+
+ return ch;
+}
+
+void fuse_chan_put(struct fuse_chan *ch)
+{
+ if (ch == NULL)
+ return;
+ pthread_mutex_lock(&ch->lock);
+ ch->ctr--;
+ if (!ch->ctr) {
+ pthread_mutex_unlock(&ch->lock);
+ close(ch->fd);
+ pthread_mutex_destroy(&ch->lock);
+ free(ch);
+ } else
+ pthread_mutex_unlock(&ch->lock);
+}
+
+static void list_add_worker(struct fuse_worker *w, struct fuse_worker *next)
+{
+ struct fuse_worker *prev = next->prev;
+ w->next = next;
+ w->prev = prev;
+ prev->next = w;
+ next->prev = w;
+}
+
+static void list_del_worker(struct fuse_worker *w)
+{
+ struct fuse_worker *prev = w->prev;
+ struct fuse_worker *next = w->next;
+ prev->next = next;
+ next->prev = prev;
+}
+
+static int fuse_loop_start_thread(struct fuse_mt *mt);
+
+static void *fuse_do_work(void *data)
+{
+ struct fuse_worker *w = (struct fuse_worker *) data;
+ struct fuse_mt *mt = w->mt;
+
+ while (!fuse_session_exited(mt->se)) {
+ int isforget = 0;
+ int res;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+ res = fuse_session_receive_buf_int(mt->se, &w->fbuf, w->ch);
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
+ if (res == -EINTR)
+ continue;
+ if (res <= 0) {
+ if (res < 0) {
+ fuse_session_exit(mt->se);
+ mt->error = res;
+ }
+ break;
+ }
+
+ pthread_mutex_lock(&mt->lock);
+ if (mt->exit) {
+ pthread_mutex_unlock(&mt->lock);
+ return NULL;
+ }
+
+ /*
+ * This disgusting hack is needed so that zillions of threads
+ * are not created on a burst of FORGET messages
+ */
+ if (!(w->fbuf.flags & FUSE_BUF_IS_FD)) {
+ struct fuse_in_header *in = w->fbuf.mem;
+
+ if (in->opcode == FUSE_FORGET ||
+ in->opcode == FUSE_BATCH_FORGET)
+ isforget = 1;
+ }
+
+ if (!isforget)
+ mt->numavail--;
+ if (mt->numavail == 0)
+ fuse_loop_start_thread(mt);
+ pthread_mutex_unlock(&mt->lock);
+
+ fuse_session_process_buf_int(mt->se, &w->fbuf, w->ch);
+
+ pthread_mutex_lock(&mt->lock);
+ if (!isforget)
+ mt->numavail++;
+ if (mt->numavail > mt->max_idle) {
+ if (mt->exit) {
+ pthread_mutex_unlock(&mt->lock);
+ return NULL;
+ }
+ list_del_worker(w);
+ mt->numavail--;
+ mt->numworker--;
+ pthread_mutex_unlock(&mt->lock);
+
+ pthread_detach(w->thread_id);
+ free(w->fbuf.mem);
+ fuse_chan_put(w->ch);
+ free(w);
+ return NULL;
+ }
+ pthread_mutex_unlock(&mt->lock);
+ }
+
+ sem_post(&mt->finish);
+
+ return NULL;
+}
+
+int fuse_start_thread(pthread_t *thread_id, void *(*func)(void *), void *arg)
+{
+ sigset_t oldset;
+ sigset_t newset;
+ int res;
+ pthread_attr_t attr;
+ char *stack_size;
+
+ /* Override default stack size */
+ pthread_attr_init(&attr);
+ stack_size = getenv(ENVNAME_THREAD_STACK);
+ if (stack_size && pthread_attr_setstacksize(&attr, atoi(stack_size)))
+ fprintf(stderr, "fuse: invalid stack size: %s\n", stack_size);
+
+ /* Disallow signal reception in worker threads */
+ sigemptyset(&newset);
+ sigaddset(&newset, SIGTERM);
+ sigaddset(&newset, SIGINT);
+ sigaddset(&newset, SIGHUP);
+ sigaddset(&newset, SIGQUIT);
+ pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+ res = pthread_create(thread_id, &attr, func, arg);
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ pthread_attr_destroy(&attr);
+ if (res != 0) {
+ fprintf(stderr, "fuse: error creating thread: %s\n",
+ strerror(res));
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct fuse_chan *fuse_clone_chan(struct fuse_mt *mt)
+{
+ int res;
+ int clonefd;
+ uint32_t masterfd;
+ struct fuse_chan *newch;
+ const char *devname = "/dev/fuse";
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+ clonefd = open(devname, O_RDWR | O_CLOEXEC);
+ if (clonefd == -1) {
+ fprintf(stderr, "fuse: failed to open %s: %s\n", devname,
+ strerror(errno));
+ return NULL;
+ }
+ fcntl(clonefd, F_SETFD, FD_CLOEXEC);
+
+ masterfd = mt->se->fd;
+ res = ioctl(clonefd, FUSE_DEV_IOC_CLONE, &masterfd);
+ if (res == -1) {
+ fprintf(stderr, "fuse: failed to clone device fd: %s\n",
+ strerror(errno));
+ close(clonefd);
+ return NULL;
+ }
+ newch = fuse_chan_new(clonefd);
+ if (newch == NULL)
+ close(clonefd);
+
+ return newch;
+}
+
+static int fuse_loop_start_thread(struct fuse_mt *mt)
+{
+ int res;
+
+ struct fuse_worker *w = malloc(sizeof(struct fuse_worker));
+ if (!w) {
+ fprintf(stderr, "fuse: failed to allocate worker structure\n");
+ return -1;
+ }
+ memset(w, 0, sizeof(struct fuse_worker));
+ w->fbuf.mem = NULL;
+ w->mt = mt;
+
+ w->ch = NULL;
+ if (mt->clone_fd) {
+ w->ch = fuse_clone_chan(mt);
+ if(!w->ch) {
+ /* Don't attempt this again */
+ fprintf(stderr, "fuse: trying to continue "
+ "without -o clone_fd.\n");
+ mt->clone_fd = 0;
+ }
+ }
+
+ res = fuse_start_thread(&w->thread_id, fuse_do_work, w);
+ if (res == -1) {
+ fuse_chan_put(w->ch);
+ free(w);
+ return -1;
+ }
+ list_add_worker(w, &mt->main);
+ mt->numavail ++;
+ mt->numworker ++;
+
+ return 0;
+}
+
+static void fuse_join_worker(struct fuse_mt *mt, struct fuse_worker *w)
+{
+ pthread_join(w->thread_id, NULL);
+ pthread_mutex_lock(&mt->lock);
+ list_del_worker(w);
+ pthread_mutex_unlock(&mt->lock);
+ free(w->fbuf.mem);
+ fuse_chan_put(w->ch);
+ free(w);
+}
+
+FUSE_SYMVER(".symver fuse_session_loop_mt_32,fuse_session_loop_mt@@FUSE_3.2");
+int fuse_session_loop_mt_32(struct fuse_session *se, struct fuse_loop_config *config)
+{
+ int err;
+ struct fuse_mt mt;
+ struct fuse_worker *w;
+
+ memset(&mt, 0, sizeof(struct fuse_mt));
+ mt.se = se;
+ mt.clone_fd = config->clone_fd;
+ mt.error = 0;
+ mt.numworker = 0;
+ mt.numavail = 0;
+ mt.max_idle = config->max_idle_threads;
+ mt.main.thread_id = pthread_self();
+ mt.main.prev = mt.main.next = &mt.main;
+ sem_init(&mt.finish, 0, 0);
+ fuse_mutex_init(&mt.lock);
+
+ pthread_mutex_lock(&mt.lock);
+ err = fuse_loop_start_thread(&mt);
+ pthread_mutex_unlock(&mt.lock);
+ if (!err) {
+ /* sem_wait() is interruptible */
+ while (!fuse_session_exited(se))
+ sem_wait(&mt.finish);
+
+ pthread_mutex_lock(&mt.lock);
+ for (w = mt.main.next; w != &mt.main; w = w->next)
+ pthread_cancel(w->thread_id);
+ mt.exit = 1;
+ pthread_mutex_unlock(&mt.lock);
+
+ while (mt.main.next != &mt.main)
+ fuse_join_worker(&mt, mt.main.next);
+
+ err = mt.error;
+ }
+
+ pthread_mutex_destroy(&mt.lock);
+ sem_destroy(&mt.finish);
+ if(se->error != 0)
+ err = se->error;
+ fuse_session_reset(se);
+ return err;
+}
+
+int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd);
+FUSE_SYMVER(".symver fuse_session_loop_mt_31,fuse_session_loop_mt@FUSE_3.0");
+int fuse_session_loop_mt_31(struct fuse_session *se, int clone_fd)
+{
+ struct fuse_loop_config config;
+ config.clone_fd = clone_fd;
+ config.max_idle_threads = 10;
+ return fuse_session_loop_mt_32(se, &config);
+}
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
new file mode 100644
index 0000000..3684b8b
--- /dev/null
+++ b/lib/fuse_lowlevel.c
@@ -0,0 +1,3069 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Implementation of (most of) the low-level FUSE API. The session loop
+ functions are implemented in separate files.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#define _GNU_SOURCE
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_kernel.h"
+#include "fuse_opt.h"
+#include "fuse_misc.h"
+#include "mount_util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/file.h>
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+#ifndef F_SETPIPE_SZ
+#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7)
+#endif
+
+
+#define PARAM(inarg) (((char *)(inarg)) + sizeof(*(inarg)))
+#define OFFSET_MAX 0x7fffffffffffffffLL
+
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+struct fuse_pollhandle {
+ uint64_t kh;
+ struct fuse_session *se;
+};
+
+static size_t pagesize;
+
+static __attribute__((constructor)) void fuse_ll_init_pagesize(void)
+{
+ pagesize = getpagesize();
+}
+
+static void convert_stat(const struct stat *stbuf, struct fuse_attr *attr)
+{
+ attr->ino = stbuf->st_ino;
+ attr->mode = stbuf->st_mode;
+ attr->nlink = stbuf->st_nlink;
+ attr->uid = stbuf->st_uid;
+ attr->gid = stbuf->st_gid;
+ attr->rdev = stbuf->st_rdev;
+ attr->size = stbuf->st_size;
+ attr->blksize = stbuf->st_blksize;
+ attr->blocks = stbuf->st_blocks;
+ attr->atime = stbuf->st_atime;
+ attr->mtime = stbuf->st_mtime;
+ attr->ctime = stbuf->st_ctime;
+ attr->atimensec = ST_ATIM_NSEC(stbuf);
+ attr->mtimensec = ST_MTIM_NSEC(stbuf);
+ attr->ctimensec = ST_CTIM_NSEC(stbuf);
+}
+
+static void convert_attr(const struct fuse_setattr_in *attr, struct stat *stbuf)
+{
+ stbuf->st_mode = attr->mode;
+ stbuf->st_uid = attr->uid;
+ stbuf->st_gid = attr->gid;
+ stbuf->st_size = attr->size;
+ stbuf->st_atime = attr->atime;
+ stbuf->st_mtime = attr->mtime;
+ stbuf->st_ctime = attr->ctime;
+ ST_ATIM_NSEC_SET(stbuf, attr->atimensec);
+ ST_MTIM_NSEC_SET(stbuf, attr->mtimensec);
+ ST_CTIM_NSEC_SET(stbuf, attr->ctimensec);
+}
+
+static size_t iov_length(const struct iovec *iov, size_t count)
+{
+ size_t seg;
+ size_t ret = 0;
+
+ for (seg = 0; seg < count; seg++)
+ ret += iov[seg].iov_len;
+ return ret;
+}
+
+static void list_init_req(struct fuse_req *req)
+{
+ req->next = req;
+ req->prev = req;
+}
+
+static void list_del_req(struct fuse_req *req)
+{
+ struct fuse_req *prev = req->prev;
+ struct fuse_req *next = req->next;
+ prev->next = next;
+ next->prev = prev;
+}
+
+static void list_add_req(struct fuse_req *req, struct fuse_req *next)
+{
+ struct fuse_req *prev = next->prev;
+ req->next = next;
+ req->prev = prev;
+ prev->next = req;
+ next->prev = req;
+}
+
+static void destroy_req(fuse_req_t req)
+{
+ pthread_mutex_destroy(&req->lock);
+ free(req);
+}
+
+void fuse_free_req(fuse_req_t req)
+{
+ int ctr;
+ struct fuse_session *se = req->se;
+
+ pthread_mutex_lock(&se->lock);
+ req->u.ni.func = NULL;
+ req->u.ni.data = NULL;
+ list_del_req(req);
+ ctr = --req->ctr;
+ fuse_chan_put(req->ch);
+ req->ch = NULL;
+ pthread_mutex_unlock(&se->lock);
+ if (!ctr)
+ destroy_req(req);
+}
+
+static struct fuse_req *fuse_ll_alloc_req(struct fuse_session *se)
+{
+ struct fuse_req *req;
+
+ req = (struct fuse_req *) calloc(1, sizeof(struct fuse_req));
+ if (req == NULL) {
+ fprintf(stderr, "fuse: failed to allocate request\n");
+ } else {
+ req->se = se;
+ req->ctr = 1;
+ list_init_req(req);
+ fuse_mutex_init(&req->lock);
+ }
+
+ return req;
+}
+
+/* Send data. If *ch* is NULL, send via session master fd */
+static int fuse_send_msg(struct fuse_session *se, struct fuse_chan *ch,
+ struct iovec *iov, int count)
+{
+ struct fuse_out_header *out = iov[0].iov_base;
+
+ out->len = iov_length(iov, count);
+ if (se->debug) {
+ if (out->unique == 0) {
+ fprintf(stderr, "NOTIFY: code=%d length=%u\n",
+ out->error, out->len);
+ } else if (out->error) {
+ fprintf(stderr,
+ " unique: %llu, error: %i (%s), outsize: %i\n",
+ (unsigned long long) out->unique, out->error,
+ strerror(-out->error), out->len);
+ } else {
+ fprintf(stderr,
+ " unique: %llu, success, outsize: %i\n",
+ (unsigned long long) out->unique, out->len);
+ }
+ }
+
+ ssize_t res = writev(ch ? ch->fd : se->fd,
+ iov, count);
+ int err = errno;
+
+ if (res == -1) {
+ assert(se != NULL);
+
+ /* ENOENT means the operation was interrupted */
+ if (!fuse_session_exited(se) && err != ENOENT)
+ perror("fuse: writing device");
+ return -err;
+ }
+
+ return 0;
+}
+
+
+int fuse_send_reply_iov_nofree(fuse_req_t req, int error, struct iovec *iov,
+ int count)
+{
+ struct fuse_out_header out;
+
+ if (error <= -1000 || error > 0) {
+ fprintf(stderr, "fuse: bad error value: %i\n", error);
+ error = -ERANGE;
+ }
+
+ out.unique = req->unique;
+ out.error = error;
+
+ iov[0].iov_base = &out;
+ iov[0].iov_len = sizeof(struct fuse_out_header);
+
+ return fuse_send_msg(req->se, req->ch, iov, count);
+}
+
+static int send_reply_iov(fuse_req_t req, int error, struct iovec *iov,
+ int count)
+{
+ int res;
+
+ res = fuse_send_reply_iov_nofree(req, error, iov, count);
+ fuse_free_req(req);
+ return res;
+}
+
+static int send_reply(fuse_req_t req, int error, const void *arg,
+ size_t argsize)
+{
+ struct iovec iov[2];
+ int count = 1;
+ if (argsize) {
+ iov[1].iov_base = (void *) arg;
+ iov[1].iov_len = argsize;
+ count++;
+ }
+ return send_reply_iov(req, error, iov, count);
+}
+
+int fuse_reply_iov(fuse_req_t req, const struct iovec *iov, int count)
+{
+ int res;
+ struct iovec *padded_iov;
+
+ padded_iov = malloc((count + 1) * sizeof(struct iovec));
+ if (padded_iov == NULL)
+ return fuse_reply_err(req, ENOMEM);
+
+ memcpy(padded_iov + 1, iov, count * sizeof(struct iovec));
+ count++;
+
+ res = send_reply_iov(req, 0, padded_iov, count);
+ free(padded_iov);
+
+ return res;
+}
+
+
+/* `buf` is allowed to be empty so that the proper size may be
+ allocated by the caller */
+size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize,
+ const char *name, const struct stat *stbuf, off_t off)
+{
+ (void)req;
+ size_t namelen;
+ size_t entlen;
+ size_t entlen_padded;
+ struct fuse_dirent *dirent;
+
+ namelen = strlen(name);
+ entlen = FUSE_NAME_OFFSET + namelen;
+ entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+
+ if ((buf == NULL) || (entlen_padded > bufsize))
+ return entlen_padded;
+
+ dirent = (struct fuse_dirent*) buf;
+ dirent->ino = stbuf->st_ino;
+ dirent->off = off;
+ dirent->namelen = namelen;
+ dirent->type = (stbuf->st_mode & S_IFMT) >> 12;
+ strncpy(dirent->name, name, namelen);
+ memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
+ return entlen_padded;
+}
+
+static void convert_statfs(const struct statvfs *stbuf,
+ struct fuse_kstatfs *kstatfs)
+{
+ kstatfs->bsize = stbuf->f_bsize;
+ kstatfs->frsize = stbuf->f_frsize;
+ kstatfs->blocks = stbuf->f_blocks;
+ kstatfs->bfree = stbuf->f_bfree;
+ kstatfs->bavail = stbuf->f_bavail;
+ kstatfs->files = stbuf->f_files;
+ kstatfs->ffree = stbuf->f_ffree;
+ kstatfs->namelen = stbuf->f_namemax;
+}
+
+static int send_reply_ok(fuse_req_t req, const void *arg, size_t argsize)
+{
+ return send_reply(req, 0, arg, argsize);
+}
+
+int fuse_reply_err(fuse_req_t req, int err)
+{
+ return send_reply(req, -err, NULL, 0);
+}
+
+void fuse_reply_none(fuse_req_t req)
+{
+ fuse_free_req(req);
+}
+
+static unsigned long calc_timeout_sec(double t)
+{
+ if (t > (double) ULONG_MAX)
+ return ULONG_MAX;
+ else if (t < 0.0)
+ return 0;
+ else
+ return (unsigned long) t;
+}
+
+static unsigned int calc_timeout_nsec(double t)
+{
+ double f = t - (double) calc_timeout_sec(t);
+ if (f < 0.0)
+ return 0;
+ else if (f >= 0.999999999)
+ return 999999999;
+ else
+ return (unsigned int) (f * 1.0e9);
+}
+
+static void fill_entry(struct fuse_entry_out *arg,
+ const struct fuse_entry_param *e)
+{
+ arg->nodeid = e->ino;
+ arg->generation = e->generation;
+ arg->entry_valid = calc_timeout_sec(e->entry_timeout);
+ arg->entry_valid_nsec = calc_timeout_nsec(e->entry_timeout);
+ arg->attr_valid = calc_timeout_sec(e->attr_timeout);
+ arg->attr_valid_nsec = calc_timeout_nsec(e->attr_timeout);
+ convert_stat(&e->attr, &arg->attr);
+}
+
+/* `buf` is allowed to be empty so that the proper size may be
+ allocated by the caller */
+size_t fuse_add_direntry_plus(fuse_req_t req, char *buf, size_t bufsize,
+ const char *name,
+ const struct fuse_entry_param *e, off_t off)
+{
+ (void)req;
+ size_t namelen;
+ size_t entlen;
+ size_t entlen_padded;
+
+ namelen = strlen(name);
+ entlen = FUSE_NAME_OFFSET_DIRENTPLUS + namelen;
+ entlen_padded = FUSE_DIRENT_ALIGN(entlen);
+ if ((buf == NULL) || (entlen_padded > bufsize))
+ return entlen_padded;
+
+ struct fuse_direntplus *dp = (struct fuse_direntplus *) buf;
+ memset(&dp->entry_out, 0, sizeof(dp->entry_out));
+ fill_entry(&dp->entry_out, e);
+
+ struct fuse_dirent *dirent = &dp->dirent;
+ dirent->ino = e->attr.st_ino;
+ dirent->off = off;
+ dirent->namelen = namelen;
+ dirent->type = (e->attr.st_mode & S_IFMT) >> 12;
+ strncpy(dirent->name, name, namelen);
+ memset(dirent->name + namelen, 0, entlen_padded - entlen);
+
+ return entlen_padded;
+}
+
+static void fill_open(struct fuse_open_out *arg,
+ const struct fuse_file_info *f)
+{
+ arg->fh = f->fh;
+ if (f->direct_io)
+ arg->open_flags |= FOPEN_DIRECT_IO;
+ if (f->keep_cache)
+ arg->open_flags |= FOPEN_KEEP_CACHE;
+ if (f->cache_readdir)
+ arg->open_flags |= FOPEN_CACHE_DIR;
+ if (f->nonseekable)
+ arg->open_flags |= FOPEN_NONSEEKABLE;
+}
+
+int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
+{
+ struct fuse_entry_out arg;
+ size_t size = req->se->conn.proto_minor < 9 ?
+ FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(arg);
+
+ /* before ABI 7.4 e->ino == 0 was invalid, only ENOENT meant
+ negative entry */
+ if (!e->ino && req->se->conn.proto_minor < 4)
+ return fuse_reply_err(req, ENOENT);
+
+ memset(&arg, 0, sizeof(arg));
+ fill_entry(&arg, e);
+ return send_reply_ok(req, &arg, size);
+}
+
+int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param *e,
+ const struct fuse_file_info *f)
+{
+ char buf[sizeof(struct fuse_entry_out) + sizeof(struct fuse_open_out)];
+ size_t entrysize = req->se->conn.proto_minor < 9 ?
+ FUSE_COMPAT_ENTRY_OUT_SIZE : sizeof(struct fuse_entry_out);
+ struct fuse_entry_out *earg = (struct fuse_entry_out *) buf;
+ struct fuse_open_out *oarg = (struct fuse_open_out *) (buf + entrysize);
+
+ memset(buf, 0, sizeof(buf));
+ fill_entry(earg, e);
+ fill_open(oarg, f);
+ return send_reply_ok(req, buf,
+ entrysize + sizeof(struct fuse_open_out));
+}
+
+int fuse_reply_attr(fuse_req_t req, const struct stat *attr,
+ double attr_timeout)
+{
+ struct fuse_attr_out arg;
+ size_t size = req->se->conn.proto_minor < 9 ?
+ FUSE_COMPAT_ATTR_OUT_SIZE : sizeof(arg);
+
+ memset(&arg, 0, sizeof(arg));
+ arg.attr_valid = calc_timeout_sec(attr_timeout);
+ arg.attr_valid_nsec = calc_timeout_nsec(attr_timeout);
+ convert_stat(attr, &arg.attr);
+
+ return send_reply_ok(req, &arg, size);
+}
+
+int fuse_reply_readlink(fuse_req_t req, const char *linkname)
+{
+ return send_reply_ok(req, linkname, strlen(linkname));
+}
+
+int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *f)
+{
+ struct fuse_open_out arg;
+
+ memset(&arg, 0, sizeof(arg));
+ fill_open(&arg, f);
+ return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_write(fuse_req_t req, size_t count)
+{
+ struct fuse_write_out arg;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.size = count;
+
+ return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
+{
+ return send_reply_ok(req, buf, size);
+}
+
+static int fuse_send_data_iov_fallback(struct fuse_session *se,
+ struct fuse_chan *ch,
+ struct iovec *iov, int iov_count,
+ struct fuse_bufvec *buf,
+ size_t len)
+{
+ struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+ void *mbuf;
+ int res;
+
+ /* Optimize common case */
+ if (buf->count == 1 && buf->idx == 0 && buf->off == 0 &&
+ !(buf->buf[0].flags & FUSE_BUF_IS_FD)) {
+ /* FIXME: also avoid memory copy if there are multiple buffers
+ but none of them contain an fd */
+
+ iov[iov_count].iov_base = buf->buf[0].mem;
+ iov[iov_count].iov_len = len;
+ iov_count++;
+ return fuse_send_msg(se, ch, iov, iov_count);
+ }
+
+ res = posix_memalign(&mbuf, pagesize, len);
+ if (res != 0)
+ return res;
+
+ mem_buf.buf[0].mem = mbuf;
+ res = fuse_buf_copy(&mem_buf, buf, 0);
+ if (res < 0) {
+ free(mbuf);
+ return -res;
+ }
+ len = res;
+
+ iov[iov_count].iov_base = mbuf;
+ iov[iov_count].iov_len = len;
+ iov_count++;
+ res = fuse_send_msg(se, ch, iov, iov_count);
+ free(mbuf);
+
+ return res;
+}
+
+struct fuse_ll_pipe {
+ size_t size;
+ int can_grow;
+ int pipe[2];
+};
+
+static void fuse_ll_pipe_free(struct fuse_ll_pipe *llp)
+{
+ close(llp->pipe[0]);
+ close(llp->pipe[1]);
+ free(llp);
+}
+
+#ifdef HAVE_SPLICE
+#if !defined(HAVE_PIPE2) || !defined(O_CLOEXEC)
+static int fuse_pipe(int fds[2])
+{
+ int rv = pipe(fds);
+
+ if (rv == -1)
+ return rv;
+
+ if (fcntl(fds[0], F_SETFL, O_NONBLOCK) == -1 ||
+ fcntl(fds[1], F_SETFL, O_NONBLOCK) == -1 ||
+ fcntl(fds[0], F_SETFD, FD_CLOEXEC) == -1 ||
+ fcntl(fds[1], F_SETFD, FD_CLOEXEC) == -1) {
+ close(fds[0]);
+ close(fds[1]);
+ rv = -1;
+ }
+ return rv;
+}
+#else
+static int fuse_pipe(int fds[2])
+{
+ return pipe2(fds, O_CLOEXEC | O_NONBLOCK);
+}
+#endif
+
+static struct fuse_ll_pipe *fuse_ll_get_pipe(struct fuse_session *se)
+{
+ struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+ if (llp == NULL) {
+ int res;
+
+ llp = malloc(sizeof(struct fuse_ll_pipe));
+ if (llp == NULL)
+ return NULL;
+
+ res = fuse_pipe(llp->pipe);
+ if (res == -1) {
+ free(llp);
+ return NULL;
+ }
+
+ /*
+ *the default size is 16 pages on linux
+ */
+ llp->size = pagesize * 16;
+ llp->can_grow = 1;
+
+ pthread_setspecific(se->pipe_key, llp);
+ }
+
+ return llp;
+}
+#endif
+
+static void fuse_ll_clear_pipe(struct fuse_session *se)
+{
+ struct fuse_ll_pipe *llp = pthread_getspecific(se->pipe_key);
+ if (llp) {
+ pthread_setspecific(se->pipe_key, NULL);
+ fuse_ll_pipe_free(llp);
+ }
+}
+
+#if defined(HAVE_SPLICE) && defined(HAVE_VMSPLICE)
+static int read_back(int fd, char *buf, size_t len)
+{
+ int res;
+
+ res = read(fd, buf, len);
+ if (res == -1) {
+ fprintf(stderr, "fuse: internal error: failed to read back from pipe: %s\n", strerror(errno));
+ return -EIO;
+ }
+ if (res != len) {
+ fprintf(stderr, "fuse: internal error: short read back from pipe: %i from %zi\n", res, len);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+ struct iovec *iov, int iov_count,
+ struct fuse_bufvec *buf, unsigned int flags)
+{
+ int res;
+ size_t len = fuse_buf_size(buf);
+ struct fuse_out_header *out = iov[0].iov_base;
+ struct fuse_ll_pipe *llp;
+ int splice_flags;
+ size_t pipesize;
+ size_t total_fd_size;
+ size_t idx;
+ size_t headerlen;
+ struct fuse_bufvec pipe_buf = FUSE_BUFVEC_INIT(len);
+
+ if (se->broken_splice_nonblock)
+ goto fallback;
+
+ if (flags & FUSE_BUF_NO_SPLICE)
+ goto fallback;
+
+ total_fd_size = 0;
+ for (idx = buf->idx; idx < buf->count; idx++) {
+ if (buf->buf[idx].flags & FUSE_BUF_IS_FD) {
+ total_fd_size = buf->buf[idx].size;
+ if (idx == buf->idx)
+ total_fd_size -= buf->off;
+ }
+ }
+ if (total_fd_size < 2 * pagesize)
+ goto fallback;
+
+ if (se->conn.proto_minor < 14 ||
+ !(se->conn.want & FUSE_CAP_SPLICE_WRITE))
+ goto fallback;
+
+ llp = fuse_ll_get_pipe(se);
+ if (llp == NULL)
+ goto fallback;
+
+
+ headerlen = iov_length(iov, iov_count);
+
+ out->len = headerlen + len;
+
+ /*
+ * Heuristic for the required pipe size, does not work if the
+ * source contains less than page size fragments
+ */
+ pipesize = pagesize * (iov_count + buf->count + 1) + out->len;
+
+ if (llp->size < pipesize) {
+ if (llp->can_grow) {
+ res = fcntl(llp->pipe[0], F_SETPIPE_SZ, pipesize);
+ if (res == -1) {
+ llp->can_grow = 0;
+ goto fallback;
+ }
+ llp->size = res;
+ }
+ if (llp->size < pipesize)
+ goto fallback;
+ }
+
+
+ res = vmsplice(llp->pipe[1], iov, iov_count, SPLICE_F_NONBLOCK);
+ if (res == -1)
+ goto fallback;
+
+ if (res != headerlen) {
+ res = -EIO;
+ fprintf(stderr, "fuse: short vmsplice to pipe: %u/%zu\n", res,
+ headerlen);
+ goto clear_pipe;
+ }
+
+ pipe_buf.buf[0].flags = FUSE_BUF_IS_FD;
+ pipe_buf.buf[0].fd = llp->pipe[1];
+
+ res = fuse_buf_copy(&pipe_buf, buf,
+ FUSE_BUF_FORCE_SPLICE | FUSE_BUF_SPLICE_NONBLOCK);
+ if (res < 0) {
+ if (res == -EAGAIN || res == -EINVAL) {
+ /*
+ * Should only get EAGAIN on kernels with
+ * broken SPLICE_F_NONBLOCK support (<=
+ * 2.6.35) where this error or a short read is
+ * returned even if the pipe itself is not
+ * full
+ *
+ * EINVAL might mean that splice can't handle
+ * this combination of input and output.
+ */
+ if (res == -EAGAIN)
+ se->broken_splice_nonblock = 1;
+
+ pthread_setspecific(se->pipe_key, NULL);
+ fuse_ll_pipe_free(llp);
+ goto fallback;
+ }
+ res = -res;
+ goto clear_pipe;
+ }
+
+ if (res != 0 && res < len) {
+ struct fuse_bufvec mem_buf = FUSE_BUFVEC_INIT(len);
+ void *mbuf;
+ size_t now_len = res;
+ /*
+ * For regular files a short count is either
+ * 1) due to EOF, or
+ * 2) because of broken SPLICE_F_NONBLOCK (see above)
+ *
+ * For other inputs it's possible that we overflowed
+ * the pipe because of small buffer fragments.
+ */
+
+ res = posix_memalign(&mbuf, pagesize, len);
+ if (res != 0)
+ goto clear_pipe;
+
+ mem_buf.buf[0].mem = mbuf;
+ mem_buf.off = now_len;
+ res = fuse_buf_copy(&mem_buf, buf, 0);
+ if (res > 0) {
+ char *tmpbuf;
+ size_t extra_len = res;
+ /*
+ * Trickiest case: got more data. Need to get
+ * back the data from the pipe and then fall
+ * back to regular write.
+ */
+ tmpbuf = malloc(headerlen);
+ if (tmpbuf == NULL) {
+ free(mbuf);
+ res = ENOMEM;
+ goto clear_pipe;
+ }
+ res = read_back(llp->pipe[0], tmpbuf, headerlen);
+ free(tmpbuf);
+ if (res != 0) {
+ free(mbuf);
+ goto clear_pipe;
+ }
+ res = read_back(llp->pipe[0], mbuf, now_len);
+ if (res != 0) {
+ free(mbuf);
+ goto clear_pipe;
+ }
+ len = now_len + extra_len;
+ iov[iov_count].iov_base = mbuf;
+ iov[iov_count].iov_len = len;
+ iov_count++;
+ res = fuse_send_msg(se, ch, iov, iov_count);
+ free(mbuf);
+ return res;
+ }
+ free(mbuf);
+ res = now_len;
+ }
+ len = res;
+ out->len = headerlen + len;
+
+ if (se->debug) {
+ fprintf(stderr,
+ " unique: %llu, success, outsize: %i (splice)\n",
+ (unsigned long long) out->unique, out->len);
+ }
+
+ splice_flags = 0;
+ if ((flags & FUSE_BUF_SPLICE_MOVE) &&
+ (se->conn.want & FUSE_CAP_SPLICE_MOVE))
+ splice_flags |= SPLICE_F_MOVE;
+
+ res = splice(llp->pipe[0], NULL, ch ? ch->fd : se->fd,
+ NULL, out->len, splice_flags);
+ if (res == -1) {
+ res = -errno;
+ perror("fuse: splice from pipe");
+ goto clear_pipe;
+ }
+ if (res != out->len) {
+ res = -EIO;
+ fprintf(stderr, "fuse: short splice from pipe: %u/%u\n",
+ res, out->len);
+ goto clear_pipe;
+ }
+ return 0;
+
+clear_pipe:
+ fuse_ll_clear_pipe(se);
+ return res;
+
+fallback:
+ return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+}
+#else
+static int fuse_send_data_iov(struct fuse_session *se, struct fuse_chan *ch,
+ struct iovec *iov, int iov_count,
+ struct fuse_bufvec *buf, unsigned int flags)
+{
+ size_t len = fuse_buf_size(buf);
+ (void) flags;
+
+ return fuse_send_data_iov_fallback(se, ch, iov, iov_count, buf, len);
+}
+#endif
+
+int fuse_reply_data(fuse_req_t req, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags)
+{
+ struct iovec iov[2];
+ struct fuse_out_header out;
+ int res;
+
+ iov[0].iov_base = &out;
+ iov[0].iov_len = sizeof(struct fuse_out_header);
+
+ out.unique = req->unique;
+ out.error = 0;
+
+ res = fuse_send_data_iov(req->se, req->ch, iov, 1, bufv, flags);
+ if (res <= 0) {
+ fuse_free_req(req);
+ return res;
+ } else {
+ return fuse_reply_err(req, res);
+ }
+}
+
+int fuse_reply_statfs(fuse_req_t req, const struct statvfs *stbuf)
+{
+ struct fuse_statfs_out arg;
+ size_t size = req->se->conn.proto_minor < 4 ?
+ FUSE_COMPAT_STATFS_SIZE : sizeof(arg);
+
+ memset(&arg, 0, sizeof(arg));
+ convert_statfs(stbuf, &arg.st);
+
+ return send_reply_ok(req, &arg, size);
+}
+
+int fuse_reply_xattr(fuse_req_t req, size_t count)
+{
+ struct fuse_getxattr_out arg;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.size = count;
+
+ return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_lock(fuse_req_t req, const struct flock *lock)
+{
+ struct fuse_lk_out arg;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.lk.type = lock->l_type;
+ if (lock->l_type != F_UNLCK) {
+ arg.lk.start = lock->l_start;
+ if (lock->l_len == 0)
+ arg.lk.end = OFFSET_MAX;
+ else
+ arg.lk.end = lock->l_start + lock->l_len - 1;
+ }
+ arg.lk.pid = lock->l_pid;
+ return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+int fuse_reply_bmap(fuse_req_t req, uint64_t idx)
+{
+ struct fuse_bmap_out arg;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.block = idx;
+
+ return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+static struct fuse_ioctl_iovec *fuse_ioctl_iovec_copy(const struct iovec *iov,
+ size_t count)
+{
+ struct fuse_ioctl_iovec *fiov;
+ size_t i;
+
+ fiov = malloc(sizeof(fiov[0]) * count);
+ if (!fiov)
+ return NULL;
+
+ for (i = 0; i < count; i++) {
+ fiov[i].base = (uintptr_t) iov[i].iov_base;
+ fiov[i].len = iov[i].iov_len;
+ }
+
+ return fiov;
+}
+
+int fuse_reply_ioctl_retry(fuse_req_t req,
+ const struct iovec *in_iov, size_t in_count,
+ const struct iovec *out_iov, size_t out_count)
+{
+ struct fuse_ioctl_out arg;
+ struct fuse_ioctl_iovec *in_fiov = NULL;
+ struct fuse_ioctl_iovec *out_fiov = NULL;
+ struct iovec iov[4];
+ size_t count = 1;
+ int res;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.flags |= FUSE_IOCTL_RETRY;
+ arg.in_iovs = in_count;
+ arg.out_iovs = out_count;
+ iov[count].iov_base = &arg;
+ iov[count].iov_len = sizeof(arg);
+ count++;
+
+ if (req->se->conn.proto_minor < 16) {
+ if (in_count) {
+ iov[count].iov_base = (void *)in_iov;
+ iov[count].iov_len = sizeof(in_iov[0]) * in_count;
+ count++;
+ }
+
+ if (out_count) {
+ iov[count].iov_base = (void *)out_iov;
+ iov[count].iov_len = sizeof(out_iov[0]) * out_count;
+ count++;
+ }
+ } else {
+ /* Can't handle non-compat 64bit ioctls on 32bit */
+ if (sizeof(void *) == 4 && req->ioctl_64bit) {
+ res = fuse_reply_err(req, EINVAL);
+ goto out;
+ }
+
+ if (in_count) {
+ in_fiov = fuse_ioctl_iovec_copy(in_iov, in_count);
+ if (!in_fiov)
+ goto enomem;
+
+ iov[count].iov_base = (void *)in_fiov;
+ iov[count].iov_len = sizeof(in_fiov[0]) * in_count;
+ count++;
+ }
+ if (out_count) {
+ out_fiov = fuse_ioctl_iovec_copy(out_iov, out_count);
+ if (!out_fiov)
+ goto enomem;
+
+ iov[count].iov_base = (void *)out_fiov;
+ iov[count].iov_len = sizeof(out_fiov[0]) * out_count;
+ count++;
+ }
+ }
+
+ res = send_reply_iov(req, 0, iov, count);
+out:
+ free(in_fiov);
+ free(out_fiov);
+
+ return res;
+
+enomem:
+ res = fuse_reply_err(req, ENOMEM);
+ goto out;
+}
+
+int fuse_reply_ioctl(fuse_req_t req, int result, const void *buf, size_t size)
+{
+ struct fuse_ioctl_out arg;
+ struct iovec iov[3];
+ size_t count = 1;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.result = result;
+ iov[count].iov_base = &arg;
+ iov[count].iov_len = sizeof(arg);
+ count++;
+
+ if (size) {
+ iov[count].iov_base = (char *) buf;
+ iov[count].iov_len = size;
+ count++;
+ }
+
+ return send_reply_iov(req, 0, iov, count);
+}
+
+int fuse_reply_ioctl_iov(fuse_req_t req, int result, const struct iovec *iov,
+ int count)
+{
+ struct iovec *padded_iov;
+ struct fuse_ioctl_out arg;
+ int res;
+
+ padded_iov = malloc((count + 2) * sizeof(struct iovec));
+ if (padded_iov == NULL)
+ return fuse_reply_err(req, ENOMEM);
+
+ memset(&arg, 0, sizeof(arg));
+ arg.result = result;
+ padded_iov[1].iov_base = &arg;
+ padded_iov[1].iov_len = sizeof(arg);
+
+ memcpy(&padded_iov[2], iov, count * sizeof(struct iovec));
+
+ res = send_reply_iov(req, 0, padded_iov, count + 2);
+ free(padded_iov);
+
+ return res;
+}
+
+int fuse_reply_poll(fuse_req_t req, unsigned revents)
+{
+ struct fuse_poll_out arg;
+
+ memset(&arg, 0, sizeof(arg));
+ arg.revents = revents;
+
+ return send_reply_ok(req, &arg, sizeof(arg));
+}
+
+static void do_lookup(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ char *name = (char *) inarg;
+
+ if (req->se->op.lookup)
+ req->se->op.lookup(req, nodeid, name);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_forget(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_forget_in *arg = (struct fuse_forget_in *) inarg;
+
+ if (req->se->op.forget)
+ req->se->op.forget(req, nodeid, arg->nlookup);
+ else
+ fuse_reply_none(req);
+}
+
+static void do_batch_forget(fuse_req_t req, fuse_ino_t nodeid,
+ const void *inarg)
+{
+ struct fuse_batch_forget_in *arg = (void *) inarg;
+ struct fuse_forget_one *param = (void *) PARAM(arg);
+ unsigned int i;
+
+ (void) nodeid;
+
+ if (req->se->op.forget_multi) {
+ req->se->op.forget_multi(req, arg->count,
+ (struct fuse_forget_data *) param);
+ } else if (req->se->op.forget) {
+ for (i = 0; i < arg->count; i++) {
+ struct fuse_forget_one *forget = &param[i];
+ struct fuse_req *dummy_req;
+
+ dummy_req = fuse_ll_alloc_req(req->se);
+ if (dummy_req == NULL)
+ break;
+
+ dummy_req->unique = req->unique;
+ dummy_req->ctx = req->ctx;
+ dummy_req->ch = NULL;
+
+ req->se->op.forget(dummy_req, forget->nodeid,
+ forget->nlookup);
+ }
+ fuse_reply_none(req);
+ } else {
+ fuse_reply_none(req);
+ }
+}
+
+static void do_getattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_file_info *fip = NULL;
+ struct fuse_file_info fi;
+
+ if (req->se->conn.proto_minor >= 9) {
+ struct fuse_getattr_in *arg = (struct fuse_getattr_in *) inarg;
+
+ if (arg->getattr_flags & FUSE_GETATTR_FH) {
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fip = &fi;
+ }
+ }
+
+ if (req->se->op.getattr)
+ req->se->op.getattr(req, nodeid, fip);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_setattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_setattr_in *arg = (struct fuse_setattr_in *) inarg;
+
+ if (req->se->op.setattr) {
+ struct fuse_file_info *fi = NULL;
+ struct fuse_file_info fi_store;
+ struct stat stbuf;
+ memset(&stbuf, 0, sizeof(stbuf));
+ convert_attr(arg, &stbuf);
+ if (arg->valid & FATTR_FH) {
+ arg->valid &= ~FATTR_FH;
+ memset(&fi_store, 0, sizeof(fi_store));
+ fi = &fi_store;
+ fi->fh = arg->fh;
+ }
+ arg->valid &=
+ FUSE_SET_ATTR_MODE |
+ FUSE_SET_ATTR_UID |
+ FUSE_SET_ATTR_GID |
+ FUSE_SET_ATTR_SIZE |
+ FUSE_SET_ATTR_ATIME |
+ FUSE_SET_ATTR_MTIME |
+ FUSE_SET_ATTR_ATIME_NOW |
+ FUSE_SET_ATTR_MTIME_NOW |
+ FUSE_SET_ATTR_CTIME;
+
+ req->se->op.setattr(req, nodeid, &stbuf, arg->valid, fi);
+ } else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_access(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_access_in *arg = (struct fuse_access_in *) inarg;
+
+ if (req->se->op.access)
+ req->se->op.access(req, nodeid, arg->mask);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_readlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ (void) inarg;
+
+ if (req->se->op.readlink)
+ req->se->op.readlink(req, nodeid);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_mknod(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_mknod_in *arg = (struct fuse_mknod_in *) inarg;
+ char *name = PARAM(arg);
+
+ if (req->se->conn.proto_minor >= 12)
+ req->ctx.umask = arg->umask;
+ else
+ name = (char *) inarg + FUSE_COMPAT_MKNOD_IN_SIZE;
+
+ if (req->se->op.mknod)
+ req->se->op.mknod(req, nodeid, name, arg->mode, arg->rdev);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_mkdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_mkdir_in *arg = (struct fuse_mkdir_in *) inarg;
+
+ if (req->se->conn.proto_minor >= 12)
+ req->ctx.umask = arg->umask;
+
+ if (req->se->op.mkdir)
+ req->se->op.mkdir(req, nodeid, PARAM(arg), arg->mode);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_unlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ char *name = (char *) inarg;
+
+ if (req->se->op.unlink)
+ req->se->op.unlink(req, nodeid, name);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_rmdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ char *name = (char *) inarg;
+
+ if (req->se->op.rmdir)
+ req->se->op.rmdir(req, nodeid, name);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_symlink(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ char *name = (char *) inarg;
+ char *linkname = ((char *) inarg) + strlen((char *) inarg) + 1;
+
+ if (req->se->op.symlink)
+ req->se->op.symlink(req, linkname, nodeid, name);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_rename(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_rename_in *arg = (struct fuse_rename_in *) inarg;
+ char *oldname = PARAM(arg);
+ char *newname = oldname + strlen(oldname) + 1;
+
+ if (req->se->op.rename)
+ req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+ 0);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_rename2(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_rename2_in *arg = (struct fuse_rename2_in *) inarg;
+ char *oldname = PARAM(arg);
+ char *newname = oldname + strlen(oldname) + 1;
+
+ if (req->se->op.rename)
+ req->se->op.rename(req, nodeid, oldname, arg->newdir, newname,
+ arg->flags);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_link(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_link_in *arg = (struct fuse_link_in *) inarg;
+
+ if (req->se->op.link)
+ req->se->op.link(req, arg->oldnodeid, nodeid, PARAM(arg));
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_create(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_create_in *arg = (struct fuse_create_in *) inarg;
+
+ if (req->se->op.create) {
+ struct fuse_file_info fi;
+ char *name = PARAM(arg);
+
+ memset(&fi, 0, sizeof(fi));
+ fi.flags = arg->flags;
+
+ if (req->se->conn.proto_minor >= 12)
+ req->ctx.umask = arg->umask;
+ else
+ name = (char *) inarg + sizeof(struct fuse_open_in);
+
+ req->se->op.create(req, nodeid, name, arg->mode, &fi);
+ } else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_open(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.flags = arg->flags;
+
+ if (req->se->op.open)
+ req->se->op.open(req, nodeid, &fi);
+ else
+ fuse_reply_open(req, &fi);
+}
+
+static void do_read(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+
+ if (req->se->op.read) {
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ if (req->se->conn.proto_minor >= 9) {
+ fi.lock_owner = arg->lock_owner;
+ fi.flags = arg->flags;
+ }
+ req->se->op.read(req, nodeid, arg->size, arg->offset, &fi);
+ } else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_write(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+ struct fuse_file_info fi;
+ char *param;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.writepage = (arg->write_flags & FUSE_WRITE_CACHE) != 0;
+
+ if (req->se->conn.proto_minor < 9) {
+ param = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+ } else {
+ fi.lock_owner = arg->lock_owner;
+ fi.flags = arg->flags;
+ param = PARAM(arg);
+ }
+
+ if (req->se->op.write)
+ req->se->op.write(req, nodeid, param, arg->size,
+ arg->offset, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_write_buf(fuse_req_t req, fuse_ino_t nodeid, const void *inarg,
+ const struct fuse_buf *ibuf)
+{
+ struct fuse_session *se = req->se;
+ struct fuse_bufvec bufv = {
+ .buf[0] = *ibuf,
+ .count = 1,
+ };
+ struct fuse_write_in *arg = (struct fuse_write_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.writepage = arg->write_flags & FUSE_WRITE_CACHE;
+
+ if (se->conn.proto_minor < 9) {
+ bufv.buf[0].mem = ((char *) arg) + FUSE_COMPAT_WRITE_IN_SIZE;
+ bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+ FUSE_COMPAT_WRITE_IN_SIZE;
+ assert(!(bufv.buf[0].flags & FUSE_BUF_IS_FD));
+ } else {
+ fi.lock_owner = arg->lock_owner;
+ fi.flags = arg->flags;
+ if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+ bufv.buf[0].mem = PARAM(arg);
+
+ bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_write_in);
+ }
+ if (bufv.buf[0].size < arg->size) {
+ fprintf(stderr, "fuse: do_write_buf: buffer size too small\n");
+ fuse_reply_err(req, EIO);
+ goto out;
+ }
+ bufv.buf[0].size = arg->size;
+
+ se->op.write_buf(req, nodeid, &bufv, arg->offset, &fi);
+
+out:
+ /* Need to reset the pipe if ->write_buf() didn't consume all data */
+ if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+ fuse_ll_clear_pipe(se);
+}
+
+static void do_flush(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_flush_in *arg = (struct fuse_flush_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.flush = 1;
+ if (req->se->conn.proto_minor >= 7)
+ fi.lock_owner = arg->lock_owner;
+
+ if (req->se->op.flush)
+ req->se->op.flush(req, nodeid, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_release(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.flags = arg->flags;
+ fi.fh = arg->fh;
+ if (req->se->conn.proto_minor >= 8) {
+ fi.flush = (arg->release_flags & FUSE_RELEASE_FLUSH) ? 1 : 0;
+ fi.lock_owner = arg->lock_owner;
+ }
+ if (arg->release_flags & FUSE_RELEASE_FLOCK_UNLOCK) {
+ fi.flock_release = 1;
+ fi.lock_owner = arg->lock_owner;
+ }
+
+ if (req->se->op.release)
+ req->se->op.release(req, nodeid, &fi);
+ else
+ fuse_reply_err(req, 0);
+}
+
+static void do_fsync(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+ struct fuse_file_info fi;
+ int datasync = arg->fsync_flags & 1;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+
+ if (req->se->op.fsync)
+ req->se->op.fsync(req, nodeid, datasync, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_opendir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_open_in *arg = (struct fuse_open_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.flags = arg->flags;
+
+ if (req->se->op.opendir)
+ req->se->op.opendir(req, nodeid, &fi);
+ else
+ fuse_reply_open(req, &fi);
+}
+
+static void do_readdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+
+ if (req->se->op.readdir)
+ req->se->op.readdir(req, nodeid, arg->size, arg->offset, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_readdirplus(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_read_in *arg = (struct fuse_read_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+
+ if (req->se->op.readdirplus)
+ req->se->op.readdirplus(req, nodeid, arg->size, arg->offset, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_releasedir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_release_in *arg = (struct fuse_release_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.flags = arg->flags;
+ fi.fh = arg->fh;
+
+ if (req->se->op.releasedir)
+ req->se->op.releasedir(req, nodeid, &fi);
+ else
+ fuse_reply_err(req, 0);
+}
+
+static void do_fsyncdir(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_fsync_in *arg = (struct fuse_fsync_in *) inarg;
+ struct fuse_file_info fi;
+ int datasync = arg->fsync_flags & 1;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+
+ if (req->se->op.fsyncdir)
+ req->se->op.fsyncdir(req, nodeid, datasync, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_statfs(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ (void) nodeid;
+ (void) inarg;
+
+ if (req->se->op.statfs)
+ req->se->op.statfs(req, nodeid);
+ else {
+ struct statvfs buf = {
+ .f_namemax = 255,
+ .f_bsize = 512,
+ };
+ fuse_reply_statfs(req, &buf);
+ }
+}
+
+static void do_setxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_setxattr_in *arg = (struct fuse_setxattr_in *) inarg;
+ char *name = PARAM(arg);
+ char *value = name + strlen(name) + 1;
+
+ if (req->se->op.setxattr)
+ req->se->op.setxattr(req, nodeid, name, value, arg->size,
+ arg->flags);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_getxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
+ if (req->se->op.getxattr)
+ req->se->op.getxattr(req, nodeid, PARAM(arg), arg->size);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_listxattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_getxattr_in *arg = (struct fuse_getxattr_in *) inarg;
+
+ if (req->se->op.listxattr)
+ req->se->op.listxattr(req, nodeid, arg->size);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_removexattr(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ char *name = (char *) inarg;
+
+ if (req->se->op.removexattr)
+ req->se->op.removexattr(req, nodeid, name);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void convert_fuse_file_lock(struct fuse_file_lock *fl,
+ struct flock *flock)
+{
+ memset(flock, 0, sizeof(struct flock));
+ flock->l_type = fl->type;
+ flock->l_whence = SEEK_SET;
+ flock->l_start = fl->start;
+ if (fl->end == OFFSET_MAX)
+ flock->l_len = 0;
+ else
+ flock->l_len = fl->end - fl->start + 1;
+ flock->l_pid = fl->pid;
+}
+
+static void do_getlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+ struct fuse_file_info fi;
+ struct flock flock;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.lock_owner = arg->owner;
+
+ convert_fuse_file_lock(&arg->lk, &flock);
+ if (req->se->op.getlk)
+ req->se->op.getlk(req, nodeid, &fi, &flock);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_setlk_common(fuse_req_t req, fuse_ino_t nodeid,
+ const void *inarg, int sleep)
+{
+ struct fuse_lk_in *arg = (struct fuse_lk_in *) inarg;
+ struct fuse_file_info fi;
+ struct flock flock;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.lock_owner = arg->owner;
+
+ if (arg->lk_flags & FUSE_LK_FLOCK) {
+ int op = 0;
+
+ switch (arg->lk.type) {
+ case F_RDLCK:
+ op = LOCK_SH;
+ break;
+ case F_WRLCK:
+ op = LOCK_EX;
+ break;
+ case F_UNLCK:
+ op = LOCK_UN;
+ break;
+ }
+ if (!sleep)
+ op |= LOCK_NB;
+
+ if (req->se->op.flock)
+ req->se->op.flock(req, nodeid, &fi, op);
+ else
+ fuse_reply_err(req, ENOSYS);
+ } else {
+ convert_fuse_file_lock(&arg->lk, &flock);
+ if (req->se->op.setlk)
+ req->se->op.setlk(req, nodeid, &fi, &flock, sleep);
+ else
+ fuse_reply_err(req, ENOSYS);
+ }
+}
+
+static void do_setlk(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ do_setlk_common(req, nodeid, inarg, 0);
+}
+
+static void do_setlkw(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ do_setlk_common(req, nodeid, inarg, 1);
+}
+
+static int find_interrupted(struct fuse_session *se, struct fuse_req *req)
+{
+ struct fuse_req *curr;
+
+ for (curr = se->list.next; curr != &se->list; curr = curr->next) {
+ if (curr->unique == req->u.i.unique) {
+ fuse_interrupt_func_t func;
+ void *data;
+
+ curr->ctr++;
+ pthread_mutex_unlock(&se->lock);
+
+ /* Ugh, ugly locking */
+ pthread_mutex_lock(&curr->lock);
+ pthread_mutex_lock(&se->lock);
+ curr->interrupted = 1;
+ func = curr->u.ni.func;
+ data = curr->u.ni.data;
+ pthread_mutex_unlock(&se->lock);
+ if (func)
+ func(curr, data);
+ pthread_mutex_unlock(&curr->lock);
+
+ pthread_mutex_lock(&se->lock);
+ curr->ctr--;
+ if (!curr->ctr)
+ destroy_req(curr);
+
+ return 1;
+ }
+ }
+ for (curr = se->interrupts.next; curr != &se->interrupts;
+ curr = curr->next) {
+ if (curr->u.i.unique == req->u.i.unique)
+ return 1;
+ }
+ return 0;
+}
+
+static void do_interrupt(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_interrupt_in *arg = (struct fuse_interrupt_in *) inarg;
+ struct fuse_session *se = req->se;
+
+ (void) nodeid;
+ if (se->debug)
+ fprintf(stderr, "INTERRUPT: %llu\n",
+ (unsigned long long) arg->unique);
+
+ req->u.i.unique = arg->unique;
+
+ pthread_mutex_lock(&se->lock);
+ if (find_interrupted(se, req))
+ destroy_req(req);
+ else
+ list_add_req(req, &se->interrupts);
+ pthread_mutex_unlock(&se->lock);
+}
+
+static struct fuse_req *check_interrupt(struct fuse_session *se,
+ struct fuse_req *req)
+{
+ struct fuse_req *curr;
+
+ for (curr = se->interrupts.next; curr != &se->interrupts;
+ curr = curr->next) {
+ if (curr->u.i.unique == req->unique) {
+ req->interrupted = 1;
+ list_del_req(curr);
+ free(curr);
+ return NULL;
+ }
+ }
+ curr = se->interrupts.next;
+ if (curr != &se->interrupts) {
+ list_del_req(curr);
+ list_init_req(curr);
+ return curr;
+ } else
+ return NULL;
+}
+
+static void do_bmap(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_bmap_in *arg = (struct fuse_bmap_in *) inarg;
+
+ if (req->se->op.bmap)
+ req->se->op.bmap(req, nodeid, arg->blocksize, arg->block);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_ioctl(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_ioctl_in *arg = (struct fuse_ioctl_in *) inarg;
+ unsigned int flags = arg->flags;
+ void *in_buf = arg->in_size ? PARAM(arg) : NULL;
+ struct fuse_file_info fi;
+
+ if (flags & FUSE_IOCTL_DIR &&
+ !(req->se->conn.want & FUSE_CAP_IOCTL_DIR)) {
+ fuse_reply_err(req, ENOTTY);
+ return;
+ }
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+
+ if (sizeof(void *) == 4 && req->se->conn.proto_minor >= 16 &&
+ !(flags & FUSE_IOCTL_32BIT)) {
+ req->ioctl_64bit = 1;
+ }
+
+ if (req->se->op.ioctl)
+ req->se->op.ioctl(req, nodeid, arg->cmd,
+ (void *)(uintptr_t)arg->arg, &fi, flags,
+ in_buf, arg->in_size, arg->out_size);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+void fuse_pollhandle_destroy(struct fuse_pollhandle *ph)
+{
+ free(ph);
+}
+
+static void do_poll(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_poll_in *arg = (struct fuse_poll_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+ fi.poll_events = arg->events;
+
+ if (req->se->op.poll) {
+ struct fuse_pollhandle *ph = NULL;
+
+ if (arg->flags & FUSE_POLL_SCHEDULE_NOTIFY) {
+ ph = malloc(sizeof(struct fuse_pollhandle));
+ if (ph == NULL) {
+ fuse_reply_err(req, ENOMEM);
+ return;
+ }
+ ph->kh = arg->kh;
+ ph->se = req->se;
+ }
+
+ req->se->op.poll(req, nodeid, &fi, ph);
+ } else {
+ fuse_reply_err(req, ENOSYS);
+ }
+}
+
+static void do_fallocate(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_fallocate_in *arg = (struct fuse_fallocate_in *) inarg;
+ struct fuse_file_info fi;
+
+ memset(&fi, 0, sizeof(fi));
+ fi.fh = arg->fh;
+
+ if (req->se->op.fallocate)
+ req->se->op.fallocate(req, nodeid, arg->mode, arg->offset, arg->length, &fi);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_copy_file_range(fuse_req_t req, fuse_ino_t nodeid_in, const void *inarg)
+{
+ struct fuse_copy_file_range_in *arg = (struct fuse_copy_file_range_in *) inarg;
+ struct fuse_file_info fi_in, fi_out;
+
+ memset(&fi_in, 0, sizeof(fi_in));
+ fi_in.fh = arg->fh_in;
+
+ memset(&fi_out, 0, sizeof(fi_out));
+ fi_out.fh = arg->fh_out;
+
+
+ if (req->se->op.copy_file_range)
+ req->se->op.copy_file_range(req, nodeid_in, arg->off_in,
+ &fi_in, arg->nodeid_out,
+ arg->off_out, &fi_out, arg->len,
+ arg->flags);
+ else
+ fuse_reply_err(req, ENOSYS);
+}
+
+static void do_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
+ struct fuse_init_out outarg;
+ struct fuse_session *se = req->se;
+ size_t bufsize = se->bufsize;
+ size_t outargsize = sizeof(outarg);
+
+ (void) nodeid;
+ if (se->debug) {
+ fprintf(stderr, "INIT: %u.%u\n", arg->major, arg->minor);
+ if (arg->major == 7 && arg->minor >= 6) {
+ fprintf(stderr, "flags=0x%08x\n", arg->flags);
+ fprintf(stderr, "max_readahead=0x%08x\n",
+ arg->max_readahead);
+ }
+ }
+ se->conn.proto_major = arg->major;
+ se->conn.proto_minor = arg->minor;
+ se->conn.capable = 0;
+ se->conn.want = 0;
+
+ memset(&outarg, 0, sizeof(outarg));
+ outarg.major = FUSE_KERNEL_VERSION;
+ outarg.minor = FUSE_KERNEL_MINOR_VERSION;
+
+ if (arg->major < 7) {
+ fprintf(stderr, "fuse: unsupported protocol version: %u.%u\n",
+ arg->major, arg->minor);
+ fuse_reply_err(req, EPROTO);
+ return;
+ }
+
+ if (arg->major > 7) {
+ /* Wait for a second INIT request with a 7.X version */
+ send_reply_ok(req, &outarg, sizeof(outarg));
+ return;
+ }
+
+ if (arg->minor >= 6) {
+ if (arg->max_readahead < se->conn.max_readahead)
+ se->conn.max_readahead = arg->max_readahead;
+ if (arg->flags & FUSE_ASYNC_READ)
+ se->conn.capable |= FUSE_CAP_ASYNC_READ;
+ if (arg->flags & FUSE_POSIX_LOCKS)
+ se->conn.capable |= FUSE_CAP_POSIX_LOCKS;
+ if (arg->flags & FUSE_ATOMIC_O_TRUNC)
+ se->conn.capable |= FUSE_CAP_ATOMIC_O_TRUNC;
+ if (arg->flags & FUSE_EXPORT_SUPPORT)
+ se->conn.capable |= FUSE_CAP_EXPORT_SUPPORT;
+ if (arg->flags & FUSE_DONT_MASK)
+ se->conn.capable |= FUSE_CAP_DONT_MASK;
+ if (arg->flags & FUSE_FLOCK_LOCKS)
+ se->conn.capable |= FUSE_CAP_FLOCK_LOCKS;
+ if (arg->flags & FUSE_AUTO_INVAL_DATA)
+ se->conn.capable |= FUSE_CAP_AUTO_INVAL_DATA;
+ if (arg->flags & FUSE_DO_READDIRPLUS)
+ se->conn.capable |= FUSE_CAP_READDIRPLUS;
+ if (arg->flags & FUSE_READDIRPLUS_AUTO)
+ se->conn.capable |= FUSE_CAP_READDIRPLUS_AUTO;
+ if (arg->flags & FUSE_ASYNC_DIO)
+ se->conn.capable |= FUSE_CAP_ASYNC_DIO;
+ if (arg->flags & FUSE_WRITEBACK_CACHE)
+ se->conn.capable |= FUSE_CAP_WRITEBACK_CACHE;
+ if (arg->flags & FUSE_NO_OPEN_SUPPORT)
+ se->conn.capable |= FUSE_CAP_NO_OPEN_SUPPORT;
+ if (arg->flags & FUSE_PARALLEL_DIROPS)
+ se->conn.capable |= FUSE_CAP_PARALLEL_DIROPS;
+ if (arg->flags & FUSE_POSIX_ACL)
+ se->conn.capable |= FUSE_CAP_POSIX_ACL;
+ if (arg->flags & FUSE_HANDLE_KILLPRIV)
+ se->conn.capable |= FUSE_CAP_HANDLE_KILLPRIV;
+ if (arg->flags & FUSE_NO_OPENDIR_SUPPORT)
+ se->conn.capable |= FUSE_CAP_NO_OPENDIR_SUPPORT;
+ if (!(arg->flags & FUSE_MAX_PAGES)) {
+ size_t max_bufsize =
+ FUSE_DEFAULT_MAX_PAGES_PER_REQ * getpagesize()
+ + FUSE_BUFFER_HEADER_SIZE;
+ if (bufsize > max_bufsize) {
+ bufsize = max_bufsize;
+ }
+ }
+ } else {
+ se->conn.max_readahead = 0;
+ }
+
+ if (se->conn.proto_minor >= 14) {
+#ifdef HAVE_SPLICE
+#ifdef HAVE_VMSPLICE
+ se->conn.capable |= FUSE_CAP_SPLICE_WRITE | FUSE_CAP_SPLICE_MOVE;
+#endif
+ se->conn.capable |= FUSE_CAP_SPLICE_READ;
+#endif
+ }
+ if (se->conn.proto_minor >= 18)
+ se->conn.capable |= FUSE_CAP_IOCTL_DIR;
+
+ /* Default settings for modern filesystems.
+ *
+ * Most of these capabilities were disabled by default in
+ * libfuse2 for backwards compatibility reasons. In libfuse3,
+ * we can finally enable them by default (as long as they're
+ * supported by the kernel).
+ */
+#define LL_SET_DEFAULT(cond, cap) \
+ if ((cond) && (se->conn.capable & (cap))) \
+ se->conn.want |= (cap)
+ LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_READ);
+ LL_SET_DEFAULT(1, FUSE_CAP_PARALLEL_DIROPS);
+ LL_SET_DEFAULT(1, FUSE_CAP_AUTO_INVAL_DATA);
+ LL_SET_DEFAULT(1, FUSE_CAP_HANDLE_KILLPRIV);
+ LL_SET_DEFAULT(1, FUSE_CAP_ASYNC_DIO);
+ LL_SET_DEFAULT(1, FUSE_CAP_IOCTL_DIR);
+ LL_SET_DEFAULT(1, FUSE_CAP_ATOMIC_O_TRUNC);
+ LL_SET_DEFAULT(se->op.write_buf, FUSE_CAP_SPLICE_READ);
+ LL_SET_DEFAULT(se->op.getlk && se->op.setlk,
+ FUSE_CAP_POSIX_LOCKS);
+ LL_SET_DEFAULT(se->op.flock, FUSE_CAP_FLOCK_LOCKS);
+ LL_SET_DEFAULT(se->op.readdirplus, FUSE_CAP_READDIRPLUS);
+ LL_SET_DEFAULT(se->op.readdirplus && se->op.readdir,
+ FUSE_CAP_READDIRPLUS_AUTO);
+ se->conn.time_gran = 1;
+
+ if (bufsize < FUSE_MIN_READ_BUFFER) {
+ fprintf(stderr, "fuse: warning: buffer size too small: %zu\n",
+ bufsize);
+ bufsize = FUSE_MIN_READ_BUFFER;
+ }
+ se->bufsize = bufsize;
+
+ if (se->conn.max_write > bufsize - FUSE_BUFFER_HEADER_SIZE)
+ se->conn.max_write = bufsize - FUSE_BUFFER_HEADER_SIZE;
+
+ se->got_init = 1;
+ if (se->op.init)
+ se->op.init(se->userdata, &se->conn);
+
+ if (se->conn.want & (~se->conn.capable)) {
+ fprintf(stderr, "fuse: error: filesystem requested capabilities "
+ "0x%x that are not supported by kernel, aborting.\n",
+ se->conn.want & (~se->conn.capable));
+ fuse_reply_err(req, EPROTO);
+ se->error = -EPROTO;
+ fuse_session_exit(se);
+ return;
+ }
+
+ unsigned max_read_mo = get_max_read(se->mo);
+ if (se->conn.max_read != max_read_mo) {
+ fprintf(stderr, "fuse: error: init() and fuse_session_new() "
+ "requested different maximum read size (%u vs %u)\n",
+ se->conn.max_read, max_read_mo);
+ fuse_reply_err(req, EPROTO);
+ se->error = -EPROTO;
+ fuse_session_exit(se);
+ return;
+ }
+
+ if (se->conn.max_write < bufsize - FUSE_BUFFER_HEADER_SIZE) {
+ se->bufsize = se->conn.max_write + FUSE_BUFFER_HEADER_SIZE;
+ }
+ if (arg->flags & FUSE_MAX_PAGES) {
+ outarg.flags |= FUSE_MAX_PAGES;
+ outarg.max_pages = (se->conn.max_write - 1) / getpagesize() + 1;
+ }
+
+ /* Always enable big writes, this is superseded
+ by the max_write option */
+ outarg.flags |= FUSE_BIG_WRITES;
+
+ if (se->conn.want & FUSE_CAP_ASYNC_READ)
+ outarg.flags |= FUSE_ASYNC_READ;
+ if (se->conn.want & FUSE_CAP_POSIX_LOCKS)
+ outarg.flags |= FUSE_POSIX_LOCKS;
+ if (se->conn.want & FUSE_CAP_ATOMIC_O_TRUNC)
+ outarg.flags |= FUSE_ATOMIC_O_TRUNC;
+ if (se->conn.want & FUSE_CAP_EXPORT_SUPPORT)
+ outarg.flags |= FUSE_EXPORT_SUPPORT;
+ if (se->conn.want & FUSE_CAP_DONT_MASK)
+ outarg.flags |= FUSE_DONT_MASK;
+ if (se->conn.want & FUSE_CAP_FLOCK_LOCKS)
+ outarg.flags |= FUSE_FLOCK_LOCKS;
+ if (se->conn.want & FUSE_CAP_AUTO_INVAL_DATA)
+ outarg.flags |= FUSE_AUTO_INVAL_DATA;
+ if (se->conn.want & FUSE_CAP_READDIRPLUS)
+ outarg.flags |= FUSE_DO_READDIRPLUS;
+ if (se->conn.want & FUSE_CAP_READDIRPLUS_AUTO)
+ outarg.flags |= FUSE_READDIRPLUS_AUTO;
+ if (se->conn.want & FUSE_CAP_ASYNC_DIO)
+ outarg.flags |= FUSE_ASYNC_DIO;
+ if (se->conn.want & FUSE_CAP_WRITEBACK_CACHE)
+ outarg.flags |= FUSE_WRITEBACK_CACHE;
+ if (se->conn.want & FUSE_CAP_POSIX_ACL)
+ outarg.flags |= FUSE_POSIX_ACL;
+ outarg.max_readahead = se->conn.max_readahead;
+ outarg.max_write = se->conn.max_write;
+ if (se->conn.proto_minor >= 13) {
+ if (se->conn.max_background >= (1 << 16))
+ se->conn.max_background = (1 << 16) - 1;
+ if (se->conn.congestion_threshold > se->conn.max_background)
+ se->conn.congestion_threshold = se->conn.max_background;
+ if (!se->conn.congestion_threshold) {
+ se->conn.congestion_threshold =
+ se->conn.max_background * 3 / 4;
+ }
+
+ outarg.max_background = se->conn.max_background;
+ outarg.congestion_threshold = se->conn.congestion_threshold;
+ }
+ if (se->conn.proto_minor >= 23)
+ outarg.time_gran = se->conn.time_gran;
+
+ if (se->debug) {
+ fprintf(stderr, " INIT: %u.%u\n", outarg.major, outarg.minor);
+ fprintf(stderr, " flags=0x%08x\n", outarg.flags);
+ fprintf(stderr, " max_readahead=0x%08x\n",
+ outarg.max_readahead);
+ fprintf(stderr, " max_write=0x%08x\n", outarg.max_write);
+ fprintf(stderr, " max_background=%i\n",
+ outarg.max_background);
+ fprintf(stderr, " congestion_threshold=%i\n",
+ outarg.congestion_threshold);
+ fprintf(stderr, " time_gran=%u\n",
+ outarg.time_gran);
+ }
+ if (arg->minor < 5)
+ outargsize = FUSE_COMPAT_INIT_OUT_SIZE;
+ else if (arg->minor < 23)
+ outargsize = FUSE_COMPAT_22_INIT_OUT_SIZE;
+
+ send_reply_ok(req, &outarg, outargsize);
+}
+
+static void do_destroy(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
+{
+ struct fuse_session *se = req->se;
+
+ (void) nodeid;
+ (void) inarg;
+
+ se->got_destroy = 1;
+ if (se->op.destroy)
+ se->op.destroy(se->userdata);
+
+ send_reply_ok(req, NULL, 0);
+}
+
+static void list_del_nreq(struct fuse_notify_req *nreq)
+{
+ struct fuse_notify_req *prev = nreq->prev;
+ struct fuse_notify_req *next = nreq->next;
+ prev->next = next;
+ next->prev = prev;
+}
+
+static void list_add_nreq(struct fuse_notify_req *nreq,
+ struct fuse_notify_req *next)
+{
+ struct fuse_notify_req *prev = next->prev;
+ nreq->next = next;
+ nreq->prev = prev;
+ prev->next = nreq;
+ next->prev = nreq;
+}
+
+static void list_init_nreq(struct fuse_notify_req *nreq)
+{
+ nreq->next = nreq;
+ nreq->prev = nreq;
+}
+
+static void do_notify_reply(fuse_req_t req, fuse_ino_t nodeid,
+ const void *inarg, const struct fuse_buf *buf)
+{
+ struct fuse_session *se = req->se;
+ struct fuse_notify_req *nreq;
+ struct fuse_notify_req *head;
+
+ pthread_mutex_lock(&se->lock);
+ head = &se->notify_list;
+ for (nreq = head->next; nreq != head; nreq = nreq->next) {
+ if (nreq->unique == req->unique) {
+ list_del_nreq(nreq);
+ break;
+ }
+ }
+ pthread_mutex_unlock(&se->lock);
+
+ if (nreq != head)
+ nreq->reply(nreq, req, nodeid, inarg, buf);
+}
+
+static int send_notify_iov(struct fuse_session *se, int notify_code,
+ struct iovec *iov, int count)
+{
+ struct fuse_out_header out;
+
+ if (!se->got_init)
+ return -ENOTCONN;
+
+ out.unique = 0;
+ out.error = notify_code;
+ iov[0].iov_base = &out;
+ iov[0].iov_len = sizeof(struct fuse_out_header);
+
+ return fuse_send_msg(se, NULL, iov, count);
+}
+
+int fuse_lowlevel_notify_poll(struct fuse_pollhandle *ph)
+{
+ if (ph != NULL) {
+ struct fuse_notify_poll_wakeup_out outarg;
+ struct iovec iov[2];
+
+ outarg.kh = ph->kh;
+
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+
+ return send_notify_iov(ph->se, FUSE_NOTIFY_POLL, iov, 2);
+ } else {
+ return 0;
+ }
+}
+
+int fuse_lowlevel_notify_inval_inode(struct fuse_session *se, fuse_ino_t ino,
+ off_t off, off_t len)
+{
+ struct fuse_notify_inval_inode_out outarg;
+ struct iovec iov[2];
+
+ if (!se)
+ return -EINVAL;
+
+ if (se->conn.proto_major < 6 || se->conn.proto_minor < 12)
+ return -ENOSYS;
+
+ outarg.ino = ino;
+ outarg.off = off;
+ outarg.len = len;
+
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+
+ return send_notify_iov(se, FUSE_NOTIFY_INVAL_INODE, iov, 2);
+}
+
+int fuse_lowlevel_notify_inval_entry(struct fuse_session *se, fuse_ino_t parent,
+ const char *name, size_t namelen)
+{
+ struct fuse_notify_inval_entry_out outarg;
+ struct iovec iov[3];
+
+ if (!se)
+ return -EINVAL;
+
+ if (se->conn.proto_major < 6 || se->conn.proto_minor < 12)
+ return -ENOSYS;
+
+ outarg.parent = parent;
+ outarg.namelen = namelen;
+ outarg.padding = 0;
+
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+ iov[2].iov_base = (void *)name;
+ iov[2].iov_len = namelen + 1;
+
+ return send_notify_iov(se, FUSE_NOTIFY_INVAL_ENTRY, iov, 3);
+}
+
+int fuse_lowlevel_notify_delete(struct fuse_session *se,
+ fuse_ino_t parent, fuse_ino_t child,
+ const char *name, size_t namelen)
+{
+ struct fuse_notify_delete_out outarg;
+ struct iovec iov[3];
+
+ if (!se)
+ return -EINVAL;
+
+ if (se->conn.proto_major < 6 || se->conn.proto_minor < 18)
+ return -ENOSYS;
+
+ outarg.parent = parent;
+ outarg.child = child;
+ outarg.namelen = namelen;
+ outarg.padding = 0;
+
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+ iov[2].iov_base = (void *)name;
+ iov[2].iov_len = namelen + 1;
+
+ return send_notify_iov(se, FUSE_NOTIFY_DELETE, iov, 3);
+}
+
+int fuse_lowlevel_notify_store(struct fuse_session *se, fuse_ino_t ino,
+ off_t offset, struct fuse_bufvec *bufv,
+ enum fuse_buf_copy_flags flags)
+{
+ struct fuse_out_header out;
+ struct fuse_notify_store_out outarg;
+ struct iovec iov[3];
+ size_t size = fuse_buf_size(bufv);
+ int res;
+
+ if (!se)
+ return -EINVAL;
+
+ if (se->conn.proto_major < 6 || se->conn.proto_minor < 15)
+ return -ENOSYS;
+
+ out.unique = 0;
+ out.error = FUSE_NOTIFY_STORE;
+
+ outarg.nodeid = ino;
+ outarg.offset = offset;
+ outarg.size = size;
+ outarg.padding = 0;
+
+ iov[0].iov_base = &out;
+ iov[0].iov_len = sizeof(out);
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+
+ res = fuse_send_data_iov(se, NULL, iov, 2, bufv, flags);
+ if (res > 0)
+ res = -res;
+
+ return res;
+}
+
+struct fuse_retrieve_req {
+ struct fuse_notify_req nreq;
+ void *cookie;
+};
+
+static void fuse_ll_retrieve_reply(struct fuse_notify_req *nreq,
+ fuse_req_t req, fuse_ino_t ino,
+ const void *inarg,
+ const struct fuse_buf *ibuf)
+{
+ struct fuse_session *se = req->se;
+ struct fuse_retrieve_req *rreq =
+ container_of(nreq, struct fuse_retrieve_req, nreq);
+ const struct fuse_notify_retrieve_in *arg = inarg;
+ struct fuse_bufvec bufv = {
+ .buf[0] = *ibuf,
+ .count = 1,
+ };
+
+ if (!(bufv.buf[0].flags & FUSE_BUF_IS_FD))
+ bufv.buf[0].mem = PARAM(arg);
+
+ bufv.buf[0].size -= sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_notify_retrieve_in);
+
+ if (bufv.buf[0].size < arg->size) {
+ fprintf(stderr, "fuse: retrieve reply: buffer size too small\n");
+ fuse_reply_none(req);
+ goto out;
+ }
+ bufv.buf[0].size = arg->size;
+
+ if (se->op.retrieve_reply) {
+ se->op.retrieve_reply(req, rreq->cookie, ino,
+ arg->offset, &bufv);
+ } else {
+ fuse_reply_none(req);
+ }
+out:
+ free(rreq);
+ if ((ibuf->flags & FUSE_BUF_IS_FD) && bufv.idx < bufv.count)
+ fuse_ll_clear_pipe(se);
+}
+
+int fuse_lowlevel_notify_retrieve(struct fuse_session *se, fuse_ino_t ino,
+ size_t size, off_t offset, void *cookie)
+{
+ struct fuse_notify_retrieve_out outarg;
+ struct iovec iov[2];
+ struct fuse_retrieve_req *rreq;
+ int err;
+
+ if (!se)
+ return -EINVAL;
+
+ if (se->conn.proto_major < 6 || se->conn.proto_minor < 15)
+ return -ENOSYS;
+
+ rreq = malloc(sizeof(*rreq));
+ if (rreq == NULL)
+ return -ENOMEM;
+
+ pthread_mutex_lock(&se->lock);
+ rreq->cookie = cookie;
+ rreq->nreq.unique = se->notify_ctr++;
+ rreq->nreq.reply = fuse_ll_retrieve_reply;
+ list_add_nreq(&rreq->nreq, &se->notify_list);
+ pthread_mutex_unlock(&se->lock);
+
+ outarg.notify_unique = rreq->nreq.unique;
+ outarg.nodeid = ino;
+ outarg.offset = offset;
+ outarg.size = size;
+ outarg.padding = 0;
+
+ iov[1].iov_base = &outarg;
+ iov[1].iov_len = sizeof(outarg);
+
+ err = send_notify_iov(se, FUSE_NOTIFY_RETRIEVE, iov, 2);
+ if (err) {
+ pthread_mutex_lock(&se->lock);
+ list_del_nreq(&rreq->nreq);
+ pthread_mutex_unlock(&se->lock);
+ free(rreq);
+ }
+
+ return err;
+}
+
+void *fuse_req_userdata(fuse_req_t req)
+{
+ return req->se->userdata;
+}
+
+const struct fuse_ctx *fuse_req_ctx(fuse_req_t req)
+{
+ return &req->ctx;
+}
+
+void fuse_req_interrupt_func(fuse_req_t req, fuse_interrupt_func_t func,
+ void *data)
+{
+ pthread_mutex_lock(&req->lock);
+ pthread_mutex_lock(&req->se->lock);
+ req->u.ni.func = func;
+ req->u.ni.data = data;
+ pthread_mutex_unlock(&req->se->lock);
+ if (req->interrupted && func)
+ func(req, data);
+ pthread_mutex_unlock(&req->lock);
+}
+
+int fuse_req_interrupted(fuse_req_t req)
+{
+ int interrupted;
+
+ pthread_mutex_lock(&req->se->lock);
+ interrupted = req->interrupted;
+ pthread_mutex_unlock(&req->se->lock);
+
+ return interrupted;
+}
+
+static struct {
+ void (*func)(fuse_req_t, fuse_ino_t, const void *);
+ const char *name;
+} fuse_ll_ops[] = {
+ [FUSE_LOOKUP] = { do_lookup, "LOOKUP" },
+ [FUSE_FORGET] = { do_forget, "FORGET" },
+ [FUSE_GETATTR] = { do_getattr, "GETATTR" },
+ [FUSE_SETATTR] = { do_setattr, "SETATTR" },
+ [FUSE_READLINK] = { do_readlink, "READLINK" },
+ [FUSE_SYMLINK] = { do_symlink, "SYMLINK" },
+ [FUSE_MKNOD] = { do_mknod, "MKNOD" },
+ [FUSE_MKDIR] = { do_mkdir, "MKDIR" },
+ [FUSE_UNLINK] = { do_unlink, "UNLINK" },
+ [FUSE_RMDIR] = { do_rmdir, "RMDIR" },
+ [FUSE_RENAME] = { do_rename, "RENAME" },
+ [FUSE_LINK] = { do_link, "LINK" },
+ [FUSE_OPEN] = { do_open, "OPEN" },
+ [FUSE_READ] = { do_read, "READ" },
+ [FUSE_WRITE] = { do_write, "WRITE" },
+ [FUSE_STATFS] = { do_statfs, "STATFS" },
+ [FUSE_RELEASE] = { do_release, "RELEASE" },
+ [FUSE_FSYNC] = { do_fsync, "FSYNC" },
+ [FUSE_SETXATTR] = { do_setxattr, "SETXATTR" },
+ [FUSE_GETXATTR] = { do_getxattr, "GETXATTR" },
+ [FUSE_LISTXATTR] = { do_listxattr, "LISTXATTR" },
+ [FUSE_REMOVEXATTR] = { do_removexattr, "REMOVEXATTR" },
+ [FUSE_FLUSH] = { do_flush, "FLUSH" },
+ [FUSE_INIT] = { do_init, "INIT" },
+ [FUSE_OPENDIR] = { do_opendir, "OPENDIR" },
+ [FUSE_READDIR] = { do_readdir, "READDIR" },
+ [FUSE_RELEASEDIR] = { do_releasedir, "RELEASEDIR" },
+ [FUSE_FSYNCDIR] = { do_fsyncdir, "FSYNCDIR" },
+ [FUSE_GETLK] = { do_getlk, "GETLK" },
+ [FUSE_SETLK] = { do_setlk, "SETLK" },
+ [FUSE_SETLKW] = { do_setlkw, "SETLKW" },
+ [FUSE_ACCESS] = { do_access, "ACCESS" },
+ [FUSE_CREATE] = { do_create, "CREATE" },
+ [FUSE_INTERRUPT] = { do_interrupt, "INTERRUPT" },
+ [FUSE_BMAP] = { do_bmap, "BMAP" },
+ [FUSE_IOCTL] = { do_ioctl, "IOCTL" },
+ [FUSE_POLL] = { do_poll, "POLL" },
+ [FUSE_FALLOCATE] = { do_fallocate, "FALLOCATE" },
+ [FUSE_DESTROY] = { do_destroy, "DESTROY" },
+ [FUSE_NOTIFY_REPLY] = { (void *) 1, "NOTIFY_REPLY" },
+ [FUSE_BATCH_FORGET] = { do_batch_forget, "BATCH_FORGET" },
+ [FUSE_READDIRPLUS] = { do_readdirplus, "READDIRPLUS"},
+ [FUSE_RENAME2] = { do_rename2, "RENAME2" },
+ [FUSE_COPY_FILE_RANGE] = { do_copy_file_range, "COPY_FILE_RANGE" },
+ [CUSE_INIT] = { cuse_lowlevel_init, "CUSE_INIT" },
+};
+
+#define FUSE_MAXOP (sizeof(fuse_ll_ops) / sizeof(fuse_ll_ops[0]))
+
+static const char *opname(enum fuse_opcode opcode)
+{
+ if (opcode >= FUSE_MAXOP || !fuse_ll_ops[opcode].name)
+ return "???";
+ else
+ return fuse_ll_ops[opcode].name;
+}
+
+static int fuse_ll_copy_from_pipe(struct fuse_bufvec *dst,
+ struct fuse_bufvec *src)
+{
+ ssize_t res = fuse_buf_copy(dst, src, 0);
+ if (res < 0) {
+ fprintf(stderr, "fuse: copy from pipe: %s\n", strerror(-res));
+ return res;
+ }
+ if ((size_t)res < fuse_buf_size(dst)) {
+ fprintf(stderr, "fuse: copy from pipe: short read\n");
+ return -1;
+ }
+ return 0;
+}
+
+void fuse_session_process_buf(struct fuse_session *se,
+ const struct fuse_buf *buf)
+{
+ fuse_session_process_buf_int(se, buf, NULL);
+}
+
+void fuse_session_process_buf_int(struct fuse_session *se,
+ const struct fuse_buf *buf, struct fuse_chan *ch)
+{
+ const size_t write_header_size = sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_write_in);
+ struct fuse_bufvec bufv = { .buf[0] = *buf, .count = 1 };
+ struct fuse_bufvec tmpbuf = FUSE_BUFVEC_INIT(write_header_size);
+ struct fuse_in_header *in;
+ const void *inarg;
+ struct fuse_req *req;
+ void *mbuf = NULL;
+ int err;
+ int res;
+
+ if (buf->flags & FUSE_BUF_IS_FD) {
+ if (buf->size < tmpbuf.buf[0].size)
+ tmpbuf.buf[0].size = buf->size;
+
+ mbuf = malloc(tmpbuf.buf[0].size);
+ if (mbuf == NULL) {
+ fprintf(stderr, "fuse: failed to allocate header\n");
+ goto clear_pipe;
+ }
+ tmpbuf.buf[0].mem = mbuf;
+
+ res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+ if (res < 0)
+ goto clear_pipe;
+
+ in = mbuf;
+ } else {
+ in = buf->mem;
+ }
+
+ if (se->debug) {
+ fprintf(stderr,
+ "unique: %llu, opcode: %s (%i), nodeid: %llu, insize: %zu, pid: %u\n",
+ (unsigned long long) in->unique,
+ opname((enum fuse_opcode) in->opcode), in->opcode,
+ (unsigned long long) in->nodeid, buf->size, in->pid);
+ }
+
+ req = fuse_ll_alloc_req(se);
+ if (req == NULL) {
+ struct fuse_out_header out = {
+ .unique = in->unique,
+ .error = -ENOMEM,
+ };
+ struct iovec iov = {
+ .iov_base = &out,
+ .iov_len = sizeof(struct fuse_out_header),
+ };
+
+ fuse_send_msg(se, ch, &iov, 1);
+ goto clear_pipe;
+ }
+
+ req->unique = in->unique;
+ req->ctx.uid = in->uid;
+ req->ctx.gid = in->gid;
+ req->ctx.pid = in->pid;
+ req->ch = ch ? fuse_chan_get(ch) : NULL;
+
+ err = EIO;
+ if (!se->got_init) {
+ enum fuse_opcode expected;
+
+ expected = se->cuse_data ? CUSE_INIT : FUSE_INIT;
+ if (in->opcode != expected)
+ goto reply_err;
+ } else if (in->opcode == FUSE_INIT || in->opcode == CUSE_INIT)
+ goto reply_err;
+
+ err = EACCES;
+ /* Implement -o allow_root */
+ if (se->deny_others && in->uid != se->owner && in->uid != 0 &&
+ in->opcode != FUSE_INIT && in->opcode != FUSE_READ &&
+ in->opcode != FUSE_WRITE && in->opcode != FUSE_FSYNC &&
+ in->opcode != FUSE_RELEASE && in->opcode != FUSE_READDIR &&
+ in->opcode != FUSE_FSYNCDIR && in->opcode != FUSE_RELEASEDIR &&
+ in->opcode != FUSE_NOTIFY_REPLY &&
+ in->opcode != FUSE_READDIRPLUS)
+ goto reply_err;
+
+ err = ENOSYS;
+ if (in->opcode >= FUSE_MAXOP || !fuse_ll_ops[in->opcode].func)
+ goto reply_err;
+ if (in->opcode != FUSE_INTERRUPT) {
+ struct fuse_req *intr;
+ pthread_mutex_lock(&se->lock);
+ intr = check_interrupt(se, req);
+ list_add_req(req, &se->list);
+ pthread_mutex_unlock(&se->lock);
+ if (intr)
+ fuse_reply_err(intr, EAGAIN);
+ }
+
+ if ((buf->flags & FUSE_BUF_IS_FD) && write_header_size < buf->size &&
+ (in->opcode != FUSE_WRITE || !se->op.write_buf) &&
+ in->opcode != FUSE_NOTIFY_REPLY) {
+ void *newmbuf;
+
+ err = ENOMEM;
+ newmbuf = realloc(mbuf, buf->size);
+ if (newmbuf == NULL)
+ goto reply_err;
+ mbuf = newmbuf;
+
+ tmpbuf = FUSE_BUFVEC_INIT(buf->size - write_header_size);
+ tmpbuf.buf[0].mem = (char *)mbuf + write_header_size;
+
+ res = fuse_ll_copy_from_pipe(&tmpbuf, &bufv);
+ err = -res;
+ if (res < 0)
+ goto reply_err;
+
+ in = mbuf;
+ }
+
+ inarg = (void *) &in[1];
+ if (in->opcode == FUSE_WRITE && se->op.write_buf)
+ do_write_buf(req, in->nodeid, inarg, buf);
+ else if (in->opcode == FUSE_NOTIFY_REPLY)
+ do_notify_reply(req, in->nodeid, inarg, buf);
+ else
+ fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg);
+
+out_free:
+ free(mbuf);
+ return;
+
+reply_err:
+ fuse_reply_err(req, err);
+clear_pipe:
+ if (buf->flags & FUSE_BUF_IS_FD)
+ fuse_ll_clear_pipe(se);
+ goto out_free;
+}
+
+#define LL_OPTION(n,o,v) \
+ { n, offsetof(struct fuse_session, o), v }
+
+static const struct fuse_opt fuse_ll_opts[] = {
+ LL_OPTION("debug", debug, 1),
+ LL_OPTION("-d", debug, 1),
+ LL_OPTION("--debug", debug, 1),
+ LL_OPTION("allow_root", deny_others, 1),
+ FUSE_OPT_END
+};
+
+void fuse_lowlevel_version(void)
+{
+ printf("using FUSE kernel interface version %i.%i\n",
+ FUSE_KERNEL_VERSION, FUSE_KERNEL_MINOR_VERSION);
+ fuse_mount_version();
+}
+
+void fuse_lowlevel_help(void)
+{
+ /* These are not all options, but the ones that are
+ potentially of interest to an end-user */
+ printf(
+" -o allow_other allow access by all users\n"
+" -o allow_root allow access by root\n"
+" -o auto_unmount auto unmount on process termination\n");
+}
+
+void fuse_session_destroy(struct fuse_session *se)
+{
+ struct fuse_ll_pipe *llp;
+
+ if (se->got_init && !se->got_destroy) {
+ if (se->op.destroy)
+ se->op.destroy(se->userdata);
+ }
+ llp = pthread_getspecific(se->pipe_key);
+ if (llp != NULL)
+ fuse_ll_pipe_free(llp);
+ pthread_key_delete(se->pipe_key);
+ pthread_mutex_destroy(&se->lock);
+ free(se->cuse_data);
+ if (se->fd != -1)
+ close(se->fd);
+ destroy_mount_opts(se->mo);
+ free(se);
+}
+
+
+static void fuse_ll_pipe_destructor(void *data)
+{
+ struct fuse_ll_pipe *llp = data;
+ fuse_ll_pipe_free(llp);
+}
+
+int fuse_session_receive_buf(struct fuse_session *se, struct fuse_buf *buf)
+{
+ return fuse_session_receive_buf_int(se, buf, NULL);
+}
+
+int fuse_session_receive_buf_int(struct fuse_session *se, struct fuse_buf *buf,
+ struct fuse_chan *ch)
+{
+ int err;
+ ssize_t res;
+#ifdef HAVE_SPLICE
+ size_t bufsize = se->bufsize;
+ struct fuse_ll_pipe *llp;
+ struct fuse_buf tmpbuf;
+
+ if (se->conn.proto_minor < 14 || !(se->conn.want & FUSE_CAP_SPLICE_READ))
+ goto fallback;
+
+ llp = fuse_ll_get_pipe(se);
+ if (llp == NULL)
+ goto fallback;
+
+ if (llp->size < bufsize) {
+ if (llp->can_grow) {
+ res = fcntl(llp->pipe[0], F_SETPIPE_SZ, bufsize);
+ if (res == -1) {
+ llp->can_grow = 0;
+ goto fallback;
+ }
+ llp->size = res;
+ }
+ if (llp->size < bufsize)
+ goto fallback;
+ }
+
+ res = splice(ch ? ch->fd : se->fd,
+ NULL, llp->pipe[1], NULL, bufsize, 0);
+ err = errno;
+
+ if (fuse_session_exited(se))
+ return 0;
+
+ if (res == -1) {
+ if (err == ENODEV) {
+ /* Filesystem was unmounted, or connection was aborted
+ via /sys/fs/fuse/connections */
+ fuse_session_exit(se);
+ return 0;
+ }
+ if (err != EINTR && err != EAGAIN)
+ perror("fuse: splice from device");
+ return -err;
+ }
+
+ if (res < sizeof(struct fuse_in_header)) {
+ fprintf(stderr, "short splice from fuse device\n");
+ return -EIO;
+ }
+
+ tmpbuf = (struct fuse_buf) {
+ .size = res,
+ .flags = FUSE_BUF_IS_FD,
+ .fd = llp->pipe[0],
+ };
+
+ /*
+ * Don't bother with zero copy for small requests.
+ * fuse_loop_mt() needs to check for FORGET so this more than
+ * just an optimization.
+ */
+ if (res < sizeof(struct fuse_in_header) +
+ sizeof(struct fuse_write_in) + pagesize) {
+ struct fuse_bufvec src = { .buf[0] = tmpbuf, .count = 1 };
+ struct fuse_bufvec dst = { .count = 1 };
+
+ if (!buf->mem) {
+ buf->mem = malloc(se->bufsize);
+ if (!buf->mem) {
+ fprintf(stderr,
+ "fuse: failed to allocate read buffer\n");
+ return -ENOMEM;
+ }
+ }
+ buf->size = se->bufsize;
+ buf->flags = 0;
+ dst.buf[0] = *buf;
+
+ res = fuse_buf_copy(&dst, &src, 0);
+ if (res < 0) {
+ fprintf(stderr, "fuse: copy from pipe: %s\n",
+ strerror(-res));
+ fuse_ll_clear_pipe(se);
+ return res;
+ }
+ if (res < tmpbuf.size) {
+ fprintf(stderr, "fuse: copy from pipe: short read\n");
+ fuse_ll_clear_pipe(se);
+ return -EIO;
+ }
+ assert(res == tmpbuf.size);
+
+ } else {
+ /* Don't overwrite buf->mem, as that would cause a leak */
+ buf->fd = tmpbuf.fd;
+ buf->flags = tmpbuf.flags;
+ }
+ buf->size = tmpbuf.size;
+
+ return res;
+
+fallback:
+#endif
+ if (!buf->mem) {
+ buf->mem = malloc(se->bufsize);
+ if (!buf->mem) {
+ fprintf(stderr,
+ "fuse: failed to allocate read buffer\n");
+ return -ENOMEM;
+ }
+ }
+
+restart:
+ res = read(ch ? ch->fd : se->fd, buf->mem, se->bufsize);
+ err = errno;
+
+ if (fuse_session_exited(se))
+ return 0;
+ if (res == -1) {
+ /* ENOENT means the operation was interrupted, it's safe
+ to restart */
+ if (err == ENOENT)
+ goto restart;
+
+ if (err == ENODEV) {
+ /* Filesystem was unmounted, or connection was aborted
+ via /sys/fs/fuse/connections */
+ fuse_session_exit(se);
+ return 0;
+ }
+ /* Errors occurring during normal operation: EINTR (read
+ interrupted), EAGAIN (nonblocking I/O), ENODEV (filesystem
+ umounted) */
+ if (err != EINTR && err != EAGAIN)
+ perror("fuse: reading device");
+ return -err;
+ }
+ if ((size_t) res < sizeof(struct fuse_in_header)) {
+ fprintf(stderr, "short read on fuse device\n");
+ return -EIO;
+ }
+
+ buf->size = res;
+
+ return res;
+}
+
+struct fuse_session *fuse_session_new(struct fuse_args *args,
+ const struct fuse_lowlevel_ops *op,
+ size_t op_size, void *userdata)
+{
+ int err;
+ struct fuse_session *se;
+ struct mount_opts *mo;
+
+ if (sizeof(struct fuse_lowlevel_ops) < op_size) {
+ fprintf(stderr, "fuse: warning: library too old, some operations may not work\n");
+ op_size = sizeof(struct fuse_lowlevel_ops);
+ }
+
+ if (args->argc == 0) {
+ fprintf(stderr, "fuse: empty argv passed to fuse_session_new().\n");
+ return NULL;
+ }
+
+ se = (struct fuse_session *) calloc(1, sizeof(struct fuse_session));
+ if (se == NULL) {
+ fprintf(stderr, "fuse: failed to allocate fuse object\n");
+ goto out1;
+ }
+ se->fd = -1;
+ se->conn.max_write = UINT_MAX;
+ se->conn.max_readahead = UINT_MAX;
+
+ /* Parse options */
+ if(fuse_opt_parse(args, se, fuse_ll_opts, NULL) == -1)
+ goto out2;
+ if(se->deny_others) {
+ /* Allowing access only by root is done by instructing
+ * kernel to allow access by everyone, and then restricting
+ * access to root and mountpoint owner in libfuse.
+ */
+ // We may be adding the option a second time, but
+ // that doesn't hurt.
+ if(fuse_opt_add_arg(args, "-oallow_other") == -1)
+ goto out2;
+ }
+ mo = parse_mount_opts(args);
+ if (mo == NULL)
+ goto out3;
+
+ if(args->argc == 1 &&
+ args->argv[0][0] == '-') {
+ fprintf(stderr, "fuse: warning: argv[0] looks like an option, but "
+ "will be ignored\n");
+ } else if (args->argc != 1) {
+ int i;
+ fprintf(stderr, "fuse: unknown option(s): `");
+ for(i = 1; i < args->argc-1; i++)
+ fprintf(stderr, "%s ", args->argv[i]);
+ fprintf(stderr, "%s'\n", args->argv[i]);
+ goto out4;
+ }
+
+ if (se->debug)
+ fprintf(stderr, "FUSE library version: %s\n", PACKAGE_VERSION);
+
+ se->bufsize = FUSE_MAX_MAX_PAGES * getpagesize() +
+ FUSE_BUFFER_HEADER_SIZE;
+
+ list_init_req(&se->list);
+ list_init_req(&se->interrupts);
+ list_init_nreq(&se->notify_list);
+ se->notify_ctr = 1;
+ fuse_mutex_init(&se->lock);
+
+ err = pthread_key_create(&se->pipe_key, fuse_ll_pipe_destructor);
+ if (err) {
+ fprintf(stderr, "fuse: failed to create thread specific key: %s\n",
+ strerror(err));
+ goto out5;
+ }
+
+ memcpy(&se->op, op, op_size);
+ se->owner = getuid();
+ se->userdata = userdata;
+
+ se->mo = mo;
+ return se;
+
+out5:
+ pthread_mutex_destroy(&se->lock);
+out4:
+ fuse_opt_free_args(args);
+out3:
+ free(mo);
+out2:
+ free(se);
+out1:
+ return NULL;
+}
+
+int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
+{
+ int fd;
+
+ /*
+ * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
+ * would ensue.
+ */
+ do {
+ fd = open("/dev/null", O_RDWR);
+ if (fd > 2)
+ close(fd);
+ } while (fd >= 0 && fd <= 2);
+
+ /*
+ * To allow FUSE daemons to run without privileges, the caller may open
+ * /dev/fuse before launching the file system and pass on the file
+ * descriptor by specifying /dev/fd/N as the mount point. Note that the
+ * parent process takes care of performing the mount in this case.
+ */
+ fd = fuse_mnt_parse_fuse_fd(mountpoint);
+ if (fd != -1) {
+ if (fcntl(fd, F_GETFD) == -1) {
+ fprintf(stderr,
+ "fuse: Invalid file descriptor /dev/fd/%u\n",
+ fd);
+ return -1;
+ }
+ se->fd = fd;
+ return 0;
+ }
+
+ /* Open channel */
+ fd = fuse_kern_mount(mountpoint, se->mo);
+ if (fd == -1)
+ return -1;
+ se->fd = fd;
+
+ /* Save mountpoint */
+ se->mountpoint = strdup(mountpoint);
+ if (se->mountpoint == NULL)
+ goto error_out;
+
+ return 0;
+
+error_out:
+ fuse_kern_unmount(mountpoint, fd);
+ return -1;
+}
+
+int fuse_session_fd(struct fuse_session *se)
+{
+ return se->fd;
+}
+
+void fuse_session_unmount(struct fuse_session *se)
+{
+ if (se->mountpoint != NULL) {
+ fuse_kern_unmount(se->mountpoint, se->fd);
+ free(se->mountpoint);
+ se->mountpoint = NULL;
+ }
+}
+
+#ifdef linux
+int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+{
+ char *buf;
+ size_t bufsize = 1024;
+ char path[128];
+ int ret;
+ int fd;
+ unsigned long pid = req->ctx.pid;
+ char *s;
+
+ sprintf(path, "/proc/%lu/task/%lu/status", pid, pid);
+
+retry:
+ buf = malloc(bufsize);
+ if (buf == NULL)
+ return -ENOMEM;
+
+ ret = -EIO;
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ goto out_free;
+
+ ret = read(fd, buf, bufsize);
+ close(fd);
+ if (ret < 0) {
+ ret = -EIO;
+ goto out_free;
+ }
+
+ if ((size_t)ret == bufsize) {
+ free(buf);
+ bufsize *= 4;
+ goto retry;
+ }
+
+ ret = -EIO;
+ s = strstr(buf, "\nGroups:");
+ if (s == NULL)
+ goto out_free;
+
+ s += 8;
+ ret = 0;
+ while (1) {
+ char *end;
+ unsigned long val = strtoul(s, &end, 0);
+ if (end == s)
+ break;
+
+ s = end;
+ if (ret < size)
+ list[ret] = val;
+ ret++;
+ }
+
+out_free:
+ free(buf);
+ return ret;
+}
+#else /* linux */
+/*
+ * This is currently not implemented on other than Linux...
+ */
+int fuse_req_getgroups(fuse_req_t req, int size, gid_t list[])
+{
+ (void) req; (void) size; (void) list;
+ return -ENOSYS;
+}
+#endif
+
+void fuse_session_exit(struct fuse_session *se)
+{
+ se->exited = 1;
+}
+
+void fuse_session_reset(struct fuse_session *se)
+{
+ se->exited = 0;
+ se->error = 0;
+}
+
+int fuse_session_exited(struct fuse_session *se)
+{
+ return se->exited;
+}
diff --git a/lib/fuse_misc.h b/lib/fuse_misc.h
new file mode 100644
index 0000000..2f6663e
--- /dev/null
+++ b/lib/fuse_misc.h
@@ -0,0 +1,59 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#include <pthread.h>
+
+/*
+ Versioned symbols cannot be used in some cases because it
+ - confuse the dynamic linker in uClibc
+ - not supported on MacOSX (in MachO binary format)
+*/
+#if (!defined(__UCLIBC__) && !defined(__APPLE__))
+#define FUSE_SYMVER(x) __asm__(x)
+#else
+#define FUSE_SYMVER(x)
+#endif
+
+#ifndef USE_UCLIBC
+#define fuse_mutex_init(mut) pthread_mutex_init(mut, NULL)
+#else
+/* Is this hack still needed? */
+static inline void fuse_mutex_init(pthread_mutex_t *mut)
+{
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP);
+ pthread_mutex_init(mut, &attr);
+ pthread_mutexattr_destroy(&attr);
+}
+#endif
+
+#ifdef HAVE_STRUCT_STAT_ST_ATIM
+/* Linux */
+#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atim.tv_nsec)
+#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctim.tv_nsec)
+#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtim.tv_nsec)
+#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atim.tv_nsec = (val)
+#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctim.tv_nsec = (val)
+#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtim.tv_nsec = (val)
+#elif defined(HAVE_STRUCT_STAT_ST_ATIMESPEC)
+/* FreeBSD */
+#define ST_ATIM_NSEC(stbuf) ((stbuf)->st_atimespec.tv_nsec)
+#define ST_CTIM_NSEC(stbuf) ((stbuf)->st_ctimespec.tv_nsec)
+#define ST_MTIM_NSEC(stbuf) ((stbuf)->st_mtimespec.tv_nsec)
+#define ST_ATIM_NSEC_SET(stbuf, val) (stbuf)->st_atimespec.tv_nsec = (val)
+#define ST_CTIM_NSEC_SET(stbuf, val) (stbuf)->st_ctimespec.tv_nsec = (val)
+#define ST_MTIM_NSEC_SET(stbuf, val) (stbuf)->st_mtimespec.tv_nsec = (val)
+#else
+#define ST_ATIM_NSEC(stbuf) 0
+#define ST_CTIM_NSEC(stbuf) 0
+#define ST_MTIM_NSEC(stbuf) 0
+#define ST_ATIM_NSEC_SET(stbuf, val) do { } while (0)
+#define ST_CTIM_NSEC_SET(stbuf, val) do { } while (0)
+#define ST_MTIM_NSEC_SET(stbuf, val) do { } while (0)
+#endif
diff --git a/lib/fuse_opt.c b/lib/fuse_opt.c
new file mode 100644
index 0000000..f6cae4f
--- /dev/null
+++ b/lib/fuse_opt.c
@@ -0,0 +1,422 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Implementation of option parsing routines (dealing with `struct
+ fuse_args`).
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#include "config.h"
+#include "fuse_opt.h"
+#include "fuse_misc.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+struct fuse_opt_context {
+ void *data;
+ const struct fuse_opt *opt;
+ fuse_opt_proc_t proc;
+ int argctr;
+ int argc;
+ char **argv;
+ struct fuse_args outargs;
+ char *opts;
+ int nonopt;
+};
+
+void fuse_opt_free_args(struct fuse_args *args)
+{
+ if (args) {
+ if (args->argv && args->allocated) {
+ int i;
+ for (i = 0; i < args->argc; i++)
+ free(args->argv[i]);
+ free(args->argv);
+ }
+ args->argc = 0;
+ args->argv = NULL;
+ args->allocated = 0;
+ }
+}
+
+static int alloc_failed(void)
+{
+ fprintf(stderr, "fuse: memory allocation failed\n");
+ return -1;
+}
+
+int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
+{
+ char **newargv;
+ char *newarg;
+
+ assert(!args->argv || args->allocated);
+
+ newarg = strdup(arg);
+ if (!newarg)
+ return alloc_failed();
+
+ newargv = realloc(args->argv, (args->argc + 2) * sizeof(char *));
+ if (!newargv) {
+ free(newarg);
+ return alloc_failed();
+ }
+
+ args->argv = newargv;
+ args->allocated = 1;
+ args->argv[args->argc++] = newarg;
+ args->argv[args->argc] = NULL;
+ return 0;
+}
+
+static int fuse_opt_insert_arg_common(struct fuse_args *args, int pos,
+ const char *arg)
+{
+ assert(pos <= args->argc);
+ if (fuse_opt_add_arg(args, arg) == -1)
+ return -1;
+
+ if (pos != args->argc - 1) {
+ char *newarg = args->argv[args->argc - 1];
+ memmove(&args->argv[pos + 1], &args->argv[pos],
+ sizeof(char *) * (args->argc - pos - 1));
+ args->argv[pos] = newarg;
+ }
+ return 0;
+}
+
+int fuse_opt_insert_arg(struct fuse_args *args, int pos, const char *arg)
+{
+ return fuse_opt_insert_arg_common(args, pos, arg);
+}
+
+static int next_arg(struct fuse_opt_context *ctx, const char *opt)
+{
+ if (ctx->argctr + 1 >= ctx->argc) {
+ fprintf(stderr, "fuse: missing argument after `%s'\n", opt);
+ return -1;
+ }
+ ctx->argctr++;
+ return 0;
+}
+
+static int add_arg(struct fuse_opt_context *ctx, const char *arg)
+{
+ return fuse_opt_add_arg(&ctx->outargs, arg);
+}
+
+static int add_opt_common(char **opts, const char *opt, int esc)
+{
+ unsigned oldlen = *opts ? strlen(*opts) : 0;
+ char *d = realloc(*opts, oldlen + 1 + strlen(opt) * 2 + 1);
+
+ if (!d)
+ return alloc_failed();
+
+ *opts = d;
+ if (oldlen) {
+ d += oldlen;
+ *d++ = ',';
+ }
+
+ for (; *opt; opt++) {
+ if (esc && (*opt == ',' || *opt == '\\'))
+ *d++ = '\\';
+ *d++ = *opt;
+ }
+ *d = '\0';
+
+ return 0;
+}
+
+int fuse_opt_add_opt(char **opts, const char *opt)
+{
+ return add_opt_common(opts, opt, 0);
+}
+
+int fuse_opt_add_opt_escaped(char **opts, const char *opt)
+{
+ return add_opt_common(opts, opt, 1);
+}
+
+static int add_opt(struct fuse_opt_context *ctx, const char *opt)
+{
+ return add_opt_common(&ctx->opts, opt, 1);
+}
+
+static int call_proc(struct fuse_opt_context *ctx, const char *arg, int key,
+ int iso)
+{
+ if (key == FUSE_OPT_KEY_DISCARD)
+ return 0;
+
+ if (key != FUSE_OPT_KEY_KEEP && ctx->proc) {
+ int res = ctx->proc(ctx->data, arg, key, &ctx->outargs);
+ if (res == -1 || !res)
+ return res;
+ }
+ if (iso)
+ return add_opt(ctx, arg);
+ else
+ return add_arg(ctx, arg);
+}
+
+static int match_template(const char *t, const char *arg, unsigned *sepp)
+{
+ int arglen = strlen(arg);
+ const char *sep = strchr(t, '=');
+ sep = sep ? sep : strchr(t, ' ');
+ if (sep && (!sep[1] || sep[1] == '%')) {
+ int tlen = sep - t;
+ if (sep[0] == '=')
+ tlen ++;
+ if (arglen >= tlen && strncmp(arg, t, tlen) == 0) {
+ *sepp = sep - t;
+ return 1;
+ }
+ }
+ if (strcmp(t, arg) == 0) {
+ *sepp = 0;
+ return 1;
+ }
+ return 0;
+}
+
+static const struct fuse_opt *find_opt(const struct fuse_opt *opt,
+ const char *arg, unsigned *sepp)
+{
+ for (; opt && opt->templ; opt++)
+ if (match_template(opt->templ, arg, sepp))
+ return opt;
+ return NULL;
+}
+
+int fuse_opt_match(const struct fuse_opt *opts, const char *opt)
+{
+ unsigned dummy;
+ return find_opt(opts, opt, &dummy) ? 1 : 0;
+}
+
+static int process_opt_param(void *var, const char *format, const char *param,
+ const char *arg)
+{
+ assert(format[0] == '%');
+ if (format[1] == 's') {
+ char **s = var;
+ char *copy = strdup(param);
+ if (!copy)
+ return alloc_failed();
+
+ free(*s);
+ *s = copy;
+ } else {
+ if (sscanf(param, format, var) != 1) {
+ fprintf(stderr, "fuse: invalid parameter in option `%s'\n", arg);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int process_opt(struct fuse_opt_context *ctx,
+ const struct fuse_opt *opt, unsigned sep,
+ const char *arg, int iso)
+{
+ if (opt->offset == -1U) {
+ if (call_proc(ctx, arg, opt->value, iso) == -1)
+ return -1;
+ } else {
+ void *var = (char *)ctx->data + opt->offset;
+ if (sep && opt->templ[sep + 1]) {
+ const char *param = arg + sep;
+ if (opt->templ[sep] == '=')
+ param ++;
+ if (process_opt_param(var, opt->templ + sep + 1,
+ param, arg) == -1)
+ return -1;
+ } else
+ *(int *)var = opt->value;
+ }
+ return 0;
+}
+
+static int process_opt_sep_arg(struct fuse_opt_context *ctx,
+ const struct fuse_opt *opt, unsigned sep,
+ const char *arg, int iso)
+{
+ int res;
+ char *newarg;
+ char *param;
+
+ if (next_arg(ctx, arg) == -1)
+ return -1;
+
+ param = ctx->argv[ctx->argctr];
+ newarg = malloc(sep + strlen(param) + 1);
+ if (!newarg)
+ return alloc_failed();
+
+ memcpy(newarg, arg, sep);
+ strcpy(newarg + sep, param);
+ res = process_opt(ctx, opt, sep, newarg, iso);
+ free(newarg);
+
+ return res;
+}
+
+static int process_gopt(struct fuse_opt_context *ctx, const char *arg, int iso)
+{
+ unsigned sep;
+ const struct fuse_opt *opt = find_opt(ctx->opt, arg, &sep);
+ if (opt) {
+ for (; opt; opt = find_opt(opt + 1, arg, &sep)) {
+ int res;
+ if (sep && opt->templ[sep] == ' ' && !arg[sep])
+ res = process_opt_sep_arg(ctx, opt, sep, arg,
+ iso);
+ else
+ res = process_opt(ctx, opt, sep, arg, iso);
+ if (res == -1)
+ return -1;
+ }
+ return 0;
+ } else
+ return call_proc(ctx, arg, FUSE_OPT_KEY_OPT, iso);
+}
+
+static int process_real_option_group(struct fuse_opt_context *ctx, char *opts)
+{
+ char *s = opts;
+ char *d = s;
+ int end = 0;
+
+ while (!end) {
+ if (*s == '\0')
+ end = 1;
+ if (*s == ',' || end) {
+ int res;
+
+ *d = '\0';
+ res = process_gopt(ctx, opts, 1);
+ if (res == -1)
+ return -1;
+ d = opts;
+ } else {
+ if (s[0] == '\\' && s[1] != '\0') {
+ s++;
+ if (s[0] >= '0' && s[0] <= '3' &&
+ s[1] >= '0' && s[1] <= '7' &&
+ s[2] >= '0' && s[2] <= '7') {
+ *d++ = (s[0] - '0') * 0100 +
+ (s[1] - '0') * 0010 +
+ (s[2] - '0');
+ s += 2;
+ } else {
+ *d++ = *s;
+ }
+ } else {
+ *d++ = *s;
+ }
+ }
+ s++;
+ }
+
+ return 0;
+}
+
+static int process_option_group(struct fuse_opt_context *ctx, const char *opts)
+{
+ int res;
+ char *copy = strdup(opts);
+
+ if (!copy) {
+ fprintf(stderr, "fuse: memory allocation failed\n");
+ return -1;
+ }
+ res = process_real_option_group(ctx, copy);
+ free(copy);
+ return res;
+}
+
+static int process_one(struct fuse_opt_context *ctx, const char *arg)
+{
+ if (ctx->nonopt || arg[0] != '-')
+ return call_proc(ctx, arg, FUSE_OPT_KEY_NONOPT, 0);
+ else if (arg[1] == 'o') {
+ if (arg[2])
+ return process_option_group(ctx, arg + 2);
+ else {
+ if (next_arg(ctx, arg) == -1)
+ return -1;
+
+ return process_option_group(ctx,
+ ctx->argv[ctx->argctr]);
+ }
+ } else if (arg[1] == '-' && !arg[2]) {
+ if (add_arg(ctx, arg) == -1)
+ return -1;
+ ctx->nonopt = ctx->outargs.argc;
+ return 0;
+ } else
+ return process_gopt(ctx, arg, 0);
+}
+
+static int opt_parse(struct fuse_opt_context *ctx)
+{
+ if (ctx->argc) {
+ if (add_arg(ctx, ctx->argv[0]) == -1)
+ return -1;
+ }
+
+ for (ctx->argctr = 1; ctx->argctr < ctx->argc; ctx->argctr++)
+ if (process_one(ctx, ctx->argv[ctx->argctr]) == -1)
+ return -1;
+
+ if (ctx->opts) {
+ if (fuse_opt_insert_arg(&ctx->outargs, 1, "-o") == -1 ||
+ fuse_opt_insert_arg(&ctx->outargs, 2, ctx->opts) == -1)
+ return -1;
+ }
+
+ /* If option separator ("--") is the last argument, remove it */
+ if (ctx->nonopt && ctx->nonopt == ctx->outargs.argc &&
+ strcmp(ctx->outargs.argv[ctx->outargs.argc - 1], "--") == 0) {
+ free(ctx->outargs.argv[ctx->outargs.argc - 1]);
+ ctx->outargs.argv[--ctx->outargs.argc] = NULL;
+ }
+
+ return 0;
+}
+
+int fuse_opt_parse(struct fuse_args *args, void *data,
+ const struct fuse_opt opts[], fuse_opt_proc_t proc)
+{
+ int res;
+ struct fuse_opt_context ctx = {
+ .data = data,
+ .opt = opts,
+ .proc = proc,
+ };
+
+ if (!args || !args->argv || !args->argc)
+ return 0;
+
+ ctx.argc = args->argc;
+ ctx.argv = args->argv;
+
+ res = opt_parse(&ctx);
+ if (res != -1) {
+ struct fuse_args tmp = *args;
+ *args = ctx.outargs;
+ ctx.outargs = tmp;
+ }
+ free(ctx.opts);
+ fuse_opt_free_args(&ctx.outargs);
+ return res;
+}
diff --git a/lib/fuse_signals.c b/lib/fuse_signals.c
new file mode 100644
index 0000000..e6f7112
--- /dev/null
+++ b/lib/fuse_signals.c
@@ -0,0 +1,91 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Utility functions for setting signal handlers.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#include "config.h"
+#include "fuse_lowlevel.h"
+#include "fuse_i.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+
+static struct fuse_session *fuse_instance;
+
+static void exit_handler(int sig)
+{
+ if (fuse_instance) {
+ fuse_session_exit(fuse_instance);
+ if(sig <= 0) {
+ fprintf(stderr, "assertion error: signal value <= 0\n");
+ abort();
+ }
+ fuse_instance->error = sig;
+ }
+}
+
+static void do_nothing(int sig)
+{
+ (void) sig;
+}
+
+static int set_one_signal_handler(int sig, void (*handler)(int), int remove)
+{
+ struct sigaction sa;
+ struct sigaction old_sa;
+
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_handler = remove ? SIG_DFL : handler;
+ sigemptyset(&(sa.sa_mask));
+ sa.sa_flags = 0;
+
+ if (sigaction(sig, NULL, &old_sa) == -1) {
+ perror("fuse: cannot get old signal handler");
+ return -1;
+ }
+
+ if (old_sa.sa_handler == (remove ? handler : SIG_DFL) &&
+ sigaction(sig, &sa, NULL) == -1) {
+ perror("fuse: cannot set signal handler");
+ return -1;
+ }
+ return 0;
+}
+
+int fuse_set_signal_handlers(struct fuse_session *se)
+{
+ /* If we used SIG_IGN instead of the do_nothing function,
+ then we would be unable to tell if we set SIG_IGN (and
+ thus should reset to SIG_DFL in fuse_remove_signal_handlers)
+ or if it was already set to SIG_IGN (and should be left
+ untouched. */
+ if (set_one_signal_handler(SIGHUP, exit_handler, 0) == -1 ||
+ set_one_signal_handler(SIGINT, exit_handler, 0) == -1 ||
+ set_one_signal_handler(SIGTERM, exit_handler, 0) == -1 ||
+ set_one_signal_handler(SIGPIPE, do_nothing, 0) == -1)
+ return -1;
+
+ fuse_instance = se;
+ return 0;
+}
+
+void fuse_remove_signal_handlers(struct fuse_session *se)
+{
+ if (fuse_instance != se)
+ fprintf(stderr,
+ "fuse: fuse_remove_signal_handlers: unknown session\n");
+ else
+ fuse_instance = NULL;
+
+ set_one_signal_handler(SIGHUP, exit_handler, 1);
+ set_one_signal_handler(SIGINT, exit_handler, 1);
+ set_one_signal_handler(SIGTERM, exit_handler, 1);
+ set_one_signal_handler(SIGPIPE, do_nothing, 1);
+}
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
new file mode 100644
index 0000000..141deca
--- /dev/null
+++ b/lib/fuse_versionscript
@@ -0,0 +1,162 @@
+FUSE_3.0 {
+ global:
+ fuse_destroy;
+ fuse_exit;
+ fuse_loop;
+ fuse_loop_mt;
+ fuse_reply_attr;
+ fuse_reply_buf;
+ fuse_reply_entry;
+ fuse_reply_err;
+ fuse_reply_none;
+ fuse_reply_readlink;
+ fuse_reply_write;
+ fuse_reply_xattr;
+ fuse_req_userdata;
+ fuse_session_destroy;
+ fuse_session_exit;
+ fuse_session_exited;
+ fuse_session_loop;
+ fuse_session_loop_mt;
+ fuse_session_reset;
+ fuse_session_fd;
+ fuse_opt_parse;
+ fuse_opt_add_opt;
+ fuse_opt_add_arg;
+ fuse_opt_free_args;
+ fuse_opt_match;
+ fuse_parse_cmdline;
+ fuse_remove_signal_handlers;
+ fuse_reply_create;
+ fuse_reply_open;
+ fuse_reply_statfs;
+ fuse_set_signal_handlers;
+ fuse_add_direntry;
+ fuse_add_direntry_plus;
+ fuse_daemonize;
+ fuse_get_session;
+ fuse_interrupted;
+ fuse_session_new;
+ fuse_main_real;
+ fuse_mount;
+ fuse_session_mount;
+ fuse_new;
+ fuse_opt_insert_arg;
+ fuse_reply_lock;
+ fuse_req_interrupt_func;
+ fuse_req_interrupted;
+ fuse_unmount;
+ fuse_session_unmount;
+ fuse_fs_access;
+ fuse_fs_bmap;
+ fuse_fs_chmod;
+ fuse_fs_chown;
+ fuse_fs_create;
+ fuse_fs_destroy;
+ fuse_fs_flush;
+ fuse_fs_fsync;
+ fuse_fs_fsyncdir;
+ fuse_fs_getattr;
+ fuse_fs_getxattr;
+ fuse_fs_init;
+ fuse_fs_link;
+ fuse_fs_listxattr;
+ fuse_fs_lock;
+ fuse_fs_mkdir;
+ fuse_fs_mknod;
+ fuse_fs_new;
+ fuse_fs_open;
+ fuse_fs_opendir;
+ fuse_fs_read;
+ fuse_fs_readdir;
+ fuse_fs_readlink;
+ fuse_fs_release;
+ fuse_fs_releasedir;
+ fuse_fs_removexattr;
+ fuse_fs_rename;
+ fuse_fs_rmdir;
+ fuse_fs_setxattr;
+ fuse_fs_statfs;
+ fuse_fs_symlink;
+ fuse_fs_truncate;
+ fuse_fs_unlink;
+ fuse_fs_utimens;
+ fuse_fs_write;
+ fuse_reply_iov;
+ fuse_version;
+ fuse_pkgversion;
+ fuse_reply_bmap;
+ cuse_lowlevel_new;
+ cuse_lowlevel_main;
+ cuse_lowlevel_setup;
+ cuse_lowlevel_teardown;
+ fuse_fs_ioctl;
+ fuse_fs_poll;
+ fuse_get_context;
+ fuse_getgroups;
+ fuse_lowlevel_notify_inval_entry;
+ fuse_lowlevel_notify_inval_inode;
+ fuse_lowlevel_notify_poll;
+ fuse_notify_poll;
+ fuse_opt_add_opt_escaped;
+ fuse_pollhandle_destroy;
+ fuse_reply_ioctl;
+ fuse_reply_ioctl_iov;
+ fuse_reply_ioctl_retry;
+ fuse_reply_poll;
+ fuse_req_ctx;
+ fuse_req_getgroups;
+ fuse_buf_copy;
+ fuse_buf_size;
+ fuse_fs_read_buf;
+ fuse_fs_write_buf;
+ fuse_lowlevel_notify_retrieve;
+ fuse_lowlevel_notify_store;
+ fuse_reply_data;
+ fuse_session_process_buf;
+ fuse_session_receive_buf;
+ fuse_start_cleanup_thread;
+ fuse_stop_cleanup_thread;
+ fuse_clean_cache;
+ fuse_lowlevel_notify_delete;
+ fuse_fs_flock;
+ fuse_fs_fallocate;
+ fuse_lowlevel_help;
+ fuse_lowlevel_version;
+ fuse_cmdline_help;
+ fuse_apply_conn_info_opts;
+ fuse_parse_conn_info_opts;
+
+ local:
+ *;
+};
+
+FUSE_3.1 {
+ global:
+ fuse_lib_help;
+ fuse_invalidate_path;
+ fuse_new_30;
+ fuse_new;
+} FUSE_3.0;
+
+FUSE_3.2 {
+ global:
+ fuse_session_loop_mt;
+ fuse_session_loop_mt_31;
+ fuse_loop_mt;
+ fuse_loop_mt_31;
+} FUSE_3.1;
+
+FUSE_3.3 {
+ global:
+ fuse_open_channel;
+} FUSE_3.2;
+
+FUSE_3.4 {
+ global:
+ fuse_fs_copy_file_range;
+} FUSE_3.3;
+
+# Local Variables:
+# indent-tabs-mode: t
+# End:
diff --git a/lib/helper.c b/lib/helper.c
new file mode 100644
index 0000000..5b80c6e
--- /dev/null
+++ b/lib/helper.c
@@ -0,0 +1,440 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Helper functions to create (simple) standalone programs. With the
+ aid of these functions it should be possible to create full FUSE
+ file system by implementing nothing but the request handlers.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_opt.h"
+#include "fuse_lowlevel.h"
+#include "mount_util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/param.h>
+
+#define FUSE_HELPER_OPT(t, p) \
+ { t, offsetof(struct fuse_cmdline_opts, p), 1 }
+
+static const struct fuse_opt fuse_helper_opts[] = {
+ FUSE_HELPER_OPT("-h", show_help),
+ FUSE_HELPER_OPT("--help", show_help),
+ FUSE_HELPER_OPT("-V", show_version),
+ FUSE_HELPER_OPT("--version", show_version),
+ FUSE_HELPER_OPT("-d", debug),
+ FUSE_HELPER_OPT("debug", debug),
+ FUSE_HELPER_OPT("-d", foreground),
+ FUSE_HELPER_OPT("debug", foreground),
+ FUSE_OPT_KEY("-d", FUSE_OPT_KEY_KEEP),
+ FUSE_OPT_KEY("debug", FUSE_OPT_KEY_KEEP),
+ FUSE_HELPER_OPT("-f", foreground),
+ FUSE_HELPER_OPT("-s", singlethread),
+ FUSE_HELPER_OPT("fsname=", nodefault_subtype),
+ FUSE_OPT_KEY("fsname=", FUSE_OPT_KEY_KEEP),
+#ifndef __FreeBSD__
+ FUSE_HELPER_OPT("subtype=", nodefault_subtype),
+ FUSE_OPT_KEY("subtype=", FUSE_OPT_KEY_KEEP),
+#endif
+ FUSE_HELPER_OPT("clone_fd", clone_fd),
+ FUSE_HELPER_OPT("max_idle_threads=%u", max_idle_threads),
+ FUSE_OPT_END
+};
+
+struct fuse_conn_info_opts {
+ int atomic_o_trunc;
+ int no_remote_posix_lock;
+ int no_remote_flock;
+ int splice_write;
+ int splice_move;
+ int splice_read;
+ int no_splice_write;
+ int no_splice_move;
+ int no_splice_read;
+ int auto_inval_data;
+ int no_auto_inval_data;
+ int no_readdirplus;
+ int no_readdirplus_auto;
+ int async_dio;
+ int no_async_dio;
+ int writeback_cache;
+ int no_writeback_cache;
+ int async_read;
+ int sync_read;
+ unsigned max_write;
+ unsigned max_readahead;
+ unsigned max_background;
+ unsigned congestion_threshold;
+ unsigned time_gran;
+ int set_max_write;
+ int set_max_readahead;
+ int set_max_background;
+ int set_congestion_threshold;
+ int set_time_gran;
+};
+
+#define CONN_OPTION(t, p, v) \
+ { t, offsetof(struct fuse_conn_info_opts, p), v }
+static const struct fuse_opt conn_info_opt_spec[] = {
+ CONN_OPTION("max_write=%u", max_write, 0),
+ CONN_OPTION("max_write=", set_max_write, 1),
+ CONN_OPTION("max_readahead=%u", max_readahead, 0),
+ CONN_OPTION("max_readahead=", set_max_readahead, 1),
+ CONN_OPTION("max_background=%u", max_background, 0),
+ CONN_OPTION("max_background=", set_max_background, 1),
+ CONN_OPTION("congestion_threshold=%u", congestion_threshold, 0),
+ CONN_OPTION("congestion_threshold=", set_congestion_threshold, 1),
+ CONN_OPTION("sync_read", sync_read, 1),
+ CONN_OPTION("async_read", async_read, 1),
+ CONN_OPTION("atomic_o_trunc", atomic_o_trunc, 1),
+ CONN_OPTION("no_remote_lock", no_remote_posix_lock, 1),
+ CONN_OPTION("no_remote_lock", no_remote_flock, 1),
+ CONN_OPTION("no_remote_flock", no_remote_flock, 1),
+ CONN_OPTION("no_remote_posix_lock", no_remote_posix_lock, 1),
+ CONN_OPTION("splice_write", splice_write, 1),
+ CONN_OPTION("no_splice_write", no_splice_write, 1),
+ CONN_OPTION("splice_move", splice_move, 1),
+ CONN_OPTION("no_splice_move", no_splice_move, 1),
+ CONN_OPTION("splice_read", splice_read, 1),
+ CONN_OPTION("no_splice_read", no_splice_read, 1),
+ CONN_OPTION("auto_inval_data", auto_inval_data, 1),
+ CONN_OPTION("no_auto_inval_data", no_auto_inval_data, 1),
+ CONN_OPTION("readdirplus=no", no_readdirplus, 1),
+ CONN_OPTION("readdirplus=yes", no_readdirplus, 0),
+ CONN_OPTION("readdirplus=yes", no_readdirplus_auto, 1),
+ CONN_OPTION("readdirplus=auto", no_readdirplus, 0),
+ CONN_OPTION("readdirplus=auto", no_readdirplus_auto, 0),
+ CONN_OPTION("async_dio", async_dio, 1),
+ CONN_OPTION("no_async_dio", no_async_dio, 1),
+ CONN_OPTION("writeback_cache", writeback_cache, 1),
+ CONN_OPTION("no_writeback_cache", no_writeback_cache, 1),
+ CONN_OPTION("time_gran=%u", time_gran, 0),
+ CONN_OPTION("time_gran=", set_time_gran, 1),
+ FUSE_OPT_END
+};
+
+
+void fuse_cmdline_help(void)
+{
+ printf(" -h --help print help\n"
+ " -V --version print version\n"
+ " -d -o debug enable debug output (implies -f)\n"
+ " -f foreground operation\n"
+ " -s disable multi-threaded operation\n"
+ " -o clone_fd use separate fuse device fd for each thread\n"
+ " (may improve performance)\n"
+ " -o max_idle_threads the maximum number of idle worker threads\n"
+ " allowed (default: 10)\n");
+}
+
+static int fuse_helper_opt_proc(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ (void) outargs;
+ struct fuse_cmdline_opts *opts = data;
+
+ switch (key) {
+ case FUSE_OPT_KEY_NONOPT:
+ if (!opts->mountpoint) {
+ if (fuse_mnt_parse_fuse_fd(arg) != -1) {
+ return fuse_opt_add_opt(&opts->mountpoint, arg);
+ }
+
+ char mountpoint[PATH_MAX] = "";
+ if (realpath(arg, mountpoint) == NULL) {
+ fprintf(stderr,
+ "fuse: bad mount point `%s': %s\n",
+ arg, strerror(errno));
+ return -1;
+ }
+ return fuse_opt_add_opt(&opts->mountpoint, mountpoint);
+ } else {
+ fprintf(stderr, "fuse: invalid argument `%s'\n", arg);
+ return -1;
+ }
+
+ default:
+ /* Pass through unknown options */
+ return 1;
+ }
+}
+
+/* Under FreeBSD, there is no subtype option so this
+ function actually sets the fsname */
+static int add_default_subtype(const char *progname, struct fuse_args *args)
+{
+ int res;
+ char *subtype_opt;
+
+ const char *basename = strrchr(progname, '/');
+ if (basename == NULL)
+ basename = progname;
+ else if (basename[1] != '\0')
+ basename++;
+
+ subtype_opt = (char *) malloc(strlen(basename) + 64);
+ if (subtype_opt == NULL) {
+ fprintf(stderr, "fuse: memory allocation failed\n");
+ return -1;
+ }
+#ifdef __FreeBSD__
+ sprintf(subtype_opt, "-ofsname=%s", basename);
+#else
+ sprintf(subtype_opt, "-osubtype=%s", basename);
+#endif
+ res = fuse_opt_add_arg(args, subtype_opt);
+ free(subtype_opt);
+ return res;
+}
+
+int fuse_parse_cmdline(struct fuse_args *args,
+ struct fuse_cmdline_opts *opts)
+{
+ memset(opts, 0, sizeof(struct fuse_cmdline_opts));
+
+ opts->max_idle_threads = 10;
+
+ if (fuse_opt_parse(args, opts, fuse_helper_opts,
+ fuse_helper_opt_proc) == -1)
+ return -1;
+
+ /* *Linux*: if neither -o subtype nor -o fsname are specified,
+ set subtype to program's basename.
+ *FreeBSD*: if fsname is not specified, set to program's
+ basename. */
+ if (!opts->nodefault_subtype)
+ if (add_default_subtype(args->argv[0], args) == -1)
+ return -1;
+
+ return 0;
+}
+
+
+int fuse_daemonize(int foreground)
+{
+ if (!foreground) {
+ int nullfd;
+ int waiter[2];
+ char completed;
+
+ if (pipe(waiter)) {
+ perror("fuse_daemonize: pipe");
+ return -1;
+ }
+
+ /*
+ * demonize current process by forking it and killing the
+ * parent. This makes current process as a child of 'init'.
+ */
+ switch(fork()) {
+ case -1:
+ perror("fuse_daemonize: fork");
+ return -1;
+ case 0:
+ break;
+ default:
+ (void) read(waiter[0], &completed, sizeof(completed));
+ _exit(0);
+ }
+
+ if (setsid() == -1) {
+ perror("fuse_daemonize: setsid");
+ return -1;
+ }
+
+ (void) chdir("/");
+
+ nullfd = open("/dev/null", O_RDWR, 0);
+ if (nullfd != -1) {
+ (void) dup2(nullfd, 0);
+ (void) dup2(nullfd, 1);
+ (void) dup2(nullfd, 2);
+ if (nullfd > 2)
+ close(nullfd);
+ }
+
+ /* Propagate completion of daemon initialization */
+ completed = 1;
+ (void) write(waiter[1], &completed, sizeof(completed));
+ close(waiter[0]);
+ close(waiter[1]);
+ } else {
+ (void) chdir("/");
+ }
+ return 0;
+}
+
+int fuse_main_real(int argc, char *argv[], const struct fuse_operations *op,
+ size_t op_size, void *user_data)
+{
+ struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+ struct fuse *fuse;
+ struct fuse_cmdline_opts opts;
+ int res;
+
+ if (fuse_parse_cmdline(&args, &opts) != 0)
+ return 1;
+
+ if (opts.show_version) {
+ printf("FUSE library version %s\n", PACKAGE_VERSION);
+ fuse_lowlevel_version();
+ res = 0;
+ goto out1;
+ }
+
+ if (opts.show_help) {
+ if(args.argv[0][0] != '\0')
+ printf("usage: %s [options] <mountpoint>\n\n",
+ args.argv[0]);
+ printf("FUSE options:\n");
+ fuse_cmdline_help();
+ fuse_lib_help(&args);
+ res = 0;
+ goto out1;
+ }
+
+ if (!opts.show_help &&
+ !opts.mountpoint) {
+ fprintf(stderr, "error: no mountpoint specified\n");
+ res = 2;
+ goto out1;
+ }
+
+
+ fuse = fuse_new_31(&args, op, op_size, user_data);
+ if (fuse == NULL) {
+ res = 3;
+ goto out1;
+ }
+
+ if (fuse_mount(fuse,opts.mountpoint) != 0) {
+ res = 4;
+ goto out2;
+ }
+
+ if (fuse_daemonize(opts.foreground) != 0) {
+ res = 5;
+ goto out3;
+ }
+
+ struct fuse_session *se = fuse_get_session(fuse);
+ if (fuse_set_signal_handlers(se) != 0) {
+ res = 6;
+ goto out3;
+ }
+
+ if (opts.singlethread)
+ res = fuse_loop(fuse);
+ else {
+ struct fuse_loop_config loop_config;
+ loop_config.clone_fd = opts.clone_fd;
+ loop_config.max_idle_threads = opts.max_idle_threads;
+ res = fuse_loop_mt_32(fuse, &loop_config);
+ }
+ if (res)
+ res = 7;
+
+ fuse_remove_signal_handlers(se);
+out3:
+ fuse_unmount(fuse);
+out2:
+ fuse_destroy(fuse);
+out1:
+ free(opts.mountpoint);
+ fuse_opt_free_args(&args);
+ return res;
+}
+
+
+void fuse_apply_conn_info_opts(struct fuse_conn_info_opts *opts,
+ struct fuse_conn_info *conn)
+{
+ if(opts->set_max_write)
+ conn->max_write = opts->max_write;
+ if(opts->set_max_background)
+ conn->max_background = opts->max_background;
+ if(opts->set_congestion_threshold)
+ conn->congestion_threshold = opts->congestion_threshold;
+ if(opts->set_time_gran)
+ conn->time_gran = opts->time_gran;
+ if(opts->set_max_readahead)
+ conn->max_readahead = opts->max_readahead;
+
+#define LL_ENABLE(cond,cap) \
+ if (cond) conn->want |= (cap)
+#define LL_DISABLE(cond,cap) \
+ if (cond) conn->want &= ~(cap)
+
+ LL_ENABLE(opts->splice_read, FUSE_CAP_SPLICE_READ);
+ LL_DISABLE(opts->no_splice_read, FUSE_CAP_SPLICE_READ);
+
+ LL_ENABLE(opts->splice_write, FUSE_CAP_SPLICE_WRITE);
+ LL_DISABLE(opts->no_splice_write, FUSE_CAP_SPLICE_WRITE);
+
+ LL_ENABLE(opts->splice_move, FUSE_CAP_SPLICE_MOVE);
+ LL_DISABLE(opts->no_splice_move, FUSE_CAP_SPLICE_MOVE);
+
+ LL_ENABLE(opts->auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+ LL_DISABLE(opts->no_auto_inval_data, FUSE_CAP_AUTO_INVAL_DATA);
+
+ LL_DISABLE(opts->no_readdirplus, FUSE_CAP_READDIRPLUS);
+ LL_DISABLE(opts->no_readdirplus_auto, FUSE_CAP_READDIRPLUS_AUTO);
+
+ LL_ENABLE(opts->async_dio, FUSE_CAP_ASYNC_DIO);
+ LL_DISABLE(opts->no_async_dio, FUSE_CAP_ASYNC_DIO);
+
+ LL_ENABLE(opts->writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+ LL_DISABLE(opts->no_writeback_cache, FUSE_CAP_WRITEBACK_CACHE);
+
+ LL_ENABLE(opts->async_read, FUSE_CAP_ASYNC_READ);
+ LL_DISABLE(opts->sync_read, FUSE_CAP_ASYNC_READ);
+
+ LL_DISABLE(opts->no_remote_posix_lock, FUSE_CAP_POSIX_LOCKS);
+ LL_DISABLE(opts->no_remote_flock, FUSE_CAP_FLOCK_LOCKS);
+}
+
+struct fuse_conn_info_opts* fuse_parse_conn_info_opts(struct fuse_args *args)
+{
+ struct fuse_conn_info_opts *opts;
+
+ opts = calloc(1, sizeof(struct fuse_conn_info_opts));
+ if(opts == NULL) {
+ fprintf(stderr, "calloc failed\n");
+ return NULL;
+ }
+ if(fuse_opt_parse(args, opts, conn_info_opt_spec, NULL) == -1) {
+ free(opts);
+ return NULL;
+ }
+ return opts;
+}
+
+int fuse_open_channel(const char *mountpoint, const char* options)
+{
+ struct mount_opts *opts = NULL;
+ int fd = -1;
+ const char *argv[] = { "", "-o", options };
+ int argc = sizeof(argv) / sizeof(argv[0]);
+ struct fuse_args args = FUSE_ARGS_INIT(argc, (char**) argv);
+
+ opts = parse_mount_opts(&args);
+ if (opts == NULL)
+ return -1;
+
+ fd = fuse_kern_mount(mountpoint, opts);
+ destroy_mount_opts(opts);
+
+ return fd;
+}
diff --git a/lib/meson.build b/lib/meson.build
new file mode 100644
index 0000000..e27feab
--- /dev/null
+++ b/lib/meson.build
@@ -0,0 +1,53 @@
+libfuse_sources = ['fuse.c', 'fuse_i.h', 'fuse_loop.c', 'fuse_loop_mt.c',
+ 'fuse_lowlevel.c', 'fuse_misc.h', 'fuse_opt.c',
+ 'fuse_signals.c', 'buffer.c', 'cuse_lowlevel.c',
+ 'helper.c', 'modules/subdir.c', 'mount_util.c' ]
+
+if host_machine.system().startswith('linux')
+ libfuse_sources += [ 'mount.c' ]
+else
+ libfuse_sources += [ 'mount_bsd.c' ]
+endif
+
+deps = [ thread_dep ]
+if cfg.get('HAVE_ICONV')
+ libfuse_sources += [ 'modules/iconv.c' ]
+ libiconv = cc.find_library('iconv', required: false)
+ if libiconv.found()
+ deps += [ libiconv ]
+ endif
+endif
+
+libdl = cc.find_library('dl', required: false)
+if libdl.found()
+ deps += [ libdl ]
+endif
+
+if host_machine.system().startswith('netbsd')
+ deps += [ cc.find_library('perfuse'),
+ cc.find_library('puffs') ]
+else
+ # Required for clock_gettime before glibc 2.17
+ deps += cc.find_library('rt')
+endif
+
+fusermount_path = join_paths(get_option('prefix'), get_option('bindir'))
+libfuse = library('fuse3', libfuse_sources, version: meson.project_version(),
+ soversion: '3', include_directories: include_dirs,
+ dependencies: deps, install: true,
+ link_depends: 'fuse_versionscript',
+ c_args: [ '-DFUSE_USE_VERSION=34',
+ '-DFUSERMOUNT_DIR="@0@"'.format(fusermount_path) ],
+ link_args: ['-Wl,--version-script,' + meson.current_source_dir()
+ + '/fuse_versionscript' ])
+
+pkg = import('pkgconfig')
+pkg.generate(libraries: [ libfuse, '-lpthread' ],
+ libraries_private: '-ldl',
+ version: meson.project_version(),
+ name: 'fuse3',
+ description: 'Filesystem in Userspace',
+ subdirs: 'fuse3')
+
+libfuse_dep = declare_dependency(include_directories: include_dirs,
+ link_with: libfuse, dependencies: deps)
diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c
new file mode 100644
index 0000000..84f4236
--- /dev/null
+++ b/lib/modules/iconv.c
@@ -0,0 +1,718 @@
+/*
+ fuse iconv module: file name charset conversion
+ Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#include <config.h>
+
+#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <iconv.h>
+#include <pthread.h>
+#include <locale.h>
+#include <langinfo.h>
+
+struct iconv {
+ struct fuse_fs *next;
+ pthread_mutex_t lock;
+ char *from_code;
+ char *to_code;
+ iconv_t tofs;
+ iconv_t fromfs;
+};
+
+struct iconv_dh {
+ struct iconv *ic;
+ void *prev_buf;
+ fuse_fill_dir_t prev_filler;
+};
+
+static struct iconv *iconv_get(void)
+{
+ return fuse_get_context()->private_data;
+}
+
+static int iconv_convpath(struct iconv *ic, const char *path, char **newpathp,
+ int fromfs)
+{
+ size_t pathlen;
+ size_t newpathlen;
+ char *newpath;
+ size_t plen;
+ char *p;
+ size_t res;
+ int err;
+
+ if (path == NULL) {
+ *newpathp = NULL;
+ return 0;
+ }
+
+ pathlen = strlen(path);
+ newpathlen = pathlen * 4;
+ newpath = malloc(newpathlen + 1);
+ if (!newpath)
+ return -ENOMEM;
+
+ plen = newpathlen;
+ p = newpath;
+ pthread_mutex_lock(&ic->lock);
+ do {
+ res = iconv(fromfs ? ic->fromfs : ic->tofs, (char **) &path,
+ &pathlen, &p, &plen);
+ if (res == (size_t) -1) {
+ char *tmp;
+ size_t inc;
+
+ err = -EILSEQ;
+ if (errno != E2BIG)
+ goto err;
+
+ inc = (pathlen + 1) * 4;
+ newpathlen += inc;
+ tmp = realloc(newpath, newpathlen + 1);
+ err = -ENOMEM;
+ if (!tmp)
+ goto err;
+
+ p = tmp + (p - newpath);
+ plen += inc;
+ newpath = tmp;
+ }
+ } while (res == (size_t) -1);
+ pthread_mutex_unlock(&ic->lock);
+ *p = '\0';
+ *newpathp = newpath;
+ return 0;
+
+err:
+ iconv(fromfs ? ic->fromfs : ic->tofs, NULL, NULL, NULL, NULL);
+ pthread_mutex_unlock(&ic->lock);
+ free(newpath);
+ return err;
+}
+
+static int iconv_getattr(const char *path, struct stat *stbuf,
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_getattr(ic->next, newpath, stbuf, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_access(const char *path, int mask)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_access(ic->next, newpath, mask);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_readlink(const char *path, char *buf, size_t size)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_readlink(ic->next, newpath, buf, size);
+ if (!err) {
+ char *newlink;
+ err = iconv_convpath(ic, buf, &newlink, 1);
+ if (!err) {
+ strncpy(buf, newlink, size - 1);
+ buf[size - 1] = '\0';
+ free(newlink);
+ }
+ }
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_opendir(const char *path, struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_opendir(ic->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_dir_fill(void *buf, const char *name,
+ const struct stat *stbuf, off_t off,
+ enum fuse_fill_dir_flags flags)
+{
+ struct iconv_dh *dh = buf;
+ char *newname;
+ int res = 0;
+ if (iconv_convpath(dh->ic, name, &newname, 1) == 0) {
+ res = dh->prev_filler(dh->prev_buf, newname, stbuf, off, flags);
+ free(newname);
+ }
+ return res;
+}
+
+static int iconv_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+ off_t offset, struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ struct iconv_dh dh;
+ dh.ic = ic;
+ dh.prev_buf = buf;
+ dh.prev_filler = filler;
+ err = fuse_fs_readdir(ic->next, newpath, &dh, iconv_dir_fill,
+ offset, fi, flags);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_releasedir(const char *path, struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_releasedir(ic->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_mknod(ic->next, newpath, mode, rdev);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_mkdir(const char *path, mode_t mode)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_mkdir(ic->next, newpath, mode);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_unlink(const char *path)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_unlink(ic->next, newpath);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_rmdir(const char *path)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_rmdir(ic->next, newpath);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_symlink(const char *from, const char *to)
+{
+ struct iconv *ic = iconv_get();
+ char *newfrom;
+ char *newto;
+ int err = iconv_convpath(ic, from, &newfrom, 0);
+ if (!err) {
+ err = iconv_convpath(ic, to, &newto, 0);
+ if (!err) {
+ err = fuse_fs_symlink(ic->next, newfrom, newto);
+ free(newto);
+ }
+ free(newfrom);
+ }
+ return err;
+}
+
+static int iconv_rename(const char *from, const char *to, unsigned int flags)
+{
+ struct iconv *ic = iconv_get();
+ char *newfrom;
+ char *newto;
+ int err = iconv_convpath(ic, from, &newfrom, 0);
+ if (!err) {
+ err = iconv_convpath(ic, to, &newto, 0);
+ if (!err) {
+ err = fuse_fs_rename(ic->next, newfrom, newto, flags);
+ free(newto);
+ }
+ free(newfrom);
+ }
+ return err;
+}
+
+static int iconv_link(const char *from, const char *to)
+{
+ struct iconv *ic = iconv_get();
+ char *newfrom;
+ char *newto;
+ int err = iconv_convpath(ic, from, &newfrom, 0);
+ if (!err) {
+ err = iconv_convpath(ic, to, &newto, 0);
+ if (!err) {
+ err = fuse_fs_link(ic->next, newfrom, newto);
+ free(newto);
+ }
+ free(newfrom);
+ }
+ return err;
+}
+
+static int iconv_chmod(const char *path, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_chmod(ic->next, newpath, mode, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_chown(const char *path, uid_t uid, gid_t gid,
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_chown(ic->next, newpath, uid, gid, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_truncate(const char *path, off_t size,
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_truncate(ic->next, newpath, size, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_utimens(const char *path, const struct timespec ts[2],
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_utimens(ic->next, newpath, ts, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_create(const char *path, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_create(ic->next, newpath, mode, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_open_file(const char *path, struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_open(ic->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_read_buf(const char *path, struct fuse_bufvec **bufp,
+ size_t size, off_t offset, struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_read_buf(ic->next, newpath, bufp, size, offset, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_write_buf(const char *path, struct fuse_bufvec *buf,
+ off_t offset, struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_write_buf(ic->next, newpath, buf, offset, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_statfs(const char *path, struct statvfs *stbuf)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_statfs(ic->next, newpath, stbuf);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_flush(const char *path, struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_flush(ic->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_release(const char *path, struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_release(ic->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_fsync(const char *path, int isdatasync,
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_fsync(ic->next, newpath, isdatasync, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_fsyncdir(const char *path, int isdatasync,
+ struct fuse_file_info *fi)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_fsyncdir(ic->next, newpath, isdatasync, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_setxattr(const char *path, const char *name,
+ const char *value, size_t size, int flags)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_setxattr(ic->next, newpath, name, value, size,
+ flags);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_getxattr(const char *path, const char *name, char *value,
+ size_t size)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_getxattr(ic->next, newpath, name, value, size);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_listxattr(const char *path, char *list, size_t size)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_listxattr(ic->next, newpath, list, size);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_removexattr(const char *path, const char *name)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_removexattr(ic->next, newpath, name);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_lock(const char *path, struct fuse_file_info *fi, int cmd,
+ struct flock *lock)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_lock(ic->next, newpath, fi, cmd, lock);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_flock(const char *path, struct fuse_file_info *fi, int op)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_flock(ic->next, newpath, fi, op);
+ free(newpath);
+ }
+ return err;
+}
+
+static int iconv_bmap(const char *path, size_t blocksize, uint64_t *idx)
+{
+ struct iconv *ic = iconv_get();
+ char *newpath;
+ int err = iconv_convpath(ic, path, &newpath, 0);
+ if (!err) {
+ err = fuse_fs_bmap(ic->next, newpath, blocksize, idx);
+ free(newpath);
+ }
+ return err;
+}
+
+static void *iconv_init(struct fuse_conn_info *conn,
+ struct fuse_config *cfg)
+{
+ struct iconv *ic = iconv_get();
+ fuse_fs_init(ic->next, conn, cfg);
+ /* Don't touch cfg->nullpath_ok, we can work with
+ either */
+ return ic;
+}
+
+static void iconv_destroy(void *data)
+{
+ struct iconv *ic = data;
+ fuse_fs_destroy(ic->next);
+ iconv_close(ic->tofs);
+ iconv_close(ic->fromfs);
+ pthread_mutex_destroy(&ic->lock);
+ free(ic->from_code);
+ free(ic->to_code);
+ free(ic);
+}
+
+static const struct fuse_operations iconv_oper = {
+ .destroy = iconv_destroy,
+ .init = iconv_init,
+ .getattr = iconv_getattr,
+ .access = iconv_access,
+ .readlink = iconv_readlink,
+ .opendir = iconv_opendir,
+ .readdir = iconv_readdir,
+ .releasedir = iconv_releasedir,
+ .mknod = iconv_mknod,
+ .mkdir = iconv_mkdir,
+ .symlink = iconv_symlink,
+ .unlink = iconv_unlink,
+ .rmdir = iconv_rmdir,
+ .rename = iconv_rename,
+ .link = iconv_link,
+ .chmod = iconv_chmod,
+ .chown = iconv_chown,
+ .truncate = iconv_truncate,
+ .utimens = iconv_utimens,
+ .create = iconv_create,
+ .open = iconv_open_file,
+ .read_buf = iconv_read_buf,
+ .write_buf = iconv_write_buf,
+ .statfs = iconv_statfs,
+ .flush = iconv_flush,
+ .release = iconv_release,
+ .fsync = iconv_fsync,
+ .fsyncdir = iconv_fsyncdir,
+ .setxattr = iconv_setxattr,
+ .getxattr = iconv_getxattr,
+ .listxattr = iconv_listxattr,
+ .removexattr = iconv_removexattr,
+ .lock = iconv_lock,
+ .flock = iconv_flock,
+ .bmap = iconv_bmap,
+};
+
+static const struct fuse_opt iconv_opts[] = {
+ FUSE_OPT_KEY("-h", 0),
+ FUSE_OPT_KEY("--help", 0),
+ { "from_code=%s", offsetof(struct iconv, from_code), 0 },
+ { "to_code=%s", offsetof(struct iconv, to_code), 1 },
+ FUSE_OPT_END
+};
+
+static void iconv_help(void)
+{
+ char *old = strdup(setlocale(LC_CTYPE, ""));
+ char *charmap = strdup(nl_langinfo(CODESET));
+ setlocale(LC_CTYPE, old);
+ free(old);
+ printf(
+" -o from_code=CHARSET original encoding of file names (default: UTF-8)\n"
+" -o to_code=CHARSET new encoding of the file names (default: %s)\n",
+ charmap);
+ free(charmap);
+}
+
+static int iconv_opt_proc(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ (void) data; (void) arg; (void) outargs;
+
+ if (!key) {
+ iconv_help();
+ return -1;
+ }
+
+ return 1;
+}
+
+static struct fuse_fs *iconv_new(struct fuse_args *args,
+ struct fuse_fs *next[])
+{
+ struct fuse_fs *fs;
+ struct iconv *ic;
+ char *old = NULL;
+ const char *from;
+ const char *to;
+
+ ic = calloc(1, sizeof(struct iconv));
+ if (ic == NULL) {
+ fprintf(stderr, "fuse-iconv: memory allocation failed\n");
+ return NULL;
+ }
+
+ if (fuse_opt_parse(args, ic, iconv_opts, iconv_opt_proc) == -1)
+ goto out_free;
+
+ if (!next[0] || next[1]) {
+ fprintf(stderr, "fuse-iconv: exactly one next filesystem required\n");
+ goto out_free;
+ }
+
+ from = ic->from_code ? ic->from_code : "UTF-8";
+ to = ic->to_code ? ic->to_code : "";
+ /* FIXME: detect charset equivalence? */
+ if (!to[0])
+ old = strdup(setlocale(LC_CTYPE, ""));
+ ic->tofs = iconv_open(from, to);
+ if (ic->tofs == (iconv_t) -1) {
+ fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n",
+ to, from);
+ goto out_free;
+ }
+ ic->fromfs = iconv_open(to, from);
+ if (ic->tofs == (iconv_t) -1) {
+ fprintf(stderr, "fuse-iconv: cannot convert from %s to %s\n",
+ from, to);
+ goto out_iconv_close_to;
+ }
+ if (old) {
+ setlocale(LC_CTYPE, old);
+ free(old);
+ }
+
+ ic->next = next[0];
+ fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
+ if (!fs)
+ goto out_iconv_close_from;
+
+ return fs;
+
+out_iconv_close_from:
+ iconv_close(ic->fromfs);
+out_iconv_close_to:
+ iconv_close(ic->tofs);
+out_free:
+ free(ic->from_code);
+ free(ic->to_code);
+ free(ic);
+ if (old) {
+ setlocale(LC_CTYPE, old);
+ free(old);
+ }
+ return NULL;
+}
+
+FUSE_REGISTER_MODULE(iconv, iconv_new);
diff --git a/lib/modules/subdir.c b/lib/modules/subdir.c
new file mode 100644
index 0000000..f84d8c1
--- /dev/null
+++ b/lib/modules/subdir.c
@@ -0,0 +1,675 @@
+/*
+ fuse subdir module: offset paths with a base directory
+ Copyright (C) 2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB
+*/
+
+#include <config.h>
+
+#include <fuse.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+
+struct subdir {
+ char *base;
+ size_t baselen;
+ int rellinks;
+ struct fuse_fs *next;
+};
+
+static struct subdir *subdir_get(void)
+{
+ return fuse_get_context()->private_data;
+}
+
+static int subdir_addpath(struct subdir *d, const char *path, char **newpathp)
+{
+ char *newpath = NULL;
+
+ if (path != NULL) {
+ unsigned newlen = d->baselen + strlen(path);
+
+ newpath = malloc(newlen + 2);
+ if (!newpath)
+ return -ENOMEM;
+
+ if (path[0] == '/')
+ path++;
+ strcpy(newpath, d->base);
+ strcpy(newpath + d->baselen, path);
+ if (!newpath[0])
+ strcpy(newpath, ".");
+ }
+ *newpathp = newpath;
+
+ return 0;
+}
+
+static int subdir_getattr(const char *path, struct stat *stbuf,
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_getattr(d->next, newpath, stbuf, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_access(const char *path, int mask)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_access(d->next, newpath, mask);
+ free(newpath);
+ }
+ return err;
+}
+
+
+static int count_components(const char *p)
+{
+ int ctr;
+
+ for (; *p == '/'; p++);
+ for (ctr = 0; *p; ctr++) {
+ for (; *p && *p != '/'; p++);
+ for (; *p == '/'; p++);
+ }
+ return ctr;
+}
+
+static void strip_common(const char **sp, const char **tp)
+{
+ const char *s = *sp;
+ const char *t = *tp;
+ do {
+ for (; *s == '/'; s++);
+ for (; *t == '/'; t++);
+ *tp = t;
+ *sp = s;
+ for (; *s == *t && *s && *s != '/'; s++, t++);
+ } while ((*s == *t && *s) || (!*s && *t == '/') || (*s == '/' && !*t));
+}
+
+static void transform_symlink(struct subdir *d, const char *path,
+ char *buf, size_t size)
+{
+ const char *l = buf;
+ size_t llen;
+ char *s;
+ int dotdots;
+ int i;
+
+ if (l[0] != '/' || d->base[0] != '/')
+ return;
+
+ strip_common(&l, &path);
+ if (l - buf < (long) d->baselen)
+ return;
+
+ dotdots = count_components(path);
+ if (!dotdots)
+ return;
+ dotdots--;
+
+ llen = strlen(l);
+ if (dotdots * 3 + llen + 2 > size)
+ return;
+
+ s = buf + dotdots * 3;
+ if (llen)
+ memmove(s, l, llen + 1);
+ else if (!dotdots)
+ strcpy(s, ".");
+ else
+ *s = '\0';
+
+ for (s = buf, i = 0; i < dotdots; i++, s += 3)
+ memcpy(s, "../", 3);
+}
+
+
+static int subdir_readlink(const char *path, char *buf, size_t size)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_readlink(d->next, newpath, buf, size);
+ if (!err && d->rellinks)
+ transform_symlink(d, newpath, buf, size);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_opendir(const char *path, struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_opendir(d->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_readdir(const char *path, void *buf,
+ fuse_fill_dir_t filler, off_t offset,
+ struct fuse_file_info *fi,
+ enum fuse_readdir_flags flags)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_readdir(d->next, newpath, buf, filler, offset,
+ fi, flags);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_releasedir(const char *path, struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_releasedir(d->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_mknod(d->next, newpath, mode, rdev);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_mkdir(const char *path, mode_t mode)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_mkdir(d->next, newpath, mode);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_unlink(const char *path)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_unlink(d->next, newpath);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_rmdir(const char *path)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_rmdir(d->next, newpath);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_symlink(const char *from, const char *path)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_symlink(d->next, from, newpath);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_rename(const char *from, const char *to, unsigned int flags)
+{
+ struct subdir *d = subdir_get();
+ char *newfrom;
+ char *newto;
+ int err = subdir_addpath(d, from, &newfrom);
+ if (!err) {
+ err = subdir_addpath(d, to, &newto);
+ if (!err) {
+ err = fuse_fs_rename(d->next, newfrom, newto, flags);
+ free(newto);
+ }
+ free(newfrom);
+ }
+ return err;
+}
+
+static int subdir_link(const char *from, const char *to)
+{
+ struct subdir *d = subdir_get();
+ char *newfrom;
+ char *newto;
+ int err = subdir_addpath(d, from, &newfrom);
+ if (!err) {
+ err = subdir_addpath(d, to, &newto);
+ if (!err) {
+ err = fuse_fs_link(d->next, newfrom, newto);
+ free(newto);
+ }
+ free(newfrom);
+ }
+ return err;
+}
+
+static int subdir_chmod(const char *path, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_chmod(d->next, newpath, mode, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_chown(const char *path, uid_t uid, gid_t gid,
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_chown(d->next, newpath, uid, gid, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_truncate(const char *path, off_t size,
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_truncate(d->next, newpath, size, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_utimens(const char *path, const struct timespec ts[2],
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_utimens(d->next, newpath, ts, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_create(const char *path, mode_t mode,
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_create(d->next, newpath, mode, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_open(const char *path, struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_open(d->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_read_buf(const char *path, struct fuse_bufvec **bufp,
+ size_t size, off_t offset, struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_read_buf(d->next, newpath, bufp, size, offset, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_write_buf(const char *path, struct fuse_bufvec *buf,
+ off_t offset, struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_write_buf(d->next, newpath, buf, offset, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_statfs(const char *path, struct statvfs *stbuf)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_statfs(d->next, newpath, stbuf);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_flush(const char *path, struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_flush(d->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_release(const char *path, struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_release(d->next, newpath, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_fsync(const char *path, int isdatasync,
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_fsync(d->next, newpath, isdatasync, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_fsyncdir(const char *path, int isdatasync,
+ struct fuse_file_info *fi)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_fsyncdir(d->next, newpath, isdatasync, fi);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_setxattr(const char *path, const char *name,
+ const char *value, size_t size, int flags)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_setxattr(d->next, newpath, name, value, size,
+ flags);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_getxattr(const char *path, const char *name, char *value,
+ size_t size)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_getxattr(d->next, newpath, name, value, size);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_listxattr(const char *path, char *list, size_t size)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_listxattr(d->next, newpath, list, size);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_removexattr(const char *path, const char *name)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_removexattr(d->next, newpath, name);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_lock(const char *path, struct fuse_file_info *fi, int cmd,
+ struct flock *lock)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_lock(d->next, newpath, fi, cmd, lock);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_flock(const char *path, struct fuse_file_info *fi, int op)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_flock(d->next, newpath, fi, op);
+ free(newpath);
+ }
+ return err;
+}
+
+static int subdir_bmap(const char *path, size_t blocksize, uint64_t *idx)
+{
+ struct subdir *d = subdir_get();
+ char *newpath;
+ int err = subdir_addpath(d, path, &newpath);
+ if (!err) {
+ err = fuse_fs_bmap(d->next, newpath, blocksize, idx);
+ free(newpath);
+ }
+ return err;
+}
+
+static void *subdir_init(struct fuse_conn_info *conn,
+ struct fuse_config *cfg)
+{
+ struct subdir *d = subdir_get();
+ fuse_fs_init(d->next, conn, cfg);
+ /* Don't touch cfg->nullpath_ok, we can work with
+ either */
+ return d;
+}
+
+static void subdir_destroy(void *data)
+{
+ struct subdir *d = data;
+ fuse_fs_destroy(d->next);
+ free(d->base);
+ free(d);
+}
+
+static const struct fuse_operations subdir_oper = {
+ .destroy = subdir_destroy,
+ .init = subdir_init,
+ .getattr = subdir_getattr,
+ .access = subdir_access,
+ .readlink = subdir_readlink,
+ .opendir = subdir_opendir,
+ .readdir = subdir_readdir,
+ .releasedir = subdir_releasedir,
+ .mknod = subdir_mknod,
+ .mkdir = subdir_mkdir,
+ .symlink = subdir_symlink,
+ .unlink = subdir_unlink,
+ .rmdir = subdir_rmdir,
+ .rename = subdir_rename,
+ .link = subdir_link,
+ .chmod = subdir_chmod,
+ .chown = subdir_chown,
+ .truncate = subdir_truncate,
+ .utimens = subdir_utimens,
+ .create = subdir_create,
+ .open = subdir_open,
+ .read_buf = subdir_read_buf,
+ .write_buf = subdir_write_buf,
+ .statfs = subdir_statfs,
+ .flush = subdir_flush,
+ .release = subdir_release,
+ .fsync = subdir_fsync,
+ .fsyncdir = subdir_fsyncdir,
+ .setxattr = subdir_setxattr,
+ .getxattr = subdir_getxattr,
+ .listxattr = subdir_listxattr,
+ .removexattr = subdir_removexattr,
+ .lock = subdir_lock,
+ .flock = subdir_flock,
+ .bmap = subdir_bmap,
+};
+
+static const struct fuse_opt subdir_opts[] = {
+ FUSE_OPT_KEY("-h", 0),
+ FUSE_OPT_KEY("--help", 0),
+ { "subdir=%s", offsetof(struct subdir, base), 0 },
+ { "rellinks", offsetof(struct subdir, rellinks), 1 },
+ { "norellinks", offsetof(struct subdir, rellinks), 0 },
+ FUSE_OPT_END
+};
+
+static void subdir_help(void)
+{
+ printf(
+" -o subdir=DIR prepend this directory to all paths (mandatory)\n"
+" -o [no]rellinks transform absolute symlinks to relative\n");
+}
+
+static int subdir_opt_proc(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ (void) data; (void) arg; (void) outargs;
+
+ if (!key) {
+ subdir_help();
+ return -1;
+ }
+
+ return 1;
+}
+
+static struct fuse_fs *subdir_new(struct fuse_args *args,
+ struct fuse_fs *next[])
+{
+ struct fuse_fs *fs;
+ struct subdir *d;
+
+ d = calloc(1, sizeof(struct subdir));
+ if (d == NULL) {
+ fprintf(stderr, "fuse-subdir: memory allocation failed\n");
+ return NULL;
+ }
+
+ if (fuse_opt_parse(args, d, subdir_opts, subdir_opt_proc) == -1)
+ goto out_free;
+
+ if (!next[0] || next[1]) {
+ fprintf(stderr, "fuse-subdir: exactly one next filesystem required\n");
+ goto out_free;
+ }
+
+ if (!d->base) {
+ fprintf(stderr, "fuse-subdir: missing 'subdir' option\n");
+ goto out_free;
+ }
+
+ if (d->base[0] && d->base[strlen(d->base)-1] != '/') {
+ char *tmp = realloc(d->base, strlen(d->base) + 2);
+ if (!tmp) {
+ fprintf(stderr, "fuse-subdir: memory allocation failed\n");
+ goto out_free;
+ }
+ d->base = tmp;
+ strcat(d->base, "/");
+ }
+ d->baselen = strlen(d->base);
+ d->next = next[0];
+ fs = fuse_fs_new(&subdir_oper, sizeof(subdir_oper), d);
+ if (!fs)
+ goto out_free;
+ return fs;
+
+out_free:
+ free(d->base);
+ free(d);
+ return NULL;
+}
+
+FUSE_REGISTER_MODULE(subdir, subdir_new);
diff --git a/lib/mount.c b/lib/mount.c
new file mode 100644
index 0000000..7a18c11
--- /dev/null
+++ b/lib/mount.c
@@ -0,0 +1,602 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Architecture specific file system mounting (Linux).
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_opt.h"
+#include "mount_util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+
+#ifdef __NetBSD__
+#include <perfuse.h>
+
+#define MS_RDONLY MNT_RDONLY
+#define MS_NOSUID MNT_NOSUID
+#define MS_NODEV MNT_NODEV
+#define MS_NOEXEC MNT_NOEXEC
+#define MS_SYNCHRONOUS MNT_SYNCHRONOUS
+#define MS_NOATIME MNT_NOATIME
+
+#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+#endif
+
+#define FUSERMOUNT_PROG "fusermount3"
+#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
+
+#ifndef HAVE_FORK
+#define fork() vfork()
+#endif
+
+#ifndef MS_DIRSYNC
+#define MS_DIRSYNC 128
+#endif
+
+enum {
+ KEY_KERN_FLAG,
+ KEY_KERN_OPT,
+ KEY_FUSERMOUNT_OPT,
+ KEY_SUBTYPE_OPT,
+ KEY_MTAB_OPT,
+ KEY_ALLOW_OTHER,
+ KEY_RO,
+};
+
+struct mount_opts {
+ int allow_other;
+ int flags;
+ int auto_unmount;
+ int blkdev;
+ char *fsname;
+ char *subtype;
+ char *subtype_opt;
+ char *mtab_opts;
+ char *fusermount_opts;
+ char *kernel_opts;
+ unsigned max_read;
+};
+
+#define FUSE_MOUNT_OPT(t, p) { t, offsetof(struct mount_opts, p), 1 }
+
+static const struct fuse_opt fuse_mount_opts[] = {
+ FUSE_MOUNT_OPT("allow_other", allow_other),
+ FUSE_MOUNT_OPT("blkdev", blkdev),
+ FUSE_MOUNT_OPT("auto_unmount", auto_unmount),
+ FUSE_MOUNT_OPT("fsname=%s", fsname),
+ FUSE_MOUNT_OPT("max_read=%u", max_read),
+ FUSE_MOUNT_OPT("subtype=%s", subtype),
+ FUSE_OPT_KEY("allow_other", KEY_KERN_OPT),
+ FUSE_OPT_KEY("auto_unmount", KEY_FUSERMOUNT_OPT),
+ FUSE_OPT_KEY("blkdev", KEY_FUSERMOUNT_OPT),
+ FUSE_OPT_KEY("fsname=", KEY_FUSERMOUNT_OPT),
+ FUSE_OPT_KEY("subtype=", KEY_SUBTYPE_OPT),
+ FUSE_OPT_KEY("blksize=", KEY_KERN_OPT),
+ FUSE_OPT_KEY("default_permissions", KEY_KERN_OPT),
+ FUSE_OPT_KEY("context=", KEY_KERN_OPT),
+ FUSE_OPT_KEY("fscontext=", KEY_KERN_OPT),
+ FUSE_OPT_KEY("defcontext=", KEY_KERN_OPT),
+ FUSE_OPT_KEY("rootcontext=", KEY_KERN_OPT),
+ FUSE_OPT_KEY("max_read=", KEY_KERN_OPT),
+ FUSE_OPT_KEY("user=", KEY_MTAB_OPT),
+ FUSE_OPT_KEY("-r", KEY_RO),
+ FUSE_OPT_KEY("ro", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("rw", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("suid", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("nosuid", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("dev", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("nodev", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("exec", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("noexec", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("async", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("sync", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("dirsync", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("atime", KEY_KERN_FLAG),
+ FUSE_OPT_KEY("noatime", KEY_KERN_FLAG),
+ FUSE_OPT_END
+};
+
+static void exec_fusermount(const char *argv[])
+{
+ execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv);
+ execvp(FUSERMOUNT_PROG, (char **) argv);
+}
+
+void fuse_mount_version(void)
+{
+ int pid = fork();
+ if (!pid) {
+ const char *argv[] = { FUSERMOUNT_PROG, "--version", NULL };
+ exec_fusermount(argv);
+ _exit(1);
+ } else if (pid != -1)
+ waitpid(pid, NULL, 0);
+}
+
+struct mount_flags {
+ const char *opt;
+ unsigned long flag;
+ int on;
+};
+
+static const struct mount_flags mount_flags[] = {
+ {"rw", MS_RDONLY, 0},
+ {"ro", MS_RDONLY, 1},
+ {"suid", MS_NOSUID, 0},
+ {"nosuid", MS_NOSUID, 1},
+ {"dev", MS_NODEV, 0},
+ {"nodev", MS_NODEV, 1},
+ {"exec", MS_NOEXEC, 0},
+ {"noexec", MS_NOEXEC, 1},
+ {"async", MS_SYNCHRONOUS, 0},
+ {"sync", MS_SYNCHRONOUS, 1},
+ {"atime", MS_NOATIME, 0},
+ {"noatime", MS_NOATIME, 1},
+#ifndef __NetBSD__
+ {"dirsync", MS_DIRSYNC, 1},
+#endif
+ {NULL, 0, 0}
+};
+
+unsigned get_max_read(struct mount_opts *o)
+{
+ return o->max_read;
+}
+
+static void set_mount_flag(const char *s, int *flags)
+{
+ int i;
+
+ for (i = 0; mount_flags[i].opt != NULL; i++) {
+ const char *opt = mount_flags[i].opt;
+ if (strcmp(opt, s) == 0) {
+ if (mount_flags[i].on)
+ *flags |= mount_flags[i].flag;
+ else
+ *flags &= ~mount_flags[i].flag;
+ return;
+ }
+ }
+ fprintf(stderr, "fuse: internal error, can't find mount flag\n");
+ abort();
+}
+
+static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ (void) outargs;
+ struct mount_opts *mo = data;
+
+ switch (key) {
+ case KEY_RO:
+ arg = "ro";
+ /* fall through */
+ case KEY_KERN_FLAG:
+ set_mount_flag(arg, &mo->flags);
+ return 0;
+
+ case KEY_KERN_OPT:
+ return fuse_opt_add_opt(&mo->kernel_opts, arg);
+
+ case KEY_FUSERMOUNT_OPT:
+ return fuse_opt_add_opt_escaped(&mo->fusermount_opts, arg);
+
+ case KEY_SUBTYPE_OPT:
+ return fuse_opt_add_opt(&mo->subtype_opt, arg);
+
+ case KEY_MTAB_OPT:
+ return fuse_opt_add_opt(&mo->mtab_opts, arg);
+ }
+
+ /* Pass through unknown options */
+ return 1;
+}
+
+/* return value:
+ * >= 0 => fd
+ * -1 => error
+ */
+static int receive_fd(int fd)
+{
+ struct msghdr msg;
+ struct iovec iov;
+ char buf[1];
+ int rv;
+ size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)];
+ struct cmsghdr *cmsg;
+
+ iov.iov_base = buf;
+ iov.iov_len = 1;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ /* old BSD implementations should use msg_accrights instead of
+ * msg_control; the interface is different. */
+ msg.msg_control = ccmsg;
+ msg.msg_controllen = sizeof(ccmsg);
+
+ while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR);
+ if (rv == -1) {
+ perror("recvmsg");
+ return -1;
+ }
+ if(!rv) {
+ /* EOF */
+ return -1;
+ }
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ if (cmsg->cmsg_type != SCM_RIGHTS) {
+ fprintf(stderr, "got control message of unknown type %d\n",
+ cmsg->cmsg_type);
+ return -1;
+ }
+ return *(int*)CMSG_DATA(cmsg);
+}
+
+void fuse_kern_unmount(const char *mountpoint, int fd)
+{
+ int res;
+ int pid;
+
+ if (fd != -1) {
+ struct pollfd pfd;
+
+ pfd.fd = fd;
+ pfd.events = 0;
+ res = poll(&pfd, 1, 0);
+
+ /* Need to close file descriptor, otherwise synchronous umount
+ would recurse into filesystem, and deadlock.
+
+ Caller expects fuse_kern_unmount to close the fd, so close it
+ anyway. */
+ close(fd);
+
+ /* If file poll returns POLLERR on the device file descriptor,
+ then the filesystem is already unmounted or the connection
+ was severed via /sys/fs/fuse/connections/NNN/abort */
+ if (res == 1 && (pfd.revents & POLLERR))
+ return;
+ }
+
+ if (geteuid() == 0) {
+ fuse_mnt_umount("fuse", mountpoint, mountpoint, 1);
+ return;
+ }
+
+ res = umount2(mountpoint, 2);
+ if (res == 0)
+ return;
+
+ pid = fork();
+ if(pid == -1)
+ return;
+
+ if(pid == 0) {
+ const char *argv[] = { FUSERMOUNT_PROG, "-u", "-q", "-z",
+ "--", mountpoint, NULL };
+
+ exec_fusermount(argv);
+ _exit(1);
+ }
+ waitpid(pid, NULL, 0);
+}
+
+static int fuse_mount_fusermount(const char *mountpoint, struct mount_opts *mo,
+ const char *opts, int quiet)
+{
+ int fds[2], pid;
+ int res;
+ int rv;
+
+ if (!mountpoint) {
+ fprintf(stderr, "fuse: missing mountpoint parameter\n");
+ return -1;
+ }
+
+ res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
+ if(res == -1) {
+ perror("fuse: socketpair() failed");
+ return -1;
+ }
+
+ pid = fork();
+ if(pid == -1) {
+ perror("fuse: fork() failed");
+ close(fds[0]);
+ close(fds[1]);
+ return -1;
+ }
+
+ if(pid == 0) {
+ char env[10];
+ const char *argv[32];
+ int a = 0;
+
+ if (quiet) {
+ int fd = open("/dev/null", O_RDONLY);
+ if (fd != -1) {
+ dup2(fd, 1);
+ dup2(fd, 2);
+ }
+ }
+
+ argv[a++] = FUSERMOUNT_PROG;
+ if (opts) {
+ argv[a++] = "-o";
+ argv[a++] = opts;
+ }
+ argv[a++] = "--";
+ argv[a++] = mountpoint;
+ argv[a++] = NULL;
+
+ close(fds[1]);
+ fcntl(fds[0], F_SETFD, 0);
+ snprintf(env, sizeof(env), "%i", fds[0]);
+ setenv(FUSE_COMMFD_ENV, env, 1);
+ exec_fusermount(argv);
+ perror("fuse: failed to exec fusermount3");
+ _exit(1);
+ }
+
+ close(fds[0]);
+ rv = receive_fd(fds[1]);
+
+ if (!mo->auto_unmount) {
+ /* with auto_unmount option fusermount3 will not exit until
+ this socket is closed */
+ close(fds[1]);
+ waitpid(pid, NULL, 0); /* bury zombie */
+ }
+
+ if (rv >= 0)
+ fcntl(rv, F_SETFD, FD_CLOEXEC);
+
+ return rv;
+}
+
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+
+static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
+ const char *mnt_opts)
+{
+ char tmp[128];
+ const char *devname = "/dev/fuse";
+ char *source = NULL;
+ char *type = NULL;
+ struct stat stbuf;
+ int fd;
+ int res;
+
+ if (!mnt) {
+ fprintf(stderr, "fuse: missing mountpoint parameter\n");
+ return -1;
+ }
+
+ res = stat(mnt, &stbuf);
+ if (res == -1) {
+ fprintf(stderr ,"fuse: failed to access mountpoint %s: %s\n",
+ mnt, strerror(errno));
+ return -1;
+ }
+
+ if (mo->auto_unmount) {
+ /* Tell the caller to fallback to fusermount3 because
+ auto-unmount does not work otherwise. */
+ return -2;
+ }
+
+ fd = open(devname, O_RDWR | O_CLOEXEC);
+ if (fd == -1) {
+ if (errno == ENODEV || errno == ENOENT)
+ fprintf(stderr, "fuse: device not found, try 'modprobe fuse' first\n");
+ else
+ fprintf(stderr, "fuse: failed to open %s: %s\n",
+ devname, strerror(errno));
+ return -1;
+ }
+ if (!O_CLOEXEC)
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+ snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+ fd, stbuf.st_mode & S_IFMT, getuid(), getgid());
+
+ res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
+ if (res == -1)
+ goto out_close;
+
+ source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
+ (mo->subtype ? strlen(mo->subtype) : 0) +
+ strlen(devname) + 32);
+
+ type = malloc((mo->subtype ? strlen(mo->subtype) : 0) + 32);
+ if (!type || !source) {
+ fprintf(stderr, "fuse: failed to allocate memory\n");
+ goto out_close;
+ }
+
+ strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+ if (mo->subtype) {
+ strcat(type, ".");
+ strcat(type, mo->subtype);
+ }
+ strcpy(source,
+ mo->fsname ? mo->fsname : (mo->subtype ? mo->subtype : devname));
+
+ res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+ if (res == -1 && errno == ENODEV && mo->subtype) {
+ /* Probably missing subtype support */
+ strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
+ if (mo->fsname) {
+ if (!mo->blkdev)
+ sprintf(source, "%s#%s", mo->subtype,
+ mo->fsname);
+ } else {
+ strcpy(source, type);
+ }
+ res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
+ }
+ if (res == -1) {
+ /*
+ * Maybe kernel doesn't support unprivileged mounts, in this
+ * case try falling back to fusermount3
+ */
+ if (errno == EPERM) {
+ res = -2;
+ } else {
+ int errno_save = errno;
+ if (mo->blkdev && errno == ENODEV &&
+ !fuse_mnt_check_fuseblk())
+ fprintf(stderr,
+ "fuse: 'fuseblk' support missing\n");
+ else
+ fprintf(stderr, "fuse: mount failed: %s\n",
+ strerror(errno_save));
+ }
+
+ goto out_close;
+ }
+
+#ifndef IGNORE_MTAB
+ if (geteuid() == 0) {
+ char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
+ res = -1;
+ if (!newmnt)
+ goto out_umount;
+
+ res = fuse_mnt_add_mount("fuse", source, newmnt, type,
+ mnt_opts);
+ free(newmnt);
+ if (res == -1)
+ goto out_umount;
+ }
+#endif /* IGNORE_MTAB */
+ free(type);
+ free(source);
+
+ return fd;
+
+out_umount:
+ umount2(mnt, 2); /* lazy umount */
+out_close:
+ free(type);
+ free(source);
+ close(fd);
+ return res;
+}
+
+static int get_mnt_flag_opts(char **mnt_optsp, int flags)
+{
+ int i;
+
+ if (!(flags & MS_RDONLY) && fuse_opt_add_opt(mnt_optsp, "rw") == -1)
+ return -1;
+
+ for (i = 0; mount_flags[i].opt != NULL; i++) {
+ if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+ fuse_opt_add_opt(mnt_optsp, mount_flags[i].opt) == -1)
+ return -1;
+ }
+ return 0;
+}
+
+struct mount_opts *parse_mount_opts(struct fuse_args *args)
+{
+ struct mount_opts *mo;
+
+ mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+ if (mo == NULL)
+ return NULL;
+
+ memset(mo, 0, sizeof(struct mount_opts));
+ mo->flags = MS_NOSUID | MS_NODEV;
+
+ if (args &&
+ fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+ goto err_out;
+
+ return mo;
+
+err_out:
+ destroy_mount_opts(mo);
+ return NULL;
+}
+
+void destroy_mount_opts(struct mount_opts *mo)
+{
+ free(mo->fsname);
+ free(mo->subtype);
+ free(mo->fusermount_opts);
+ free(mo->subtype_opt);
+ free(mo->kernel_opts);
+ free(mo->mtab_opts);
+ free(mo);
+}
+
+
+int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+{
+ int res = -1;
+ char *mnt_opts = NULL;
+
+ res = -1;
+ if (get_mnt_flag_opts(&mnt_opts, mo->flags) == -1)
+ goto out;
+ if (mo->kernel_opts && fuse_opt_add_opt(&mnt_opts, mo->kernel_opts) == -1)
+ goto out;
+ if (mo->mtab_opts && fuse_opt_add_opt(&mnt_opts, mo->mtab_opts) == -1)
+ goto out;
+
+ res = fuse_mount_sys(mountpoint, mo, mnt_opts);
+ if (res == -2) {
+ if (mo->fusermount_opts &&
+ fuse_opt_add_opt(&mnt_opts, mo->fusermount_opts) == -1)
+ goto out;
+
+ if (mo->subtype) {
+ char *tmp_opts = NULL;
+
+ res = -1;
+ if (fuse_opt_add_opt(&tmp_opts, mnt_opts) == -1 ||
+ fuse_opt_add_opt(&tmp_opts, mo->subtype_opt) == -1) {
+ free(tmp_opts);
+ goto out;
+ }
+
+ res = fuse_mount_fusermount(mountpoint, mo, tmp_opts, 1);
+ free(tmp_opts);
+ if (res == -1)
+ res = fuse_mount_fusermount(mountpoint, mo,
+ mnt_opts, 0);
+ } else {
+ res = fuse_mount_fusermount(mountpoint, mo, mnt_opts, 0);
+ }
+ }
+out:
+ free(mnt_opts);
+ return res;
+}
diff --git a/lib/mount_bsd.c b/lib/mount_bsd.c
new file mode 100644
index 0000000..cbd3ced
--- /dev/null
+++ b/lib/mount_bsd.c
@@ -0,0 +1,292 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2005-2008 Csaba Henk <csaba.henk@creo.hu>
+
+ Architecture specific file system mounting (FreeBSD).
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "fuse_i.h"
+#include "fuse_misc.h"
+#include "fuse_opt.h"
+
+#include <sys/param.h>
+#include <sys/mount.h>
+
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <paths.h>
+#include <limits.h>
+
+#define FUSERMOUNT_PROG "mount_fusefs"
+#define FUSE_DEV_TRUNK "/dev/fuse"
+
+enum {
+ KEY_RO,
+ KEY_KERN
+};
+
+struct mount_opts {
+ int allow_other;
+ char *kernel_opts;
+ unsigned max_read;
+};
+
+#define FUSE_DUAL_OPT_KEY(templ, key) \
+ FUSE_OPT_KEY(templ, key), FUSE_OPT_KEY("no" templ, key)
+
+static const struct fuse_opt fuse_mount_opts[] = {
+ { "allow_other", offsetof(struct mount_opts, allow_other), 1 },
+ { "max_read=%u", offsetof(struct mount_opts, max_read), 1 },
+ FUSE_OPT_KEY("-r", KEY_RO),
+ /* standard FreeBSD mount options */
+ FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("async", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("atime", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("dev", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("exec", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("suid", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("symfollow", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("rdonly", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("sync", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("union", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("userquota", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("groupquota", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("clusterr", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("clusterw", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("suiddir", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("snapshot", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("multilabel", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("acls", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("force", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("update", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("ro", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("rw", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("auto", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("automounted", KEY_KERN),
+ /* options supported under both Linux and FBSD */
+ FUSE_DUAL_OPT_KEY("allow_other", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("default_permissions",KEY_KERN),
+ FUSE_OPT_KEY("max_read=", KEY_KERN),
+ FUSE_OPT_KEY("subtype=", KEY_KERN),
+ /* FBSD FUSE specific mount options */
+ FUSE_DUAL_OPT_KEY("private", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("neglect_shares", KEY_KERN),
+ FUSE_DUAL_OPT_KEY("push_symlinks_in", KEY_KERN),
+ FUSE_OPT_KEY("nosync_unmount", KEY_KERN),
+ /* stock FBSD mountopt parsing routine lets anything be negated... */
+ /*
+ * Linux specific mount options, but let just the mount util
+ * handle them
+ */
+ FUSE_OPT_KEY("fsname=", KEY_KERN),
+ FUSE_OPT_END
+};
+
+void fuse_mount_version(void)
+{
+ system(FUSERMOUNT_PROG " --version");
+}
+
+unsigned get_max_read(struct mount_opts *o)
+{
+ return o->max_read;
+}
+
+static int fuse_mount_opt_proc(void *data, const char *arg, int key,
+ struct fuse_args *outargs)
+{
+ (void) outargs;
+ struct mount_opts *mo = data;
+
+ switch (key) {
+ case KEY_RO:
+ arg = "ro";
+ /* fall through */
+
+ case KEY_KERN:
+ return fuse_opt_add_opt(&mo->kernel_opts, arg);
+ }
+
+ /* Pass through unknown options */
+ return 1;
+}
+
+void fuse_kern_unmount(const char *mountpoint, int fd)
+{
+ close(fd);
+ unmount(mountpoint, MNT_FORCE);
+}
+
+/* Check if kernel is doing init in background */
+static int init_backgrounded(void)
+{
+ unsigned ibg;
+ size_t len;
+
+ len = sizeof(ibg);
+
+ if (sysctlbyname("vfs.fuse.init_backgrounded", &ibg, &len, NULL, 0))
+ return 0;
+
+ return ibg;
+}
+
+
+static int fuse_mount_core(const char *mountpoint, const char *opts)
+{
+ const char *mountprog = FUSERMOUNT_PROG;
+ int fd;
+ char *fdnam, *dev;
+ pid_t pid, cpid;
+ int status;
+
+ fdnam = getenv("FUSE_DEV_FD");
+
+ if (fdnam) {
+ char *ep;
+
+ fd = strtol(fdnam, &ep, 10);
+
+ if (*ep != '\0') {
+ fprintf(stderr, "invalid value given in FUSE_DEV_FD\n");
+ return -1;
+ }
+
+ if (fd < 0)
+ return -1;
+
+ goto mount;
+ }
+
+ dev = getenv("FUSE_DEV_NAME");
+
+ if (! dev)
+ dev = (char *)FUSE_DEV_TRUNK;
+
+ if ((fd = open(dev, O_RDWR)) < 0) {
+ perror("fuse: failed to open fuse device");
+ return -1;
+ }
+
+mount:
+ if (getenv("FUSE_NO_MOUNT") || ! mountpoint)
+ goto out;
+
+ pid = fork();
+ cpid = pid;
+
+ if (pid == -1) {
+ perror("fuse: fork() failed");
+ close(fd);
+ return -1;
+ }
+
+ if (pid == 0) {
+ if (! init_backgrounded()) {
+ /*
+ * If init is not backgrounded, we have to
+ * call the mount util backgrounded, to avoid
+ * deadlock.
+ */
+
+ pid = fork();
+
+ if (pid == -1) {
+ perror("fuse: fork() failed");
+ close(fd);
+ exit(1);
+ }
+ }
+
+ if (pid == 0) {
+ const char *argv[32];
+ int a = 0;
+ int ret = -1;
+
+ if (! fdnam)
+ {
+ ret = asprintf(&fdnam, "%d", fd);
+ if(ret == -1)
+ {
+ perror("fuse: failed to assemble mount arguments");
+ close(fd);
+ exit(1);
+ }
+ }
+
+ argv[a++] = mountprog;
+ if (opts) {
+ argv[a++] = "-o";
+ argv[a++] = opts;
+ }
+ argv[a++] = fdnam;
+ argv[a++] = mountpoint;
+ argv[a++] = NULL;
+ execvp(mountprog, (char **) argv);
+ perror("fuse: failed to exec mount program");
+ free(fdnam);
+ exit(1);
+ }
+
+ exit(0);
+ }
+
+ if (waitpid(cpid, &status, 0) == -1 || WEXITSTATUS(status) != 0) {
+ perror("fuse: failed to mount file system");
+ close(fd);
+ return -1;
+ }
+
+out:
+ return fd;
+}
+
+struct mount_opts *parse_mount_opts(struct fuse_args *args)
+{
+ struct mount_opts *mo;
+
+ mo = (struct mount_opts*) malloc(sizeof(struct mount_opts));
+ if (mo == NULL)
+ return NULL;
+
+ memset(mo, 0, sizeof(struct mount_opts));
+
+ if (args &&
+ fuse_opt_parse(args, mo, fuse_mount_opts, fuse_mount_opt_proc) == -1)
+ goto err_out;
+
+ return mo;
+
+err_out:
+ destroy_mount_opts(mo);
+ return NULL;
+}
+
+void destroy_mount_opts(struct mount_opts *mo)
+{
+ free(mo->kernel_opts);
+ free(mo);
+}
+
+int fuse_kern_mount(const char *mountpoint, struct mount_opts *mo)
+{
+ /* mount util should not try to spawn the daemon */
+ setenv("MOUNT_FUSEFS_SAFE", "1", 1);
+ /* to notify the mount util it's called from lib */
+ setenv("MOUNT_FUSEFS_CALL_BY_LIB", "1", 1);
+
+ return fuse_mount_core(mountpoint, mo->kernel_opts);
+}
diff --git a/lib/mount_util.c b/lib/mount_util.c
new file mode 100644
index 0000000..f1d2325
--- /dev/null
+++ b/lib/mount_util.c
@@ -0,0 +1,369 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ Architecture-independent mounting code.
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#include "config.h"
+#include "mount_util.h"
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <paths.h>
+#if !defined( __NetBSD__) && !defined(__FreeBSD__) && !defined(__DragonFly__)
+#include <mntent.h>
+#else
+#define IGNORE_MTAB
+#endif
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+
+#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+#define umount2(mnt, flags) unmount(mnt, ((flags) == 2) ? MNT_FORCE : 0)
+#endif
+
+#ifdef IGNORE_MTAB
+#define mtab_needs_update(mnt) 0
+#else
+static int mtab_needs_update(const char *mnt)
+{
+ int res;
+ struct stat stbuf;
+
+ /* If mtab is within new mount, don't touch it */
+ if (strncmp(mnt, _PATH_MOUNTED, strlen(mnt)) == 0 &&
+ _PATH_MOUNTED[strlen(mnt)] == '/')
+ return 0;
+
+ /*
+ * Skip mtab update if /etc/mtab:
+ *
+ * - doesn't exist,
+ * - is a symlink,
+ * - is on a read-only filesystem.
+ */
+ res = lstat(_PATH_MOUNTED, &stbuf);
+ if (res == -1) {
+ if (errno == ENOENT)
+ return 0;
+ } else {
+ uid_t ruid;
+ int err;
+
+ if (S_ISLNK(stbuf.st_mode))
+ return 0;
+
+ ruid = getuid();
+ if (ruid != 0)
+ setreuid(0, -1);
+
+ res = access(_PATH_MOUNTED, W_OK);
+ err = (res == -1) ? errno : 0;
+ if (ruid != 0)
+ setreuid(ruid, -1);
+
+ if (err == EROFS)
+ return 0;
+ }
+
+ return 1;
+}
+#endif /* IGNORE_MTAB */
+
+static int add_mount(const char *progname, const char *fsname,
+ const char *mnt, const char *type, const char *opts)
+{
+ int res;
+ int status;
+ sigset_t blockmask;
+ sigset_t oldmask;
+
+ sigemptyset(&blockmask);
+ sigaddset(&blockmask, SIGCHLD);
+ res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+ if (res == -1) {
+ fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+ return -1;
+ }
+
+ res = fork();
+ if (res == -1) {
+ fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+ goto out_restore;
+ }
+ if (res == 0) {
+ char *env = NULL;
+
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ if(setuid(geteuid()) == -1) {
+ fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+ res = -1;
+ goto out_restore;
+ }
+
+ execle("/bin/mount", "/bin/mount", "--no-canonicalize", "-i",
+ "-f", "-t", type, "-o", opts, fsname, mnt, NULL, &env);
+ fprintf(stderr, "%s: failed to execute /bin/mount: %s\n",
+ progname, strerror(errno));
+ exit(1);
+ }
+ res = waitpid(res, &status, 0);
+ if (res == -1)
+ fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
+ if (status != 0)
+ res = -1;
+
+ out_restore:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return res;
+}
+
+int fuse_mnt_add_mount(const char *progname, const char *fsname,
+ const char *mnt, const char *type, const char *opts)
+{
+ if (!mtab_needs_update(mnt))
+ return 0;
+
+ return add_mount(progname, fsname, mnt, type, opts);
+}
+
+static int exec_umount(const char *progname, const char *rel_mnt, int lazy)
+{
+ int res;
+ int status;
+ sigset_t blockmask;
+ sigset_t oldmask;
+
+ sigemptyset(&blockmask);
+ sigaddset(&blockmask, SIGCHLD);
+ res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+ if (res == -1) {
+ fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+ return -1;
+ }
+
+ res = fork();
+ if (res == -1) {
+ fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+ goto out_restore;
+ }
+ if (res == 0) {
+ char *env = NULL;
+
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ if(setuid(geteuid()) == -1) {
+ fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+ res = -1;
+ goto out_restore;
+ }
+
+ if (lazy) {
+ execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+ "-l", NULL, &env);
+ } else {
+ execle("/bin/umount", "/bin/umount", "-i", rel_mnt,
+ NULL, &env);
+ }
+ fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+ progname, strerror(errno));
+ exit(1);
+ }
+ res = waitpid(res, &status, 0);
+ if (res == -1)
+ fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
+ if (status != 0) {
+ res = -1;
+ }
+
+ out_restore:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+ return res;
+
+}
+
+int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+ const char *rel_mnt, int lazy)
+{
+ int res;
+
+ if (!mtab_needs_update(abs_mnt)) {
+ res = umount2(rel_mnt, lazy ? 2 : 0);
+ if (res == -1)
+ fprintf(stderr, "%s: failed to unmount %s: %s\n",
+ progname, abs_mnt, strerror(errno));
+ return res;
+ }
+
+ return exec_umount(progname, rel_mnt, lazy);
+}
+
+static int remove_mount(const char *progname, const char *mnt)
+{
+ int res;
+ int status;
+ sigset_t blockmask;
+ sigset_t oldmask;
+
+ sigemptyset(&blockmask);
+ sigaddset(&blockmask, SIGCHLD);
+ res = sigprocmask(SIG_BLOCK, &blockmask, &oldmask);
+ if (res == -1) {
+ fprintf(stderr, "%s: sigprocmask: %s\n", progname, strerror(errno));
+ return -1;
+ }
+
+ res = fork();
+ if (res == -1) {
+ fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno));
+ goto out_restore;
+ }
+ if (res == 0) {
+ char *env = NULL;
+
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ if(setuid(geteuid()) == -1) {
+ fprintf(stderr, "%s: setuid: %s\n", progname, strerror(errno));
+ res = -1;
+ goto out_restore;
+ }
+
+ execle("/bin/umount", "/bin/umount", "--no-canonicalize", "-i",
+ "--fake", mnt, NULL, &env);
+ fprintf(stderr, "%s: failed to execute /bin/umount: %s\n",
+ progname, strerror(errno));
+ exit(1);
+ }
+ res = waitpid(res, &status, 0);
+ if (res == -1)
+ fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno));
+
+ if (status != 0)
+ res = -1;
+
+ out_restore:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+ return res;
+}
+
+int fuse_mnt_remove_mount(const char *progname, const char *mnt)
+{
+ if (!mtab_needs_update(mnt))
+ return 0;
+
+ return remove_mount(progname, mnt);
+}
+
+char *fuse_mnt_resolve_path(const char *progname, const char *orig)
+{
+ char buf[PATH_MAX];
+ char *copy;
+ char *dst;
+ char *end;
+ char *lastcomp;
+ const char *toresolv;
+
+ if (!orig[0]) {
+ fprintf(stderr, "%s: invalid mountpoint '%s'\n", progname,
+ orig);
+ return NULL;
+ }
+
+ copy = strdup(orig);
+ if (copy == NULL) {
+ fprintf(stderr, "%s: failed to allocate memory\n", progname);
+ return NULL;
+ }
+
+ toresolv = copy;
+ lastcomp = NULL;
+ for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --);
+ if (end[0] != '/') {
+ char *tmp;
+ end[1] = '\0';
+ tmp = strrchr(copy, '/');
+ if (tmp == NULL) {
+ lastcomp = copy;
+ toresolv = ".";
+ } else {
+ lastcomp = tmp + 1;
+ if (tmp == copy)
+ toresolv = "/";
+ }
+ if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) {
+ lastcomp = NULL;
+ toresolv = copy;
+ }
+ else if (tmp)
+ tmp[0] = '\0';
+ }
+ if (realpath(toresolv, buf) == NULL) {
+ fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig,
+ strerror(errno));
+ free(copy);
+ return NULL;
+ }
+ if (lastcomp == NULL)
+ dst = strdup(buf);
+ else {
+ dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1);
+ if (dst) {
+ unsigned buflen = strlen(buf);
+ if (buflen && buf[buflen-1] == '/')
+ sprintf(dst, "%s%s", buf, lastcomp);
+ else
+ sprintf(dst, "%s/%s", buf, lastcomp);
+ }
+ }
+ free(copy);
+ if (dst == NULL)
+ fprintf(stderr, "%s: failed to allocate memory\n", progname);
+ return dst;
+}
+
+int fuse_mnt_check_fuseblk(void)
+{
+ char buf[256];
+ FILE *f = fopen("/proc/filesystems", "r");
+ if (!f)
+ return 1;
+
+ while (fgets(buf, sizeof(buf), f))
+ if (strstr(buf, "fuseblk\n")) {
+ fclose(f);
+ return 1;
+ }
+
+ fclose(f);
+ return 0;
+}
+
+int fuse_mnt_parse_fuse_fd(const char *mountpoint)
+{
+ int fd = -1;
+ int len = 0;
+
+ if (sscanf(mountpoint, "/dev/fd/%u%n", &fd, &len) == 1 &&
+ len == strlen(mountpoint)) {
+ return fd;
+ }
+
+ return -1;
+}
diff --git a/lib/mount_util.h b/lib/mount_util.h
new file mode 100644
index 0000000..0ef0fbe
--- /dev/null
+++ b/lib/mount_util.h
@@ -0,0 +1,18 @@
+/*
+ FUSE: Filesystem in Userspace
+ Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
+
+ This program can be distributed under the terms of the GNU LGPLv2.
+ See the file COPYING.LIB.
+*/
+
+#include <sys/types.h>
+
+int fuse_mnt_add_mount(const char *progname, const char *fsname,
+ const char *mnt, const char *type, const char *opts);
+int fuse_mnt_remove_mount(const char *progname, const char *mnt);
+int fuse_mnt_umount(const char *progname, const char *abs_mnt,
+ const char *rel_mnt, int lazy);
+char *fuse_mnt_resolve_path(const char *progname, const char *orig);
+int fuse_mnt_check_fuseblk(void);
+int fuse_mnt_parse_fuse_fd(const char *mountpoint);
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..f101d82
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,110 @@
+project('libfuse3', ['cpp', 'c'], version: '3.6.2',
+ meson_version: '>= 0.42',
+ default_options: [ 'buildtype=debugoptimized' ])
+
+
+platform = host_machine.system()
+if platform == 'darwin'
+ error('libfuse does not support OS-X.\n' +
+ 'Take a look at http://osxfuse.github.io/ instead')
+elif platform == 'cygwin' or platform == 'windows'
+ error('libfuse does not support Windows.\n' +
+ 'Take a look at http://www.secfs.net/winfsp/ instead')
+endif
+
+#
+# Feature detection
+#
+cfg = configuration_data()
+cc = meson.get_compiler('c')
+
+# Default includes when checking for presence of functions and
+# struct members
+include_default = '''
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+'''
+args_default = [ '-D_GNU_SOURCE' ]
+
+cfg.set_quoted('PACKAGE_VERSION', meson.project_version())
+
+# Test for presence of some functions
+test_funcs = [ 'fork', 'fstatat', 'openat', 'readlinkat', 'pipe2',
+ 'splice', 'vmsplice', 'posix_fallocate', 'fdatasync',
+ 'utimensat', 'copy_file_range', 'fallocate' ]
+foreach func : test_funcs
+ cfg.set('HAVE_' + func.to_upper(),
+ cc.has_function(func, prefix: include_default, args: args_default))
+endforeach
+cfg.set('HAVE_SETXATTR',
+ cc.has_function('setxattr', prefix: '#include <sys/xattr.h>'))
+cfg.set('HAVE_ICONV',
+ cc.has_function('iconv', prefix: '#include <iconv.h>'))
+
+# Test if structs have specific member
+cfg.set('HAVE_STRUCT_STAT_ST_ATIM',
+ cc.has_member('struct stat', 'st_atim',
+ prefix: include_default,
+ args: args_default))
+cfg.set('HAVE_STRUCT_STAT_ST_ATIMESPEC',
+ cc.has_member('struct stat', 'st_atimespec',
+ prefix: include_default,
+ args: args_default))
+
+# Write the test results into config.h (stored in build directory)
+configure_file(output: 'config.h',
+ configuration : cfg)
+
+#
+# Compiler configuration
+#
+add_project_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-Wall', '-Wextra', '-Wno-sign-compare',
+ '-Wstrict-prototypes', '-Wmissing-declarations', '-Wwrite-strings',
+ '-fno-strict-aliasing', language: 'c')
+add_project_arguments('-D_REENTRANT', '-DHAVE_CONFIG_H', '-D_GNU_SOURCE',
+ '-Wall', '-Wextra', '-Wno-sign-compare', '-std=c++11',
+ '-Wmissing-declarations', '-Wwrite-strings',
+ '-fno-strict-aliasing', language: 'cpp')
+
+# Some (stupid) GCC versions warn about unused return values even when they are
+# casted to void. This makes -Wunused-result pretty useless, since there is no
+# way to suppress the warning when we really *want* to ignore the value.
+code = '''
+__attribute__((warn_unused_result)) int get_4() {
+ return 4;
+}
+int main(void) {
+ (void) get_4();
+ return 0;
+}'''
+if not cc.compiles(code, args: [ '-O0', '-Werror=unused-result' ])
+ message('Compiler warns about unused result even when casting to void')
+ add_project_arguments('-Wno-unused-result', language: 'c')
+endif
+
+# '.' will refer to current build directory, which contains config.h
+include_dirs = include_directories('include', 'lib', '.')
+
+# Common dependencies
+thread_dep = dependency('threads')
+
+#
+# Read build files from sub-directories
+#
+subdirs = [ 'lib', 'include', 'test' ]
+if get_option('utils') and not platform.endswith('bsd') and platform != 'dragonfly'
+ subdirs += [ 'util', 'doc' ]
+endif
+
+if get_option('examples')
+ subdirs += 'example'
+endif
+
+foreach n : subdirs
+ subdir(n)
+endforeach