Project import
diff --git a/.dir-locals.el b/.dir-locals.el
new file mode 100644
index 0000000..c70a23a
--- /dev/null
+++ b/.dir-locals.el
@@ -0,0 +1,26 @@
+((nil . ((eval . (add-hook 'before-save-hook
+			   'whitespace-cleanup nil t))))
+ (python-mode . ((indent-tabs-mode . nil)))
+ (c-mode . ((c-file-style . "stroustrup")
+	    (indent-tabs-mode . t)
+	    (tab-width . 8)
+	    (c-basic-offset . 8)
+	    (c-file-offsets .
+			    ((block-close . 0)
+			     (brace-list-close . 0)
+			     (brace-list-entry . 0)
+			     (brace-list-intro . +)
+			     (case-label . 0)
+			     (class-close . 0)
+			     (defun-block-intro . +)
+			     (defun-close . 0)
+			     (defun-open . 0)
+			     (else-clause . 0)
+			     (inclass . +)
+			     (label . 0)
+			     (statement . 0)
+			     (statement-block-intro . +)
+			     (statement-case-intro . +)
+			     (statement-cont . +)
+			     (substatement . +)
+			     (topmost-intro . 0))))))
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b7bed8f
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,33 @@
+sudo: required
+language:
+    - python
+    - c
+python: "3.5"
+compiler:
+    - gcc
+    - clang
+env:
+    - CC=gcc-6
+    - CC=clang
+addons:
+  apt:
+    sources:
+    - ubuntu-toolchain-r-test
+    packages:
+    - doxygen
+    - libtool
+    - valgrind
+    - automake
+    - autoconf
+    - gcc-6
+install:
+    - sudo python -m pip install pytest
+script:
+    - $CC --version
+    - libtool --version
+    - valgrind --version
+    - ./makeconf.sh
+    - ./configure
+    - CFLAGS="-fsanitize=address,undefined -g -O1 -Wall -Werror" make
+    - doxygen doc/Doxyfile
+    - make test
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..2f85c29
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,67 @@
+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):
+
+Alex Richman <alex@richman.io>
+Anatol Pomozov <anatol.pomozov@gmail.com>
+Antonio SJ Musumeci <trapexit@spawn.link>
+bobrofon <ifbossfor@ya.ru>
+Christopher Harrison <ch12@sanger.ac.uk>
+Consus <consus@gmx.com>
+Csaba Henk <csaba.henk@creo.hu>
+cvs2git <>
+Dalvik Khertel <khertel@outlook.com>
+Daniel Thau <danthau@bedrocklinux.org>
+David McNab <david@rebirthing.co.nz>
+David Sheets <sheets@alum.mit.edu>
+divinity76 <divinity76@gmail.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>
+Feng Shuo <steve.shuo.feng@gmail.com>
+Hendrik Brueckner <brueckner@linux.vnet.ibm.com>
+Ikey Doherty <michael.i.doherty@intel.com>
+Jan Blumschein <jan@jan-blumschein.de>
+Jay Hankins <jay-hankins@users.noreply.github.com>
+Joachim Schiele <joachim.schiele@daimler.com>
+Joachim Schiele <js@lastlog.de>
+John Muir <john@jmuir.com>
+Laszlo Papp <ext-laszlo.papp@nokia.com>
+Madan Valluri <mvalluri@cumulus-systems.com>
+Mark Glines <mark@glines.org>
+Max Krasnyansky <maxk@kernel.org>
+Michael Grigoriev <mag@luminal.org>
+Mihail Konev <k.mvc@ya.ru>
+Miklos Szeredi <miklos@szeredi.hu>
+Miklos Szeredi <mszeredi@suse.cz>
+mkmm@gmx-topmail.de <mkmm@gmx-topmail.de>
+Natanael Copa <ncopa@alpinelinux.org>
+Nikolaus Rath <Nikolaus@rath.org>
+Olivier Blin <olivier.blin@softathome.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>
+Roland Bauerschmidt <rb@debian.org>
+Sam Stuewe <halosghost@archlinux.info>
+Sebastian Pipping <sebastian@pipping.org>
+therealneworld@gmail.com <therealneworld@gmail.com>
+Winfried Koehler <w_scan@gmx-topmail.de>
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING
@@ -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/COPYING.LIB b/COPYING.LIB
new file mode 100644
index 0000000..4362b49
--- /dev/null
+++ b/COPYING.LIB
@@ -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/ChangeLog.rst b/ChangeLog.rst
new file mode 100644
index 0000000..56847cc
--- /dev/null
+++ b/ChangeLog.rst
@@ -0,0 +1,366 @@
+libfuse 3.0.0 (2016-12-08)
+==========================
+
+* NOTE TO PACKAGERS:
+
+  libfuse 3 is designed to be co-installable with libfuse 2. However,
+  some files will be installed by both libfuse 2 and libfuse 3
+  (e.g. /etc/fuse.conf, the udev and init scripts, and the
+  mount.fuse(8) manpage). These files should be taken from
+  libfuse 3. The format/content is guaranteed to remain backwards
+  compatible with libfuse 2.
+
+  We recommend to ship libfuse2 and libfuse3 in three separate
+  packages: a libfuse-common package that contains files shared by
+  libfuse 2+3 (taken from the libfuse3 tarball), and libfuse2 and
+  libfuse3 packages that contain the shared library and helper
+  programs for the respective version.
+
+* Fixed test errors when running tests as root.
+
+* Made check for util-linux version more robust.
+
+* Added documentation for all fuse capability flags (`FUSE_CAP_*`) and
+  `struct fuse_conn_info` fields.
+
+* fuse_loop(), fuse_loop_mt(), fuse_session_loop() and
+  fuse_session_loop_mt() now return more detailed error codes instead
+  of just -1. See the documentation of fuse_session_loop() for details.
+
+* The FUSE main loop is now aborted if the file-system requests
+  capabilities that are not supported by the kernel. In this case, the
+  session loop is exited with a return code of ``-EPROTO``.
+
+* Most file-system capabilities that were opt-in in libfuse2 are now
+  enabled by default. Filesystem developers are encouraged to review
+  the documentation of the FUSE_CAP_* features to ensure that their
+  filesystem is compatible with the new semantics. As before, a
+  particular capability can still be disabled by unsetting the
+  corresponding bit of `fuse_conn_info.wants` in the init() handler.
+
+* Added FUSE_CAP_PARALLEL_DIROPS and FUSE_CAP_POSIX_ACL,
+  FUSE_HANDLE_KILLPRIV feature flags.
+
+* FUSE filesystems are now responsible for unsetting the setuid/setgid
+  flags when a file is written, truncated, or its owner
+  changed. Previously, this was handled by the kernel but subject to
+  race conditions.
+
+* The fusermount and mount.fuse binaries have been renamed to
+  fusermount3 and mount.fuse3 to allow co-installation of libfuse 2.x
+  and 3.x
+
+* Added a `max_read` field to `struct fuse_conn_info`. For the time
+  being, the maximum size of read requests has to be specified both
+  there *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.
+
+* Documentation: clarified that the fuse_argv structure that is passed
+  to `fuse_new()` and `fuse_lowlevel_new()` must always contain at
+  least one element.
+
+* The high-level init() handler now receives an additional struct
+  fuse_config pointer that can be used to adjust high-level API
+  specific configuration options.
+
+* The `nopath_flag` field of struct fuse_operations has been
+  removed. Instead, a new `nullpath_ok` flag can now be set
+  in struct fuse_config.
+
+* File systems that use the low-level API and support lookup requests
+  for '.' and '..' should continue make sure to set the
+  FUSE_CAP_EXPORT_SUPPORT bit in fuse_conn_info->want.
+
+  (This has actually always been the case, but was not very obvious
+  from the documentation).
+
+* The help text generated by fuse_lowlevel_help(), fuse_new() (and
+  indirectly fuse_main()) no longer includes options that are unlikely
+  to be of interest to end-users. The full list of accepted options is
+  now included in the respective function's documentation (located in
+  the fuse.h/fuse_lowlevel.h and doc/html).
+
+* The ``-o nopath`` option has been dropped - it never actually did
+  anything (since it is unconditionally overwritten with the value of
+  the `nopath` flag in `struct fuse_operations).
+
+* The ``-o large_read`` mount option has been dropped. Hopefully no
+  one uses a Linux 2.4 kernel anymore.
+
+* The `-o nonempty` mount point has been removed, mounting over
+  non-empty directories is now always allowed. This brings the
+  behavior of FUSE file systems in-line with the behavior of the
+  regular `mount` command.
+
+  File systems that do not want to allow mounting to non-empty
+  directories should perform this check themselves before handing
+  control to libfuse.
+
+* The chmod, chown, truncate, utimens and getattr handlers of the
+  high-level API now all receive an additional struct fuse_file_info
+  pointer (which, however, may be NULL even if the file is currently
+  open).
+
+  The fgetattr and ftruncate handlers have become obsolete and have
+  been removed.
+
+* The `fuse_session_new` function no longer accepts the ``-o
+  clone_fd`` option. Instead, this has become a parameter of the
+  `fuse_session_loop_mt` and ``fuse_loop_mt` functions.
+
+* For low-level file systems that implement the `write_buf` handler,
+  the `splice_read` option is now enabled by default. As usual, this
+  can be changed in the file system's `init` handler.
+
+* The treatment of low-level options has been made more consistent:
+
+  Options that can be set in the init() handler (via the
+  fuse_conn_info parameter) can now be set only here,
+  i.e. fuse_session_new() no longer accepts arguments that change the
+  fuse_conn_info object before or after the call do init(). As a side
+  effect, this removes the ambiguity where some options can be
+  overwritten by init(), while others overwrite the choices made by
+  init().
+
+  For file systems that wish to offer command line options for these
+  settings, the new fuse_parse_conn_info_opts() and
+  fuse_apply_conn_info_opts() functions are available.
+
+  Consequently, the fuse_lowlevel_help() method has been dropped.
+
+* The `async_read` field in `struct fuse_conn_info` has been
+  removed. To determine if the kernel supports asynchronous reads,
+  file systems should check the `FUSE_CAP_ASYNC_READ` bit of the
+  `capable` field. To enable/disable asynchronous reads, file systems
+  should set the flag in the `wanted` field.
+
+* The `fuse_parse_cmdline` function no longer prints out help when the
+  ``--verbose`` or ``--help`` flags are given. This needs to be done
+  by the file system (e.g. using the `fuse_cmdline_help()` and
+  `fuse_lowlevel_help()` functions).
+
+* Added ``example/cuse_client.c`` to test ``example/cuse.c``.
+
+* Removed ``example/null.c``. This has not been working for a while
+  for unknown reasons -- maybe because it tries to treat the
+  mountpoint as a file rather than a directory?
+
+* There are several new examples that demonstrate the use of
+  the ``fuse_lowlevel_notify_*`` functions:
+
+  - ``example/notify_store_retrieve.c``
+  - ``example/notify_inval_inode.c``
+  - ``example/notify_inval_entry.c``
+
+* The ``-o big_writes`` mount option has been removed. It is now
+  always active. File systems that want to limit the size of write
+  requests should use the ``-o max_write=<N>`` option instead.
+
+* The `fuse_lowlevel_new` function has been renamed to
+  `fuse_session_new` and no longer interprets the --version or --help
+  options. To print help or version information, use the new
+  `fuse_lowlevel_help` and `fuse_lowlevel_version` functions.
+
+* The ``allow_other`` and ``allow_root`` mount options (accepted by
+  `fuse_session_new()`) may now be specified together. In this case,
+  ``allow_root`` takes precedence.
+
+* There are new `fuse_session_unmount` and `fuse_session_mount`
+  functions that should be used in the low-level API. The `fuse_mount`
+  and `fuse_unmount` functions should be used with the high-level API
+  only.
+
+* Neither `fuse_mount` nor `fuse_session_mount` take struct fuse_opts
+  parameters anymore. Mount options are parsed by `fuse_new` (for the
+  high-level API) and `fuse_session_new` (for the low-level API)
+  instead. To print help or version information, use the new
+  `fuse_mount_help` and `fuse_mount_version` functions.
+
+* The ``fuse_lowlevel_notify_*`` functions now all take a `struct
+  fuse_session` parameter instead of a `struct fuse_chan`.
+
+* The channel interface (``fuse_chan_*`` functions) has been made
+  private. As a result, the typical initialization sequence of a
+  low-level file system has changed from ::
+
+        ch = fuse_mount(mountpoint, &args);
+        se = fuse_lowlevel_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+        fuse_set_signal_handlers(se);
+        fuse_session_add_chan(se, ch);
+        fuse_daemonize(fg);
+        if (mt)
+            fuse_session_loop_mt(se);
+        else
+            fuse_session_loop(se);
+        fuse_remove_signal_handlers(se);
+        fuse_session_remove_chan(ch);
+        fuse_session_destroy(se);
+        fuse_unmount(mountpoint, ch);
+
+  to ::
+
+        se = fuse_session_new(&args, &ll_ops, sizeof(ll_ops), NULL);
+        fuse_set_signal_handlers(se);
+        fuse_session_mount(se, mountpoint);
+        fuse_daemonize(fg);
+        if (mt)
+            fuse_session_loop_mt(se);
+        else
+            fuse_session_loop(se);
+        fuse_remove_signal_handlers(se);
+        fuse_session_unmount(se);
+        fuse_lowlevel_destroy(se);
+
+  The typical high-level setup has changed from ::
+
+        ch = fuse_mount(*mountpoint, &args);
+        fuse = fuse_new(ch, &args, op, op_size, user_data);
+        se = fuse_get_session(fuse);
+        fuse_set_signal_handlers(se);
+        fuse_daemonize(fg);
+        if (mt)
+            fuse_loop_mt(fuse);
+        else
+            fuse_loop(fuse);
+        fuse_remove_signal_handlers(se);
+        fuse_unmount(mountpoint, ch);
+        fuse_destroy(fuse);
+
+  to ::
+
+        fuse = fuse_new(&args, op, op_size, user_data);
+        se = fuse_get_session(fuse);
+        fuse_set_signal_handlers(se);
+        fuse_mount(se, mountpoint);
+        fuse_daemonize(fg);
+         if (mt)
+            fuse_loop_mt(fuse);
+        else
+            fuse_loop(fuse);
+        fuse_remove_signal_handlers(se);
+        fuse_unmount(se);
+        fuse_destroy(fuse);
+
+  File systems that use `fuse_main` are not affected by this change.
+
+  For integration with custom event loops, the new `fuse_session_fd`
+  function provides the file descriptor that's used for communication
+  with the kernel.
+
+* Added *clone_fd* option.  This creates a separate device file
+  descriptor for each processing thread, which might improve
+  performance.
+
+* Added *writeback_cache* option. With kernel 3.14 and newer this
+  enables write-back caching which can significantly improve
+  performance.
+
+* Added *async_dio* option. With kernel 3.13 and newer, this allows
+  direct I/O to be done asynchronously.
+
+* The (high- and low-level) `rename` handlers now takes a *flags*
+  parameter (with values corresponding to the *renameat2* system call
+  introduced in Linux 3.15).
+
+* The "ulockmgr_server" has been dropped.
+
+* There is a new (low-level) `readdirplus` handler, with a
+  corresponding example in ``examples/fuse_lo-plus.c`` and a new
+  `fuse_add_direntry_plus` API function.
+
+* The (high-level) `readdir` handler now takes a *flags* argument.
+
+* The (high-level) `filler` function passed to `readdir` now takes an
+  additional *flags* argument.
+
+* The (high-level) `getdir` handler has been dropped.
+
+* The *flag_nullpath_ok* and *flag_utime_omit_ok* flags have been
+  dropped.
+
+* The (high-level) *utime* handler has been dropped.
+
+* The `fuse_invalidate` function has been removed.
+
+* The `fuse_is_lib_option` function has been removed.
+
+* The *fh_old* member of `struct fuse_file_info` has been dropped.
+
+* The type of the *writepage* member of `struct fuse_file_info` was
+  changed from *int* to *unsigned int*.
+
+* The `struct fuse_file_info` gained a new *poll_events* member.
+
+* There is a new `fuse_pkgversion` function.
+
+* The *fuse_off_t* and *fuse_ino_t* changed from *unsigned long* to
+  *uint64_t*, i.e. they are now 64 bits also on 32-bit systems.
+
+* The type of the *generation* member of `struct fuse_entry_param*
+  changed from *unsigned* to *uint64_t*.
+
+* The (low-level) `setattr` handler gained a *FUSE_SET_ATTR_CTIME* bit
+  *for its *to_set* parameter.
+
+* The `struct fuse_session_ops` data structure has been dropped.
+
+* The documentation has been clarified and improved in many places.
+
+
+FUSE 2.9.7 (2016-06-20)
+=======================
+
+* Added SELinux support.
+* Fixed race-condition when session is terminated right after starting
+  a FUSE file system.
+
+FUSE 2.9.6 (2016-04-23)
+=======================
+
+* Tarball now includes documentation.
+* Shared-object version has now been bumped correctly.
+
+FUSE 2.9.5 (2016-01-14)
+=======================
+
+* New maintainer: Nikolaus Rath <Nikolaus@rath.org>. Many thanks to
+  Miklos Szeredi <miklos@szeredi.hu> for bringing FUSE to where it is
+  now!
+
+* fix warning in mount.c:receive_fd().  Reported by Albert Berger
+
+* fix possible memory leak.  Reported by Jose R. Guzman
+
+FUSE 2.9.4 (2015-05-22)
+=======================
+
+* fix exec environment for mount and umount.  Found by Tavis Ormandy
+  (CVE-2015-3202).
+
+* fix fuse_remove_signal_handlers() to properly restore the default
+  signal handler.  Reported by: Chris Johnson
+
+* highlevel API: fix directory file handle passed to ioctl() method.
+  Reported by Eric Biggers
+
+* libfuse: document deadlock avoidance for fuse_notify_inval_entry()
+  and fuse_notify_delete()
+
+* fusermount, libfuse: send value as unsigned in "user_id=" and
+  "group_id=" options.  Uids/gids larger than 2147483647 would result
+  in EINVAL when mounting the filesystem.  This also needs a fix in
+  the kernel.
+
+* Initilaize stat buffer passed to ->getattr() and ->fgetattr() to
+  zero in all cases.  Reported by Daniel Iwan
+
+* libfuse: Add missing includes.  This allows compiling fuse with
+  musl.  Patch by Daniel Thau
+
+
+Older Versions (before 2013-01-01)
+==================================
+
+Please see Git history, e.g. at
+https://github.com/libfuse/libfuse/blob/fuse_2_9_3/ChangeLog.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..4e95a71
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,33 @@
+## Process this file with automake to produce Makefile.in
+
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS = @subdirs2@ doc
+
+EXTRA_DIST = \
+	fuse3.pc.in \
+	README* \
+	test/*.py \
+	test/pytest.ini
+
+pkgconfigdir = @pkgconfigdir@
+pkgconfig_DATA = fuse3.pc
+
+$(pkgconfig_DATA): config.status
+
+.PHONY: setuid_fusermount
+setuid_fusermount:
+	@echo "Attempting to use sudo to make util/fusermount3 setuid root"
+	@echo "If this fails, set permissions manually and re-run make test"
+	test $$(ls -n util/fusermount3 | awk 'NR==1 {print $$3}') -eq 0 || \
+	    sudo chown root util/fusermount3
+	test -u util/fusermount3 || \
+	    sudo chmod u+s util/fusermount3
+
+# If we are not root, util/fusermount3 needs to be setuid root
+# for tests to work.
+
+test_deps = $(shell [ "$${UID}" -eq 0 ] || echo setuid_fusermount)
+.PHONY: test
+test: all $(test_deps)
+	python3 -m pytest test/
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ae41719
--- /dev/null
+++ b/README.md
@@ -0,0 +1,117 @@
+libfuse
+=======
+
+Warning: unresolved security issue
+----------------------------------
+
+Be aware that FUSE has an unresolved security bug
+([bug #15](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 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. Alternatively, the severity of the bug can be somewhat
+reduced by not using the `allow_other` mount option.
+
+
+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.
+
+
+Installation
+------------
+
+You can download libfuse from
+https://github.com/libfuse/libfuse/releases. After extracting the
+tarball, build and install with
+
+    ./configure
+    make -j8
+    make install
+
+To run some self tests, you need a Python 3 environment with the
+[py.test](http://www.pytest.org/) module installed. To run the tests,
+execute
+
+    python3 -m pytest test/
+
+You may also need to add `/usr/local/lib` to `/etc/ld.so.conf` and/or
+run *ldconfig*. If you're building from the git repository (instead of
+using a release tarball), you also need to run `./makeconf.sh` to
+create the `configure` script.
+
+You'll also need a fuse kernel module (Linux kernels 2.6.14 or later
+contain FUSE support).
+
+Security implications
+---------------------
+
+If you run `make install`, the *fusermount3* program is installed
+set-user-id to root.  This is done to allow normal users to mount
+their own filesystem implementations.
+
+There must however be some limitations, in order to prevent Bad User from
+doing nasty things.  Currently those limitations are:
+
+  - The user can only mount on a mountpoint, for which it has write
+    permission
+
+  - The mountpoint is not 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 `fuse.conf`)
+
+
+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.
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..d51e10a
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,131 @@
+AC_INIT(fuse, 3.0.0)
+AC_PREREQ(2.59d)
+AC_CONFIG_MACRO_DIR([m4])
+AC_CANONICAL_TARGET
+AM_INIT_AUTOMAKE([subdir-objects foreign])
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES(yes)])
+AC_CONFIG_HEADERS(include/config.h)
+
+AC_PROG_LIBTOOL
+AC_PROG_CC_STDC
+AC_SYS_LARGEFILE
+AC_PROG_MKDIR_P
+AM_PROG_CC_C_O
+
+case $target_os in
+     *linux*)	arch=linux;;
+     *netbsd*)  arch=netbsd;;
+     *bsd*)	arch=bsd;;
+     *)		arch=unknown;;
+esac
+
+if test "$ac_env_CFLAGS_set" != set; then
+	CFLAGS="-Wall -W -Wno-sign-compare -Wstrict-prototypes -Wmissing-declarations -Wwrite-strings -g -O2 -fno-strict-aliasing"
+fi
+
+AC_ARG_ENABLE(lib,
+	[  --enable-lib            Compile with library ])
+AC_ARG_ENABLE(util,
+	[  --enable-util           Compile with util ])
+AC_ARG_ENABLE(example,
+	[  --enable-example        Compile with examples ])
+AC_ARG_ENABLE(test,
+	[  --enable-test           Compile with tests ])
+AC_ARG_ENABLE(mtab,
+	[  --disable-mtab          Disable and ignore usage of /etc/mtab ])
+
+AC_ARG_WITH(pkgconfigdir,
+	    [  --with-pkgconfigdir=DIR      pkgconfig file in DIR @<:@LIBDIR/pkgconfig@:>@],
+	    [pkgconfigdir=$withval],
+	    [pkgconfigdir='${libdir}/pkgconfig'])
+AC_SUBST(pkgconfigdir)
+
+subdirs2="include"
+
+if test "$enable_lib" != "no"; then
+	subdirs2="$subdirs2 lib";
+fi
+if test "$arch" = linux -a "$enable_util" != "no"; then
+	subdirs2="$subdirs2 util";
+fi
+if test "$enable_example" != "no"; then
+	subdirs2="$subdirs2 example";
+fi
+if test "$enable_test" != "no"; then
+	subdirs2="$subdirs2 test";
+fi
+if test "$enable_mtab" = "no"; then
+	AC_DEFINE(IGNORE_MTAB, 1, [Don't update /etc/mtab])
+fi
+
+AC_CHECK_FUNCS([fork setxattr fdatasync splice vmsplice utimensat pipe2])
+AC_CHECK_FUNCS([posix_fallocate fstatat openat readlinkat])
+AC_CHECK_MEMBERS([struct stat.st_atim])
+AC_CHECK_MEMBERS([struct stat.st_atimespec])
+
+LIBS=
+AC_SEARCH_LIBS(dlopen, [dl])
+AC_SEARCH_LIBS(clock_gettime, [rt])
+libfuse_libs=$LIBS
+LIBS=
+AC_CHECK_LIB(ulockmgr, ulockmgr_op)
+passthrough_fh_libs=$LIBS
+AC_SUBST(passthrough_fh_libs)
+LIBS=
+
+AC_ARG_WITH([libiconv-prefix],
+[  --with-libiconv-prefix=DIR  search for libiconv in DIR/include and DIR/lib], [
+    for dir in `echo "$withval" | tr : ' '`; do
+      if test -d $dir/include; then CPPFLAGS="$CPPFLAGS -I$dir/include"; fi
+      if test -d $dir/lib; then LDFLAGS="$LDFLAGS -L$dir/lib"; fi
+    done
+   ])
+AM_ICONV
+libfuse_libs="$libfuse_libs $LTLIBICONV"
+AM_CONDITIONAL(ICONV, test "$am_cv_func_iconv" = yes)
+AC_SUBST(libfuse_libs)
+
+if test -z "$MOUNT_FUSE_PATH"; then
+	MOUNT_FUSE_PATH='${sbindir}'
+	AC_MSG_NOTICE([MOUNT_FUSE_PATH env var not set, using default $MOUNT_FUSE_PATH])
+fi
+AC_SUBST(MOUNT_FUSE_PATH)
+if test -z "$UDEV_RULES_PATH"; then
+	UDEV_RULES_PATH='${libdir}/udev/rules.d'
+	AC_MSG_NOTICE([UDEV_RULES_PATH env var not set, using default $UDEV_RULES_PATH])
+fi
+AC_SUBST(UDEV_RULES_PATH)
+if test -z "$INIT_D_PATH"; then
+	INIT_D_PATH='${sysconfdir}/init.d'
+	AC_MSG_NOTICE([INIT_D_PATH env var not set, using default $INIT_D_PATH])
+fi
+AC_SUBST(INIT_D_PATH)
+
+AC_SUBST(subdirs2)
+
+AM_CONDITIONAL(LINUX, test "$arch" = linux)
+AM_CONDITIONAL(NETBSD, test "$arch" = netbsd)
+AM_CONDITIONAL(BSD, test "$arch" = bsd)
+
+util_linux_ok=yes
+if test "$arch" = linux -a "$cross_compiling" != "yes"; then
+        AC_MSG_CHECKING([if umount supports --fake --no-canonicalize])
+        if umount --help 2>&1 | grep -q -- "--fake" &&
+           umount --help 2>&1 | grep -q -- "--no-canonicalize"; then
+                AC_MSG_RESULT([yes])
+        else
+                AC_MSG_RESULT([no])
+                util_linux_ok=no
+        fi
+fi
+
+AC_CONFIG_FILES([fuse3.pc Makefile lib/Makefile util/Makefile example/Makefile include/Makefile doc/Makefile test/Makefile])
+AC_OUTPUT
+
+if test "$util_linux_ok" = no; then
+	AC_MSG_WARN([
+******************************************************************
+* Please install util-linux version 2.18 or later which supports *
+* --fake and --no-canonicalize options in mount and umount       *
+******************************************************************])
+fi
diff --git a/doc/Doxyfile b/doc/Doxyfile
new file mode 100644
index 0000000..654c8af
--- /dev/null
+++ b/doc/Doxyfile
@@ -0,0 +1,1233 @@
+# Doxyfile 1.5.6
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = libfuse
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek,
+# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish,
+# Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish,
+# and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful is your file systems
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen to replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = YES
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = YES
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or define consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and defines in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.  This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = .
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.h *.c *.h *.dox
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix filesystem feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           = example
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = *.c *.h
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.  Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.  The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
+# is applied to all files.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20])
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to FRAME, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+,
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are
+# probably better off using the HTML help feature. Other possible values
+# for this tag are: HIERARCHIES, which will generate the Groups, Directories,
+# and Class Hiererachy pages using a tree view instead of an ordered list;
+# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which
+# disables this behavior completely. For backwards compatibility with previous
+# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE
+# respectively.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all function-like macros that are alone
+# on a line, have an all uppercase name, and do not end with a semicolon. Such
+# function macros are typically used for boiler-plate code, and will confuse
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#   TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#   TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option is superseded by the HAVE_DOT option below. This is only a
+# fallback. It is recommended to install and use dot, since it yields more
+# powerful graphs.
+
+CLASS_DIAGRAMS         = NO
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 1000
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is enabled by default, which results in a transparent
+# background. Warning: Depending on the platform used, enabling this option
+# may lead to badly anti-aliased labels on the edges of a graph (i.e. they
+# become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..8801da2
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,5 @@
+## Process this file with automake to produce Makefile.in
+
+dist_man_MANS = fusermount3.1 mount.fuse.8
+
+EXTRA_DIST = kernel.txt Doxyfile html README.NFS
diff --git a/doc/README.NFS b/doc/README.NFS
new file mode 100644
index 0000000..239dcb2
--- /dev/null
+++ b/doc/README.NFS
@@ -0,0 +1,33 @@
+NFS exporting is supported in Linux kernels 2.6.27 or later.
+
+You need to add an fsid=NNN option to /etc/exports to make exporting a
+FUSE directory work.
+
+Filesystem support
+------------------
+
+NFS exporting works to some extent on all fuse filesystems, but not
+perfectly.  This is due to the stateless nature of the protocol, the
+server has no way of knowing whether the client is keeping a reference
+to a file or not, and hence that file may be removed from the server's
+cache.  In that case there has to be a way to look up that object
+using the inode number, otherwise an ESTALE error will be returned.
+
+1) low-level interface
+
+Filesystems need to set FUSE_CAP_EXPORT_SUPPORT in conn->wants and
+implement special lookups for the names "." and "..". The former may
+be requested on any inode, including non-directories, while the latter
+is only requested for directories.  Otherwise these special lookups
+should behave identically to ordinary lookups.
+
+2) high-level interface
+
+Because the high-level interface is path based, it is not possible to
+delegate looking up by inode to the filesystem.
+
+To work around this, currently a "noforget" option is provided, which
+makes the library remember nodes forever.  This will make the NFS
+server happy, but also results in an ever growing memory footprint for
+the filesystem.  For this reason if the filesystem is large (or the
+memory is small), then this option is not recommended.
diff --git a/doc/fusermount3.1 b/doc/fusermount3.1
new file mode 100644
index 0000000..6f94e42
--- /dev/null
+++ b/doc/fusermount3.1
@@ -0,0 +1,43 @@
+.TH FUSERMOUNT3 1 2011\-10\-23 2.8.6 "Filesystem in Userspace (FUSE)"
+
+.SH NAME
+\fBfusermount3\fR \- mount and unmount FUSE filesystems
+
+.SH SYNOPSIS
+\fBfusermount3\fR [\fIOPTIONS\fR] \fIMOUNTPOINT\fR
+
+.SH DESCRIPTION
+Filesystem in Userspace (FUSE) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. It also aims to provide a secure method for non privileged users to create and mount their own filesystem implementations.
+.PP
+\fBfusermount3\fR is a program to mount and unmount FUSE
+filesystems. It should be called directly only for unmounting FUSE
+file systems. To allow mounting and unmounting by unprivileged users,
+\fBfusermount3\fR needs to be installed set-uid root.
+.SH OPTIONS
+.IP "\-h" 4
+print help.
+.IP "\-V" 4
+print version.
+.IP "-o \fIOPTION\fR[,\fIOPTION\fR...]" 4
+mount options.
+.IP "-u" 4
+unmount.
+.IP "-q" 4
+quiet.
+.IP "-z" 4
+lazy unmount.
+
+.SH SEE ALSO
+\fImount\fR(8),
+\fImount.fuse\fR(8),
+
+.SH HOMEPAGE
+More information about fusermount3 and the FUSE project can be found at <\fIhttp://fuse.sourceforge.net/\fR>.
+
+.SH AUTHORS
+.LP
+FUSE is currently maintained by Nikolaus Rath <Nikolaus@rath.org>
+.LP
+The original author of FUSE is Miklos Szeredi <\fImiklos@szeredi.hu\fR>.
+.LP
+This manual page was originally written by Daniel Baumann <\fIdaniel.baumann@progress\-technologies.net\fR>.
diff --git a/doc/kernel.txt b/doc/kernel.txt
new file mode 100644
index 0000000..e73484f
--- /dev/null
+++ b/doc/kernel.txt
@@ -0,0 +1,380 @@
+Definitions
+~~~~~~~~~~~
+
+Userspace filesystem:
+
+  A filesystem in which data and metadata are provided by an ordinary
+  userspace process.  The filesystem can be accessed normally through
+  the kernel interface.
+
+Filesystem daemon:
+
+  The process(es) providing the data and metadata of the filesystem.
+
+Non-privileged mount (or user mount):
+
+  A userspace filesystem mounted by a non-privileged (non-root) user.
+  The filesystem daemon is running with the privileges of the mounting
+  user.  NOTE: this is not the same as mounts allowed with the "user"
+  option in /etc/fstab, which is not discussed here.
+
+Filesystem connection:
+
+  A connection between the filesystem daemon and the kernel.  The
+  connection exists until either the daemon dies, or the filesystem is
+  umounted.  Note that detaching (or lazy umounting) the filesystem
+  does _not_ break the connection, in this case it will exist until
+  the last reference to the filesystem is released.
+
+Mount owner:
+
+  The user who does the mounting.
+
+User:
+
+  The user who is performing filesystem operations.
+
+What is FUSE?
+~~~~~~~~~~~~~
+
+FUSE is a userspace filesystem framework.  It consists of a kernel
+module (fuse.ko), a userspace library (libfuse.*) and a mount utility
+(fusermount3).
+
+One of the most important features of FUSE is allowing secure,
+non-privileged mounts.  This opens up new possibilities for the use of
+filesystems.  A good example is sshfs: a secure network filesystem
+using the sftp protocol.
+
+The userspace library and utilities are available from the FUSE
+homepage:
+
+  https://github.com/libfuse/libfuse/
+
+Filesystem type
+~~~~~~~~~~~~~~~
+
+The filesystem type given to mount(2) can be one of the following:
+
+'fuse'
+
+  This is the usual way to mount a FUSE filesystem.  The first
+  argument of the mount system call may contain an arbitrary string,
+  which is not interpreted by the kernel.
+
+'fuseblk'
+
+  The filesystem is block device based.  The first argument of the
+  mount system call is interpreted as the name of the device.
+
+Mount options
+~~~~~~~~~~~~~
+
+See mount.fuse(8).
+
+Control filesystem
+~~~~~~~~~~~~~~~~~~
+
+There's a control filesystem for FUSE, which can be mounted by:
+
+  mount -t fusectl none /sys/fs/fuse/connections
+
+Mounting it under the '/sys/fs/fuse/connections' directory makes it
+backwards compatible with earlier versions.
+
+Under the fuse control filesystem each connection has a directory
+named by a unique number.
+
+For each connection the following files exist within this directory:
+
+ 'waiting'
+
+  The number of requests which are waiting to be transferred to
+  userspace or being processed by the filesystem daemon.  If there is
+  no filesystem activity and 'waiting' is non-zero, then the
+  filesystem is hung or deadlocked.
+
+ 'abort'
+
+  Writing anything into this file will abort the filesystem
+  connection.  This means that all waiting requests will be aborted an
+  error returned for all aborted and new requests.
+
+Only the owner of the mount may read or write these files.
+
+Interrupting filesystem operations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a process issuing a FUSE filesystem request is interrupted, the
+following will happen:
+
+  1) If the request is not yet sent to userspace AND the signal is
+     fatal (SIGKILL or unhandled fatal signal), then the request is
+     dequeued and returns immediately.
+
+  2) If the request is not yet sent to userspace AND the signal is not
+     fatal, then an 'interrupted' flag is set for the request.  When
+     the request has been successfully transferred to userspace and
+     this flag is set, an INTERRUPT request is queued.
+
+  3) If the request is already sent to userspace, then an INTERRUPT
+     request is queued.
+
+INTERRUPT requests take precedence over other requests, so the
+userspace filesystem will receive queued INTERRUPTs before any others.
+
+The userspace filesystem may ignore the INTERRUPT requests entirely,
+or may honor them by sending a reply to the _original_ request, with
+the error set to EINTR.
+
+It is also possible that there's a race between processing the
+original request and it's INTERRUPT request.  There are two possibilities:
+
+  1) The INTERRUPT request is processed before the original request is
+     processed
+
+  2) The INTERRUPT request is processed after the original request has
+     been answered
+
+If the filesystem cannot find the original request, it should wait for
+some timeout and/or a number of new requests to arrive, after which it
+should reply to the INTERRUPT request with an EAGAIN error.  In case
+1) the INTERRUPT request will be requeued.  In case 2) the INTERRUPT
+reply will be ignored.
+
+Aborting a filesystem connection
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is possible to get into certain situations where the filesystem is
+not responding.  Reasons for this may be:
+
+  a) Broken userspace filesystem implementation
+
+  b) Network connection down
+
+  c) Accidental deadlock
+
+  d) Malicious deadlock
+
+(For more on c) and d) see later sections)
+
+In either of these cases it may be useful to abort the connection to
+the filesystem.  There are several ways to do this:
+
+  - Kill the filesystem daemon.  Works in case of a) and b)
+
+  - Kill the filesystem daemon and all users of the filesystem.  Works
+    in all cases except some malicious deadlocks
+
+  - Use forced umount (umount -f).  Works in all cases but only if
+    filesystem is still attached (it hasn't been lazy unmounted)
+
+  - Abort filesystem through the FUSE control filesystem.  Most
+    powerful method, always works.
+
+How do non-privileged mounts work?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Since the mount() system call is a privileged operation, a helper
+program (fusermount3) is needed, which is installed setuid root.
+
+The implication of providing non-privileged mounts is that the mount
+owner must not be able to use this capability to compromise the
+system.  Obvious requirements arising from this are:
+
+ A) mount owner should not be able to get elevated privileges with the
+    help of the mounted filesystem
+
+ B) mount owner should not get illegitimate access to information from
+    other users' and the super user's processes
+
+ C) mount owner should not be able to induce undesired behavior in
+    other users' or the super user's processes
+
+How are requirements fulfilled?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ A) The mount owner could gain elevated privileges by either:
+
+     1) creating a filesystem containing a device file, then opening
+	this device
+
+     2) creating a filesystem containing a suid or sgid application,
+	then executing this application
+
+    The solution is not to allow opening device files and ignore
+    setuid and setgid bits when executing programs.  To ensure this
+    fusermount3 always adds "nosuid" and "nodev" to the mount options
+    for non-privileged mounts.
+
+ B) If another user is accessing files or directories in the
+    filesystem, the filesystem daemon serving requests can record the
+    exact sequence and timing of operations performed.  This
+    information is otherwise inaccessible to the mount owner, so this
+    counts as an information leak.
+
+    The solution to this problem will be presented in point 2) of C).
+
+ C) There are several ways in which the mount owner can induce
+    undesired behavior in other users' processes, such as:
+
+     1) mounting a filesystem over a file or directory which the mount
+        owner could otherwise not be able to modify (or could only
+        make limited modifications).
+
+        This is solved in fusermount3, by checking the access
+        permissions on the mountpoint and only allowing the mount if
+        the mount owner can do unlimited modification (has write
+        access to the mountpoint, and mountpoint is not a "sticky"
+        directory)
+
+     2) Even if 1) is solved the mount owner can change the behavior
+        of other users' processes.
+
+         i) It can slow down or indefinitely delay the execution of a
+           filesystem operation creating a DoS against the user or the
+           whole system.  For example a suid application locking a
+           system file, and then accessing a file on the mount owner's
+           filesystem could be stopped, and thus causing the system
+           file to be locked forever.
+
+         ii) It can present files or directories of unlimited length, or
+           directory structures of unlimited depth, possibly causing a
+           system process to eat up diskspace, memory or other
+           resources, again causing DoS.
+
+	The solution to this as well as B) is not to allow processes
+	to access the filesystem, which could otherwise not be
+	monitored or manipulated by the mount owner.  Since if the
+	mount owner can ptrace a process, it can do all of the above
+	without using a FUSE mount, the same criteria as used in
+	ptrace can be used to check if a process is allowed to access
+	the filesystem or not.
+
+	Note that the ptrace check is not strictly necessary to
+	prevent B/2/i, it is enough to check if mount owner has enough
+	privilege to send signal to the process accessing the
+	filesystem, since SIGSTOP can be used to get a similar effect.
+
+I think these limitations are unacceptable?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a sysadmin trusts the users enough, or can ensure through other
+measures, that system processes will never enter non-privileged
+mounts, it can relax the last limitation with a "user_allow_other"
+config option.  If this config option is set, the mounting user can
+add the "allow_other" mount option which disables the check for other
+users' processes.
+
+Kernel - userspace interface
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following diagram shows how a filesystem operation (in this
+example unlink) is performed in FUSE.
+
+NOTE: everything in this description is greatly simplified
+
+ |  "rm /mnt/fuse/file"               |  FUSE filesystem daemon
+ |                                    |
+ |                                    |  >sys_read()
+ |                                    |    >fuse_dev_read()
+ |                                    |      >request_wait()
+ |                                    |        [sleep on fc->waitq]
+ |                                    |
+ |  >sys_unlink()                     |
+ |    >fuse_unlink()                  |
+ |      [get request from             |
+ |       fc->unused_list]             |
+ |      >request_send()               |
+ |        [queue req on fc->pending]  |
+ |        [wake up fc->waitq]         |        [woken up]
+ |        >request_wait_answer()      |
+ |          [sleep on req->waitq]     |
+ |                                    |      <request_wait()
+ |                                    |      [remove req from fc->pending]
+ |                                    |      [copy req to read buffer]
+ |                                    |      [add req to fc->processing]
+ |                                    |    <fuse_dev_read()
+ |                                    |  <sys_read()
+ |                                    |
+ |                                    |  [perform unlink]
+ |                                    |
+ |                                    |  >sys_write()
+ |                                    |    >fuse_dev_write()
+ |                                    |      [look up req in fc->processing]
+ |                                    |      [remove from fc->processing]
+ |                                    |      [copy write buffer to req]
+ |          [woken up]                |      [wake up req->waitq]
+ |                                    |    <fuse_dev_write()
+ |                                    |  <sys_write()
+ |        <request_wait_answer()      |
+ |      <request_send()               |
+ |      [add request to               |
+ |       fc->unused_list]             |
+ |    <fuse_unlink()                  |
+ |  <sys_unlink()                     |
+
+There are a couple of ways in which to deadlock a FUSE filesystem.
+Since we are talking about unprivileged userspace programs,
+something must be done about these.
+
+Scenario 1 -  Simple deadlock
+-----------------------------
+
+ |  "rm /mnt/fuse/file"               |  FUSE filesystem daemon
+ |                                    |
+ |  >sys_unlink("/mnt/fuse/file")     |
+ |    [acquire inode semaphore        |
+ |     for "file"]                    |
+ |    >fuse_unlink()                  |
+ |      [sleep on req->waitq]         |
+ |                                    |  <sys_read()
+ |                                    |  >sys_unlink("/mnt/fuse/file")
+ |                                    |    [acquire inode semaphore
+ |                                    |     for "file"]
+ |                                    |    *DEADLOCK*
+
+The solution for this is to allow the filesystem to be aborted.
+
+Scenario 2 - Tricky deadlock
+----------------------------
+
+This one needs a carefully crafted filesystem.  It's a variation on
+the above, only the call back to the filesystem is not explicit,
+but is caused by a pagefault.
+
+ |  Kamikaze filesystem thread 1      |  Kamikaze filesystem thread 2
+ |                                    |
+ |  [fd = open("/mnt/fuse/file")]     |  [request served normally]
+ |  [mmap fd to 'addr']               |
+ |  [close fd]                        |  [FLUSH triggers 'magic' flag]
+ |  [read a byte from addr]           |
+ |    >do_page_fault()                |
+ |      [find or create page]         |
+ |      [lock page]                   |
+ |      >fuse_readpage()              |
+ |         [queue READ request]       |
+ |         [sleep on req->waitq]      |
+ |                                    |  [read request to buffer]
+ |                                    |  [create reply header before addr]
+ |                                    |  >sys_write(addr - headerlength)
+ |                                    |    >fuse_dev_write()
+ |                                    |      [look up req in fc->processing]
+ |                                    |      [remove from fc->processing]
+ |                                    |      [copy write buffer to req]
+ |                                    |        >do_page_fault()
+ |                                    |           [find or create page]
+ |                                    |           [lock page]
+ |                                    |           * DEADLOCK *
+
+Solution is basically the same as above.
+
+An additional problem is that while the write buffer is being copied
+to the request, the request must not be interrupted/aborted.  This is
+because the destination address of the copy may not be valid after the
+request has returned.
+
+This is solved with doing the copy atomically, and allowing abort
+while the page(s) belonging to the write buffer are faulted with
+get_user_pages().  The 'req->locked' flag indicates when the copy is
+taking place, and abort is delayed until this flag is unset.
diff --git a/doc/mainpage.dox b/doc/mainpage.dox
new file mode 100644
index 0000000..dc64868
--- /dev/null
+++ b/doc/mainpage.dox
@@ -0,0 +1,39 @@
+/*!
+\mainpage libfuse API documentation
+
+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. 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.
+
+
+## Getting started ##
+
+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.
+
+The high-level API that is primarily specified in fuse.h. The
+low-level API that is primarily documented in fuse_lowlevel.h.
+
+## Examples ##
+
+FUSE comes with several examples in the <a
+href="files.html">examples</a> directory. A good starting point are
+hello.c (for the high-level API) and hello_ll.c (for the low-level
+API).
+
+
+*/
diff --git a/doc/mount.fuse.8 b/doc/mount.fuse.8
new file mode 100644
index 0000000..c42894b
--- /dev/null
+++ b/doc/mount.fuse.8
@@ -0,0 +1,243 @@
+.TH fuse "8"
+.SH NAME
+fuse \- configuration and mount options for FUSE file systems
+.SH DESCRIPTION
+FUSE (Filesystem in Userspace) is a simple interface for userspace programs to export a virtual filesystem to the Linux kernel. FUSE also aims to provide a secure method for non privileged users to create and mount their own filesystem implementations.
+.SH DEFINITIONS
+.TP
+\fBFUSE\fP
+The in-kernel filesystem that forwards requests to a user-space
+process.
+.TP
+\fBfilesystem\fP
+The user-space process that responds to requests received from the
+kernel.
+.TP
+\fBlibfuse\fP
+The shared library that most (user-space) filesystems use to
+communicate with FUSE (the kernel filesystem). libfuse also provides
+the \fBfusermount3\fP (or \fBfusermount\fP if you have older version of
+libfuse) helper to allow non-privileged users to mount filesystems.
+.TP
+\fBfilesystem owner\fP
+The user that starts the filesystem and instructs the kernel to
+associate it with a particular mountpoint. The latter is typically done
+by the filesystem itself on start-up. When using libfuse, this is done
+by calling the \fBfusermount3\fP utility.
+.TP
+\fBclient\fP
+Any process that interacts with the mountpoint.
+.SH CONFIGURATION
+Some options regarding mount policy can be set in the file \fI/etc/fuse.conf\fP. Currently these options are:
+.TP
+\fBmount_max = NNN\fP
+Set the maximum number of FUSE mounts allowed to non-root users. The default is 1000.
+.TP
+\fBuser_allow_other\fP
+Allow non-root users to specify the \fBallow_other\fP or
+\fBallow_root\fP mount options (see below).
+.TP
+These limits are enforced by the \fBfusermount3\fP helper, so they can be avoided by filesystems that run as root.
+.SH OPTIONS
+Most of the generic mount options described in \fBmount\fP are
+supported (\fBro\fP, \fBrw\fP, \fBsuid\fP, \fBnosuid\fP, \fBdev\fP,
+\fBnodev\fP, \fBexec\fP, \fBnoexec\fP, \fBatime\fP, \fBnoatime\fP,
+\fBsync\fP, \fBasync\fP, \fBdirsync\fP). Filesystems are mounted with
+\fBnodev,nosuid\fP by default, which can only be overridden by a
+privileged user.
+.SS "General mount options:"
+These are FUSE specific mount options that can be specified for all filesystems:
+.TP
+\fBdefault_permissions\fP
+This option instructs the kernel to perform its own permission check
+instead of deferring all permission checking to the
+filesystem. The check by the kernel is done in addition to any
+permission checks by the filesystem, and both have to succeed for an
+operation to be allowed. The kernel performs a standard UNIX permission
+check (based on mode bits and ownership of the directory entry, and
+uid/gid of the client).
+
+This mount option is activated implicitly if the filesystem enables
+ACL support during the initial feature negotiation when opening the
+device fd. In this case, the kernel performs both ACL and standard
+unix permission checking.
+
+Filesystems that do not implement any permission checking should
+generally add this option internally.
+.TP
+\fBallow_other\fP
+This option overrides the security measure
+restricting file access to the filesystem owner, so that all users
+(including root) can access the files.
+.TP
+\fBrootmode=M\fP
+Specifies the the file mode of the filesystem's root (in octal
+representation).
+.TP
+\fBblkdev\fP
+Mount a filesystem backed by a block device.  This is a privileged
+option. The device must be specified with the \fBfsname=NAME\fP
+option.
+.TP
+\fBblksize=N\fP
+Set the block size for the filesystem. This option is only valid
+for 'fuseblk' type mounts. The default is 512.
+
+In most cases, this option should not be specified by
+the filesystem owner but set internally by the filesystem.
+.TP
+\fBmax_read=N\fP
+With this option the maximum size of read operations can be set. The
+default is infinite, but typically the kernel enforces its own limit
+in addition to this one. A value of zero corresponds to no limit.
+
+This option should not be specified by the filesystem owner. The
+correct (or optimum) value depends on the filesystem implementation
+and should thus be set by the filesystem internally.
+
+This mount option is deprecated in favor of direct negotiation over
+the device fd (as done for e.g. the maximum size of write
+operations). For the time being, libfuse-using filesystems that want
+to limit the read size must therefore use this mount option \fIand\fP
+set the same value again in the init() handler.
+.TP
+\fBfd=N\fP
+The file descriptor to use for communication between the userspace
+filesystem and the kernel.  The file descriptor must have been
+obtained by opening the FUSE device (/dev/fuse).
+
+This option should not be specified by the filesystem owner. It is set
+by libfuse (or, if libfuse is not used, must be set by the filesystem
+itself).
+.TP
+\fBuser_id=N\fP
+\fBgroup_id=N\fP
+Specifies the numeric uid/gid of the mount owner.
+
+This option should not be specified by the filesystem owner. It is set
+by libfuse (or, if libfuse is not used, must be set by the filesystem
+itself).
+.TP
+\fBfsname=NAME\fP
+Sets the filesystem source (first field in \fI/etc/mtab\fP). The
+default is the name of the filesystem process.
+.TP
+\fBsubtype=TYPE\fP
+Sets the filesystem type (third field in \fI/etc/mtab\fP). The default
+is the name of the filesystem process. If the kernel suppports it, \fI/etc/mtab\fP and \fI/proc/mounts\fP will show the filesystem type as \fBfuse.TYPE\fP
+
+If the kernel doesn't support subtypes, the source filed will be
+\fBTYPE#NAME\fP, or if \fBfsname\fP option is not specified, just
+\fBTYPE\fP.
+
+.SS "libfuse-specific mount options:"
+These following options are not actually passed to the kernel but
+interpreted by libfuse. They can be specified for all filesystems
+that use libfuse:
+.TP
+\fBallow_root\fP
+This option is similar to \fBallow_other\fP but file access is limited
+to the filesystem owner and root.  This option and \fBallow_other\fP are mutually exclusive.
+.TP
+\fBauto_unmount\fP
+This option enables automatic release of the mountpoint if filesystem
+terminates for any reason. Normally the filesystem is
+responsible for releasing the mountpoint, which means that the
+mountpoint becomes inaccessible if the filesystem terminates
+without first unmounting.
+
+.SS "High-level mount options:"
+These following options are not actually passed to the kernel but
+interpreted by libfuse. They can only be specified for filesystems
+that use the high-level libfuse API:
+.TP
+\fBkernel_cache\fP
+This option disables flushing the cache of the file contents on every \fBopen\fP(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.
+
+\fBNOTE\fP: if this option is not specified (and neither \fBdirect_io\fP) data is still cached after the \fBopen\fP(2), so a \fBread\fP(2) system call will not always initiate a read operation.
+.TP
+\fBauto_cache\fP
+This option is an alternative to
+\fBkernel_cache\fP. Instead of unconditionally keeping cached data, the
+cached data is invalidated on \fBopen\fP(2) if the modification
+time or the size of the file has changed since it was last opened.
+.TP
+\fBumask=M\fP
+Override the permission bits in \fIst_mode\fP set by the filesystem. The resulting permission bits are the ones missing from the given umask value.  The value is given in octal representation.
+.TP
+\fBuid=N\fP
+Override the \fIst_uid\fP field set by the filesystem (N is numeric).
+.TP
+\fBgid=N\fP
+Override the \fIst_gid\fP field set by the filesystem (N is numeric).
+.TP
+\fBentry_timeout=T\fP
+The timeout in seconds for which name lookups will be cached. The default is 1.0 second. For all the timeout options, it is possible to give fractions of a second as well (e.g. \fBentry_timeout=2.8\fP)
+.TP
+\fBnegative_timeout=T\fP
+The timeout in seconds for which a negative lookup will be cached. This means, that if file did not exist (lookup retuned \fBENOENT\fP), the lookup will only be redone after the timeout, and the file/directory will be assumed to not exist until then.  The default is 0.0 second, meaning that caching negative lookups are disabled.
+.TP
+\fBattr_timeout=T\fP
+The timeout in seconds for which file/directory attributes are cached.  The default is 1.0 second.
+.TP
+\fBac_attr_timeout=T\fP
+The timeout in seconds for which file attributes are cached for the purpose of checking if \fBauto_cache\fP should flush the file data on  open. The default is the value of \fBattr_timeout\fP
+.TP
+\fBnoforget\fP
+.TP
+\fBremember=T\fP
+Normally, libfuse assigns inodes to paths only for as long as the kernel
+is aware of them. With this option inodes are instead assigned
+for at least \fBT\fP seconds (or, in the case of \fBnoforget\fP,
+the life-time of the filesystem). This will require more
+memory, but may be necessary when using applications that make use of
+inode numbers.
+.TP
+\fBmodules=M1[:M2...]\fP
+Add modules to the filesystem stack.  Modules are pushed in the order they are specified, with the original filesystem being on the bottom of the stack.
+.SH FUSE MODULES (STACKING)
+Modules are filesystem stacking support to high level API. Filesystem modules can be built into libfuse or loaded from shared object
+.SS "iconv"
+Perform file name character set conversion.  Options are:
+.TP
+\fBfrom_code=CHARSET\fP
+Character set to convert from (see \fBiconv -l\fP for a list of possible values). Default is \fBUTF-8\fP.
+.TP
+\fBto_code=CHARSET\fP
+Character set to convert to.  Default is determined by the current locale.
+.SS "subdir"
+Prepend a given directory to each path. Options are:
+.TP
+\fBsubdir=DIR\fP
+Directory to prepend to all paths.  This option is \fImandatory\fP.
+.TP
+\fBrellinks\fP
+Transform absolute symlinks into relative
+.TP
+\fBnorellinks\fP
+Do not transform absolute symlinks into relative.  This is the default.
+.SH SECURITY
+The fusermount3 program is installed set-user-gid to fuse. This is done to allow users from fuse group to mount
+their own filesystem implementations.
+There must however be some limitations, in order to prevent Bad User from
+doing nasty things.  Currently those limitations are:
+.IP 1.
+The user can only mount on a mountpoint, for which it has write permission
+.IP 2.
+The mountpoint is not a sticky directory which isn't owned by the user (like \fI/tmp\fP usually is)
+.IP 3.
+No other user (including root) can access the contents of the mounted filesystem.
+.SH NOTE
+FUSE filesystems are unmounted using the \fBfusermount3\fP(1) command (\fBfusermount3 -u mountpoint\fP).
+.SH "AUTHORS"
+.LP
+FUSE is currently maintained by Nikolaus Rath <Nikolaus@rath.org>
+.LP
+The original author of FUSE is Miklos Szeredi <mszeredi@inf.bme.hu>.
+.LP
+This man page was originally written by Bastien Roucaries <roucaries.bastien+debian@gmail.com> for the
+Debian GNU/Linux distribution.
+.SH SEE ALSO
+.BR fusermount3 (1)
+.BR fusermount (1)
+.BR mount (8)
diff --git a/example/Makefile.am b/example/Makefile.am
new file mode 100644
index 0000000..6e267a0
--- /dev/null
+++ b/example/Makefile.am
@@ -0,0 +1,19 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/include -D_REENTRANT
+noinst_HEADERS = ioctl.h
+noinst_PROGRAMS = passthrough passthrough_fh hello hello_ll \
+		  ioctl ioctl_client poll poll_client \
+		  passthrough_ll notify_inval_inode \
+		  notify_store_retrieve notify_inval_entry \
+		  cuse cuse_client
+
+LDADD = ../lib/libfuse3.la
+passthrough_fh_LDADD = ../lib/libfuse3.la @passthrough_fh_libs@
+
+ioctl_client_CPPFLAGS =
+ioctl_client_LDFLAGS =
+ioctl_client_LDADD =
+poll_client_CPPFLAGS =
+poll_client_LDFLAGS =
+poll_client_LDADD =
diff --git a/example/cuse.c b/example/cuse.c
new file mode 100644
index 0000000..15e2d37
--- /dev/null
+++ b/example/cuse.c
@@ -0,0 +1,322 @@
+/*
+  CUSE example: 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 GPL.
+  See the file COPYING.
+
+*/
+
+/** @file
+ *
+ * This example demonstrates how to implement a character device in
+ * userspace ("CUSE"). This is only allowed for root. The character
+ * device should appear in /dev under the specified name. It can be
+ * tested with the cuse_client.c program.
+ *
+ * Mount the file system with:
+ *
+ *     cuse -f --name=mydevice
+ *
+ * You should now have a new /dev/mydevice character device. To "unmount" it,
+ * kill the "cuse" process.
+ *
+ * To compile this example, run
+ *
+ *     gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse
+ *
+ * ## Source code ##
+ * \include cuse.c
+ */
+
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <cuse_lowlevel.h>
+#include <fuse_opt.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "ioctl.h"
+
+static void *cusexmp_buf;
+static size_t cusexmp_size;
+
+static const char *usage =
+"usage: cusexmp [options]\n"
+"\n"
+"options:\n"
+"    --help|-h             print this help message\n"
+"    --maj=MAJ|-M MAJ      device major number\n"
+"    --min=MIN|-m MIN      device minor number\n"
+"    --name=NAME|-n NAME   device name (mandatory)\n"
+"    -d   -o debug         enable debug output (implies -f)\n"
+"    -f                    foreground operation\n"
+"    -s                    disable multi-threaded operation\n"
+"\n";
+
+static int cusexmp_resize(size_t new_size)
+{
+	void *new_buf;
+
+	if (new_size == cusexmp_size)
+		return 0;
+
+	new_buf = realloc(cusexmp_buf, new_size);
+	if (!new_buf && new_size)
+		return -ENOMEM;
+
+	if (new_size > cusexmp_size)
+		memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size);
+
+	cusexmp_buf = new_buf;
+	cusexmp_size = new_size;
+
+	return 0;
+}
+
+static int cusexmp_expand(size_t new_size)
+{
+	if (new_size > cusexmp_size)
+		return cusexmp_resize(new_size);
+	return 0;
+}
+
+static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
+{
+	fuse_reply_open(req, fi);
+}
+
+static void cusexmp_read(fuse_req_t req, size_t size, off_t off,
+			 struct fuse_file_info *fi)
+{
+	(void)fi;
+
+	if (off >= cusexmp_size)
+		off = cusexmp_size;
+	if (size > cusexmp_size - off)
+		size = cusexmp_size - off;
+
+	fuse_reply_buf(req, cusexmp_buf + off, size);
+}
+
+static void cusexmp_write(fuse_req_t req, const char *buf, size_t size,
+			  off_t off, struct fuse_file_info *fi)
+{
+	(void)fi;
+
+	if (cusexmp_expand(off + size)) {
+		fuse_reply_err(req, ENOMEM);
+		return;
+	}
+
+	memcpy(cusexmp_buf + off, buf, size);
+	fuse_reply_write(req, size);
+}
+
+static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf,
+		       size_t in_bufsz, size_t out_bufsz, int is_read)
+{
+	const struct fioc_rw_arg *arg;
+	struct iovec in_iov[2], out_iov[3], iov[3];
+	size_t cur_size;
+
+	/* read in arg */
+	in_iov[0].iov_base = addr;
+	in_iov[0].iov_len = sizeof(*arg);
+	if (!in_bufsz) {
+		fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0);
+		return;
+	}
+	arg = in_buf;
+	in_buf += sizeof(*arg);
+	in_bufsz -= sizeof(*arg);
+
+	/* prepare size outputs */
+	out_iov[0].iov_base =
+		addr + (unsigned long)&(((struct fioc_rw_arg *)0)->prev_size);
+	out_iov[0].iov_len = sizeof(arg->prev_size);
+
+	out_iov[1].iov_base =
+		addr + (unsigned long)&(((struct fioc_rw_arg *)0)->new_size);
+	out_iov[1].iov_len = sizeof(arg->new_size);
+
+	/* prepare client buf */
+	if (is_read) {
+		out_iov[2].iov_base = arg->buf;
+		out_iov[2].iov_len = arg->size;
+		if (!out_bufsz) {
+			fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3);
+			return;
+		}
+	} else {
+		in_iov[1].iov_base = arg->buf;
+		in_iov[1].iov_len = arg->size;
+		if (arg->size && !in_bufsz) {
+			fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2);
+			return;
+		}
+	}
+
+	/* we're all set */
+	cur_size = cusexmp_size;
+	iov[0].iov_base = &cur_size;
+	iov[0].iov_len = sizeof(cur_size);
+
+	iov[1].iov_base = &cusexmp_size;
+	iov[1].iov_len = sizeof(cusexmp_size);
+
+	if (is_read) {
+		size_t off = arg->offset;
+		size_t size = arg->size;
+
+		if (off >= cusexmp_size)
+			off = cusexmp_size;
+		if (size > cusexmp_size - off)
+			size = cusexmp_size - off;
+
+		iov[2].iov_base = cusexmp_buf + off;
+		iov[2].iov_len = size;
+		fuse_reply_ioctl_iov(req, size, iov, 3);
+	} else {
+		if (cusexmp_expand(arg->offset + in_bufsz)) {
+			fuse_reply_err(req, ENOMEM);
+			return;
+		}
+
+		memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz);
+		fuse_reply_ioctl_iov(req, in_bufsz, iov, 2);
+	}
+}
+
+static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg,
+			  struct fuse_file_info *fi, unsigned flags,
+			  const void *in_buf, size_t in_bufsz, size_t out_bufsz)
+{
+	int is_read = 0;
+
+	(void)fi;
+
+	if (flags & FUSE_IOCTL_COMPAT) {
+		fuse_reply_err(req, ENOSYS);
+		return;
+	}
+
+	switch (cmd) {
+	case FIOC_GET_SIZE:
+		if (!out_bufsz) {
+			struct iovec iov = { arg, sizeof(size_t) };
+
+			fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
+		} else
+			fuse_reply_ioctl(req, 0, &cusexmp_size,
+					 sizeof(cusexmp_size));
+		break;
+
+	case FIOC_SET_SIZE:
+		if (!in_bufsz) {
+			struct iovec iov = { arg, sizeof(size_t) };
+
+			fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
+		} else {
+			cusexmp_resize(*(size_t *)in_buf);
+			fuse_reply_ioctl(req, 0, NULL, 0);
+		}
+		break;
+
+	case FIOC_READ:
+		is_read = 1;
+		/* no break */
+	case FIOC_WRITE:
+		fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read);
+		break;
+
+	default:
+		fuse_reply_err(req, EINVAL);
+	}
+}
+
+struct cusexmp_param {
+	unsigned		major;
+	unsigned		minor;
+	char			*dev_name;
+	int			is_help;
+};
+
+#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 }
+
+static const struct fuse_opt cusexmp_opts[] = {
+	CUSEXMP_OPT("-M %u",		major),
+	CUSEXMP_OPT("--maj=%u",		major),
+	CUSEXMP_OPT("-m %u",		minor),
+	CUSEXMP_OPT("--min=%u",		minor),
+	CUSEXMP_OPT("-n %s",		dev_name),
+	CUSEXMP_OPT("--name=%s",	dev_name),
+	FUSE_OPT_KEY("-h",		0),
+	FUSE_OPT_KEY("--help",		0),
+	FUSE_OPT_END
+};
+
+static int cusexmp_process_arg(void *data, const char *arg, int key,
+			       struct fuse_args *outargs)
+{
+	struct cusexmp_param *param = data;
+
+	(void)outargs;
+	(void)arg;
+
+	switch (key) {
+	case 0:
+		param->is_help = 1;
+		fprintf(stderr, "%s", usage);
+		return fuse_opt_add_arg(outargs, "-ho");
+	default:
+		return 1;
+	}
+}
+
+static const struct cuse_lowlevel_ops cusexmp_clop = {
+	.open		= cusexmp_open,
+	.read		= cusexmp_read,
+	.write		= cusexmp_write,
+	.ioctl		= cusexmp_ioctl,
+};
+
+int main(int argc, char **argv)
+{
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+	struct cusexmp_param param = { 0, 0, NULL, 0 };
+	char dev_name[128] = "DEVNAME=";
+	const char *dev_info_argv[] = { dev_name };
+	struct cuse_info ci;
+
+	if (fuse_opt_parse(&args, &param, cusexmp_opts, cusexmp_process_arg)) {
+		printf("failed to parse option\n");
+		return 1;
+	}
+
+	if (!param.is_help) {
+		if (!param.dev_name) {
+			fprintf(stderr, "Error: device name missing\n");
+			return 1;
+		}
+		strncat(dev_name, param.dev_name, sizeof(dev_name) - 9);
+	}
+
+	memset(&ci, 0, sizeof(ci));
+	ci.dev_major = param.major;
+	ci.dev_minor = param.minor;
+	ci.dev_info_argc = 1;
+	ci.dev_info_argv = dev_info_argv;
+	ci.flags = CUSE_UNRESTRICTED_IOCTL;
+
+	return cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop,
+				  NULL);
+}
diff --git a/example/cuse_client.c b/example/cuse_client.c
new file mode 100755
index 0000000..cc85c20
--- /dev/null
+++ b/example/cuse_client.c
@@ -0,0 +1,152 @@
+/*
+  FUSE fioclient: FUSE ioctl example client
+  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 GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This program tests the cuse.c example file system.
+ *
+ * Example usage (assuming that /dev/foobar is a CUSE device provided
+ * by the cuse.c example file system):
+ *
+ *     $ cuse_client /dev/foobar s
+ *     0
+ *
+ *     $ echo "hello" | cuse_client /dev/foobar w 6
+ *     Writing 6 bytes
+ *     transferred 6 bytes (0 -> 6)
+ *
+ *     $ cuse_client /dev/foobar s
+ *     6
+ *
+ *     $ cuse_client /dev/foobar r 10
+ *     hello
+ *     transferred 6 bytes (6 -> 6)
+ *
+ * Compiling this example
+ *
+ *     gcc -Wall cuse_client.c -o cuse_client
+ *
+ * ## Source Code ##
+ * \include cuse_client.c
+ */
+
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "ioctl.h"
+
+const char *usage =
+"Usage: cuse_client FIOC_FILE COMMAND\n"
+"\n"
+"COMMANDS\n"
+"  s [SIZE]     : get size if SIZE is omitted, set size otherwise\n"
+"  r SIZE [OFF] : read SIZE bytes @ OFF (dfl 0) and output to stdout\n"
+"  w SIZE [OFF] : write SIZE bytes @ OFF (dfl 0) from stdin\n"
+"\n";
+
+static int do_rw(int fd, int is_read, size_t size, off_t offset,
+		 size_t *prev_size, size_t *new_size)
+{
+	struct fioc_rw_arg arg = { .offset = offset };
+	ssize_t ret;
+
+	arg.buf = calloc(1, size);
+	if (!arg.buf) {
+		fprintf(stderr, "failed to allocated %zu bytes\n", size);
+		return -1;
+	}
+
+	if (is_read) {
+		arg.size = size;
+		ret = ioctl(fd, FIOC_READ, &arg);
+		if (ret >= 0)
+			fwrite(arg.buf, 1, ret, stdout);
+	} else {
+		arg.size = fread(arg.buf, 1, size, stdin);
+		fprintf(stderr, "Writing %zu bytes\n", arg.size);
+		ret = ioctl(fd, FIOC_WRITE, &arg);
+	}
+
+	if (ret >= 0) {
+		*prev_size = arg.prev_size;
+		*new_size = arg.new_size;
+	} else
+		perror("ioctl");
+
+	free(arg.buf);
+	return ret;
+}
+
+int main(int argc, char **argv)
+{
+	size_t param[2] = { };
+	size_t size, prev_size = 0, new_size = 0;
+	char cmd;
+	int fd, i, rc;
+
+	if (argc < 3)
+		goto usage;
+
+	fd = open(argv[1], O_RDWR);
+	if (fd < 0) {
+		perror("open");
+		return 1;
+	}
+
+	cmd = tolower(argv[2][0]);
+	argc -= 3;
+	argv += 3;
+
+	for (i = 0; i < argc; i++) {
+		char *endp;
+		param[i] = strtoul(argv[i], &endp, 0);
+		if (endp == argv[i] || *endp != '\0')
+			goto usage;
+	}
+
+	switch (cmd) {
+	case 's':
+		if (!argc) {
+			if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+				perror("ioctl");
+				return 1;
+			}
+			printf("%zu\n", size);
+		} else {
+			size = param[0];
+			if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+				perror("ioctl");
+				return 1;
+			}
+		}
+		return 0;
+
+	case 'r':
+	case 'w':
+		rc = do_rw(fd, cmd == 'r', param[0], param[1],
+			   &prev_size, &new_size);
+		if (rc < 0)
+			return 1;
+		fprintf(stderr, "transferred %d bytes (%zu -> %zu)\n",
+			rc, prev_size, new_size);
+		return 0;
+	}
+
+ usage:
+	fprintf(stderr, "%s", usage);
+	return 1;
+}
diff --git a/example/hello.c b/example/hello.c
new file mode 100644
index 0000000..67932e0
--- /dev/null
+++ b/example/hello.c
@@ -0,0 +1,178 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * minimal example filesystem using high-level API
+ *
+ * Compile with:
+ *
+ *     gcc -Wall hello.c `pkg-config fuse3 --cflags --libs` -o hello
+ *
+ * ## Source code ##
+ * \include hello.c
+ */
+
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stddef.h>
+#include <assert.h>
+
+/*
+ * Command line options
+ *
+ * We can't set default values for the char* fields here because
+ * fuse_opt_parse would attempt to free() them when the user specifies
+ * different values on the command line.
+ */
+static struct options {
+	const char *filename;
+	const char *contents;
+	int show_help;
+} options;
+
+#define OPTION(t, p)                           \
+    { t, offsetof(struct options, p), 1 }
+static const struct fuse_opt option_spec[] = {
+	OPTION("--name=%s", filename),
+	OPTION("--contents=%s", contents),
+	OPTION("-h", show_help),
+	OPTION("--help", show_help),
+	FUSE_OPT_END
+};
+
+static void *hello_init(struct fuse_conn_info *conn,
+			struct fuse_config *cfg)
+{
+	(void) conn;
+	cfg->kernel_cache = 1;
+	return NULL;
+}
+
+static int hello_getattr(const char *path, struct stat *stbuf,
+			 struct fuse_file_info *fi)
+{
+	(void) fi;
+	int res = 0;
+
+	memset(stbuf, 0, sizeof(struct stat));
+	if (strcmp(path, "/") == 0) {
+		stbuf->st_mode = S_IFDIR | 0755;
+		stbuf->st_nlink = 2;
+	} else if (strcmp(path+1, options.filename) == 0) {
+		stbuf->st_mode = S_IFREG | 0444;
+		stbuf->st_nlink = 1;
+		stbuf->st_size = strlen(options.contents);
+	} else
+		res = -ENOENT;
+
+	return res;
+}
+
+static int hello_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+			 off_t offset, struct fuse_file_info *fi,
+			 enum fuse_readdir_flags flags)
+{
+	(void) offset;
+	(void) fi;
+	(void) flags;
+
+	if (strcmp(path, "/") != 0)
+		return -ENOENT;
+
+	filler(buf, ".", NULL, 0, 0);
+	filler(buf, "..", NULL, 0, 0);
+	filler(buf, options.filename, NULL, 0, 0);
+
+	return 0;
+}
+
+static int hello_open(const char *path, struct fuse_file_info *fi)
+{
+	if (strcmp(path+1, options.filename) != 0)
+		return -ENOENT;
+
+	if ((fi->flags & 3) != O_RDONLY)
+		return -EACCES;
+
+	return 0;
+}
+
+static int hello_read(const char *path, char *buf, size_t size, off_t offset,
+		      struct fuse_file_info *fi)
+{
+	size_t len;
+	(void) fi;
+	if(strcmp(path+1, options.filename) != 0)
+		return -ENOENT;
+
+	len = strlen(options.contents);
+	if (offset < len) {
+		if (offset + size > len)
+			size = len - offset;
+		memcpy(buf, options.contents + offset, size);
+	} else
+		size = 0;
+
+	return size;
+}
+
+static struct fuse_operations hello_oper = {
+	.init           = hello_init,
+	.getattr	= hello_getattr,
+	.readdir	= hello_readdir,
+	.open		= hello_open,
+	.read		= hello_read,
+};
+
+static void show_help(const char *progname)
+{
+	printf("usage: %s [options] <mountpoint>\n\n", progname);
+	printf("File-system specific options:\n"
+	       "    --name=<s>          Name of the \"hello\" file\n"
+	       "                        (default: \"hello\")\n"
+	       "    --contents=<s>      Contents \"hello\" file\n"
+	       "                        (default \"Hello, World!\\n\")\n"
+	       "\n");
+}
+
+int main(int argc, char *argv[])
+{
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+
+	/* Set defaults -- we have to use strdup so that
+	   fuse_opt_parse can free the defaults if other
+	   values are specified */
+	options.filename = strdup("hello");
+	options.contents = strdup("Hello World!\n");
+
+	/* Parse options */
+	if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+		return 1;
+
+	/* When --help is specified, first print our own file-system
+	   specific help text, then signal fuse_main to show
+	   additional help (by adding `--help` to the options again)
+	   without usage: line (by setting argv[0] to the empty
+	   string) */
+	if (options.show_help) {
+		show_help(argv[0]);
+		assert(fuse_opt_add_arg(&args, "--help") == 0);
+		args.argv[0] = (char*) "";
+	}
+
+	return fuse_main(args.argc, args.argv, &hello_oper, NULL);
+}
diff --git a/example/hello_ll.c b/example/hello_ll.c
new file mode 100644
index 0000000..da3a4fc
--- /dev/null
+++ b/example/hello_ll.c
@@ -0,0 +1,217 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * minimal example filesystem using low-level API
+ *
+ * Compile with:
+ *
+ *     gcc -Wall hello_ll.c `pkg-config fuse3 --cflags --libs` -o hello_ll
+ *
+ * ## Source code ##
+ * \include hello_ll.c
+ */
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <assert.h>
+
+static const char *hello_str = "Hello World!\n";
+static const char *hello_name = "hello";
+
+static int hello_stat(fuse_ino_t ino, struct stat *stbuf)
+{
+	stbuf->st_ino = ino;
+	switch (ino) {
+	case 1:
+		stbuf->st_mode = S_IFDIR | 0755;
+		stbuf->st_nlink = 2;
+		break;
+
+	case 2:
+		stbuf->st_mode = S_IFREG | 0444;
+		stbuf->st_nlink = 1;
+		stbuf->st_size = strlen(hello_str);
+		break;
+
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+static void hello_ll_getattr(fuse_req_t req, fuse_ino_t ino,
+			     struct fuse_file_info *fi)
+{
+	struct stat stbuf;
+
+	(void) fi;
+
+	memset(&stbuf, 0, sizeof(stbuf));
+	if (hello_stat(ino, &stbuf) == -1)
+		fuse_reply_err(req, ENOENT);
+	else
+		fuse_reply_attr(req, &stbuf, 1.0);
+}
+
+static void hello_ll_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+	struct fuse_entry_param e;
+
+	if (parent != 1 || strcmp(name, hello_name) != 0)
+		fuse_reply_err(req, ENOENT);
+	else {
+		memset(&e, 0, sizeof(e));
+		e.ino = 2;
+		e.attr_timeout = 1.0;
+		e.entry_timeout = 1.0;
+		hello_stat(e.ino, &e.attr);
+
+		fuse_reply_entry(req, &e);
+	}
+}
+
+struct dirbuf {
+	char *p;
+	size_t size;
+};
+
+static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+		       fuse_ino_t ino)
+{
+	struct stat stbuf;
+	size_t oldsize = b->size;
+	b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+	b->p = (char *) realloc(b->p, b->size);
+	memset(&stbuf, 0, sizeof(stbuf));
+	stbuf.st_ino = ino;
+	fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+			  b->size);
+}
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+			     off_t off, size_t maxsize)
+{
+	if (off < bufsize)
+		return fuse_reply_buf(req, buf + off,
+				      min(bufsize - off, maxsize));
+	else
+		return fuse_reply_buf(req, NULL, 0);
+}
+
+static void hello_ll_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+			     off_t off, struct fuse_file_info *fi)
+{
+	(void) fi;
+
+	if (ino != 1)
+		fuse_reply_err(req, ENOTDIR);
+	else {
+		struct dirbuf b;
+
+		memset(&b, 0, sizeof(b));
+		dirbuf_add(req, &b, ".", 1);
+		dirbuf_add(req, &b, "..", 1);
+		dirbuf_add(req, &b, hello_name, 2);
+		reply_buf_limited(req, b.p, b.size, off, size);
+		free(b.p);
+	}
+}
+
+static void hello_ll_open(fuse_req_t req, fuse_ino_t ino,
+			  struct fuse_file_info *fi)
+{
+	if (ino != 2)
+		fuse_reply_err(req, EISDIR);
+	else if ((fi->flags & 3) != O_RDONLY)
+		fuse_reply_err(req, EACCES);
+	else
+		fuse_reply_open(req, fi);
+}
+
+static void hello_ll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+			  off_t off, struct fuse_file_info *fi)
+{
+	(void) fi;
+
+	assert(ino == 2);
+	reply_buf_limited(req, hello_str, strlen(hello_str), off, size);
+}
+
+static struct fuse_lowlevel_ops hello_ll_oper = {
+	.lookup		= hello_ll_lookup,
+	.getattr	= hello_ll_getattr,
+	.readdir	= hello_ll_readdir,
+	.open		= hello_ll_open,
+	.read		= hello_ll_read,
+};
+
+int main(int argc, char *argv[])
+{
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+	struct fuse_session *se;
+	struct fuse_cmdline_opts opts;
+	int ret = -1;
+
+	if (fuse_parse_cmdline(&args, &opts) != 0)
+		return 1;
+	if (opts.show_help) {
+		printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+		fuse_cmdline_help();
+		fuse_lowlevel_help();
+		ret = 0;
+		goto err_out1;
+	} else if (opts.show_version) {
+		printf("FUSE library version %s\n", fuse_pkgversion());
+		fuse_lowlevel_version();
+		ret = 0;
+		goto err_out1;
+	}
+
+	se = fuse_session_new(&args, &hello_ll_oper,
+			      sizeof(hello_ll_oper), NULL);
+	if (se == NULL)
+	    goto err_out1;
+
+	if (fuse_set_signal_handlers(se) != 0)
+	    goto err_out2;
+
+	if (fuse_session_mount(se, opts.mountpoint) != 0)
+	    goto err_out3;
+
+	fuse_daemonize(opts.foreground);
+
+	/* Block until ctrl+c or fusermount -u */
+	if (opts.singlethread)
+		ret = fuse_session_loop(se);
+	else
+		ret = fuse_session_loop_mt(se, opts.clone_fd);
+
+	fuse_session_unmount(se);
+err_out3:
+	fuse_remove_signal_handlers(se);
+err_out2:
+	fuse_session_destroy(se);
+err_out1:
+	free(opts.mountpoint);
+	fuse_opt_free_args(&args);
+
+	return ret ? 1 : 0;
+}
diff --git a/example/ioctl.c b/example/ioctl.c
new file mode 100644
index 0000000..734765d
--- /dev/null
+++ b/example/ioctl.c
@@ -0,0 +1,232 @@
+/*
+  FUSE fioc: FUSE ioctl example
+  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 GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ * @tableofcontents
+ *
+ * This example illustrates how to write a FUSE file system that can
+ * process (a restricted set of) ioctls. It can be tested with the
+ * ioctl_client.c program.
+ *
+ * Compile with:
+ *
+ *     gcc -Wall ioctl.c `pkg-config fuse3 --cflags --libs` -o ioctl
+ *
+ * ## Source code ##
+ * \include ioctl.c
+ */
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <fuse.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <time.h>
+#include <errno.h>
+
+#include "ioctl.h"
+
+#define FIOC_NAME	"fioc"
+
+enum {
+	FIOC_NONE,
+	FIOC_ROOT,
+	FIOC_FILE,
+};
+
+static void *fioc_buf;
+static size_t fioc_size;
+
+static int fioc_resize(size_t new_size)
+{
+	void *new_buf;
+
+	if (new_size == fioc_size)
+		return 0;
+
+	new_buf = realloc(fioc_buf, new_size);
+	if (!new_buf && new_size)
+		return -ENOMEM;
+
+	if (new_size > fioc_size)
+		memset(new_buf + fioc_size, 0, new_size - fioc_size);
+
+	fioc_buf = new_buf;
+	fioc_size = new_size;
+
+	return 0;
+}
+
+static int fioc_expand(size_t new_size)
+{
+	if (new_size > fioc_size)
+		return fioc_resize(new_size);
+	return 0;
+}
+
+static int fioc_file_type(const char *path)
+{
+	if (strcmp(path, "/") == 0)
+		return FIOC_ROOT;
+	if (strcmp(path, "/" FIOC_NAME) == 0)
+		return FIOC_FILE;
+	return FIOC_NONE;
+}
+
+static int fioc_getattr(const char *path, struct stat *stbuf,
+			struct fuse_file_info *fi)
+{
+	(void) fi;
+	stbuf->st_uid = getuid();
+	stbuf->st_gid = getgid();
+	stbuf->st_atime = stbuf->st_mtime = time(NULL);
+
+	switch (fioc_file_type(path)) {
+	case FIOC_ROOT:
+		stbuf->st_mode = S_IFDIR | 0755;
+		stbuf->st_nlink = 2;
+		break;
+	case FIOC_FILE:
+		stbuf->st_mode = S_IFREG | 0644;
+		stbuf->st_nlink = 1;
+		stbuf->st_size = fioc_size;
+		break;
+	case FIOC_NONE:
+		return -ENOENT;
+	}
+
+	return 0;
+}
+
+static int fioc_open(const char *path, struct fuse_file_info *fi)
+{
+	(void) fi;
+
+	if (fioc_file_type(path) != FIOC_NONE)
+		return 0;
+	return -ENOENT;
+}
+
+static int fioc_do_read(char *buf, size_t size, off_t offset)
+{
+	if (offset >= fioc_size)
+		return 0;
+
+	if (size > fioc_size - offset)
+		size = fioc_size - offset;
+
+	memcpy(buf, fioc_buf + offset, size);
+
+	return size;
+}
+
+static int fioc_read(const char *path, char *buf, size_t size,
+		     off_t offset, struct fuse_file_info *fi)
+{
+	(void) fi;
+
+	if (fioc_file_type(path) != FIOC_FILE)
+		return -EINVAL;
+
+	return fioc_do_read(buf, size, offset);
+}
+
+static int fioc_do_write(const char *buf, size_t size, off_t offset)
+{
+	if (fioc_expand(offset + size))
+		return -ENOMEM;
+
+	memcpy(fioc_buf + offset, buf, size);
+
+	return size;
+}
+
+static int fioc_write(const char *path, const char *buf, size_t size,
+		      off_t offset, struct fuse_file_info *fi)
+{
+	(void) fi;
+
+	if (fioc_file_type(path) != FIOC_FILE)
+		return -EINVAL;
+
+	return fioc_do_write(buf, size, offset);
+}
+
+static int fioc_truncate(const char *path, off_t size,
+			 struct fuse_file_info *fi)
+{
+	(void) fi;
+	if (fioc_file_type(path) != FIOC_FILE)
+		return -EINVAL;
+
+	return fioc_resize(size);
+}
+
+static int fioc_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+			off_t offset, struct fuse_file_info *fi,
+			enum fuse_readdir_flags flags)
+{
+	(void) fi;
+	(void) offset;
+	(void) flags;
+
+	if (fioc_file_type(path) != FIOC_ROOT)
+		return -ENOENT;
+
+	filler(buf, ".", NULL, 0, 0);
+	filler(buf, "..", NULL, 0, 0);
+	filler(buf, FIOC_NAME, NULL, 0, 0);
+
+	return 0;
+}
+
+static int fioc_ioctl(const char *path, int cmd, void *arg,
+		      struct fuse_file_info *fi, unsigned int flags, void *data)
+{
+	(void) arg;
+	(void) fi;
+	(void) flags;
+
+	if (fioc_file_type(path) != FIOC_FILE)
+		return -EINVAL;
+
+	if (flags & FUSE_IOCTL_COMPAT)
+		return -ENOSYS;
+
+	switch (cmd) {
+	case FIOC_GET_SIZE:
+		*(size_t *)data = fioc_size;
+		return 0;
+
+	case FIOC_SET_SIZE:
+		fioc_resize(*(size_t *)data);
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static struct fuse_operations fioc_oper = {
+	.getattr	= fioc_getattr,
+	.readdir	= fioc_readdir,
+	.truncate	= fioc_truncate,
+	.open		= fioc_open,
+	.read		= fioc_read,
+	.write		= fioc_write,
+	.ioctl		= fioc_ioctl,
+};
+
+int main(int argc, char *argv[])
+{
+	return fuse_main(argc, argv, &fioc_oper, NULL);
+}
diff --git a/example/ioctl.h b/example/ioctl.h
new file mode 100644
index 0000000..ded2a15
--- /dev/null
+++ b/example/ioctl.h
@@ -0,0 +1,42 @@
+/*
+  FUSE-ioctl: ioctl support for FUSE
+  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 GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ * @tableofcontents
+ *
+ * Header file to share definitions between the ioctl.c example file
+ * system and the ioctl_client.c test program.
+ *
+ * \include ioctl.h
+ */
+
+
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/ioctl.h>
+
+enum {
+	FIOC_GET_SIZE	= _IOR('E', 0, size_t),
+	FIOC_SET_SIZE	= _IOW('E', 1, size_t),
+
+	/*
+	 * The following two ioctls don't follow usual encoding rules
+	 * and transfer variable amount of data.
+	 */
+	FIOC_READ	= _IO('E', 2),
+	FIOC_WRITE	= _IO('E', 3),
+};
+
+struct fioc_rw_arg {
+	off_t		offset;
+	void		*buf;
+	size_t		size;
+	size_t		prev_size;	/* out param for previous total size */
+	size_t		new_size;	/* out param for new total size */
+};
diff --git a/example/ioctl_client.c b/example/ioctl_client.c
new file mode 100644
index 0000000..a58a897
--- /dev/null
+++ b/example/ioctl_client.c
@@ -0,0 +1,72 @@
+/*
+  FUSE fioclient: FUSE ioctl example client
+  Copyright (C) 2008       SUSE Linux Products GmbH
+  Copyright (C) 2008       Tejun Heo <teheo@suse.de>
+
+  This program tests the ioctl.c example file systsem.
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This program tests the ioctl.c example file systsem.
+ *
+ * Compile with:
+ *
+ *     gcc -Wall ioctl_client.c -o ioctl_client
+ *
+ * ## Source code ##
+ * \include ioctl_client.c
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/fcntl.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "ioctl.h"
+
+const char *usage =
+"Usage: fioclient FIOC_FILE [size]\n"
+"\n"
+"Get size if <size> is omitted, set size otherwise\n"
+"\n";
+
+int main(int argc, char **argv)
+{
+	size_t size;
+	int fd;
+
+	if (argc < 2) {
+		fprintf(stderr, "%s", usage);
+		return 1;
+	}
+
+	fd = open(argv[1], O_RDWR);
+	if (fd < 0) {
+		perror("open");
+		return 1;
+	}
+
+	if (argc == 2) {
+		if (ioctl(fd, FIOC_GET_SIZE, &size)) {
+			perror("ioctl");
+			return 1;
+		}
+		printf("%zu\n", size);
+	} else {
+		size = strtoul(argv[2], NULL, 0);
+		if (ioctl(fd, FIOC_SET_SIZE, &size)) {
+			perror("ioctl");
+			return 1;
+		}
+	}
+	return 0;
+}
diff --git a/example/notify_inval_entry.c b/example/notify_inval_entry.c
new file mode 100644
index 0000000..30192d1
--- /dev/null
+++ b/example/notify_inval_entry.c
@@ -0,0 +1,348 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This example implements a file system with a single file whose
+ * file name changes dynamically to reflect the current time.
+ *
+ * It illustrates the use of the fuse_lowlevel_notify_inval_entry()
+ * function.
+ *
+ * To see the effect, first start the file system with the
+ * ``--no-notify``
+ *
+ *     $ notify_inval_entry --update-interval=1 --timeout 30 --no-notify mnt/
+ *
+ * Observe that `ls` always prints the correct directory contents
+ * (since `readdir` output is not cached)::
+ *
+ *     $ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
+ *     Time_is_15h_48m_33s  current_time
+ *     Time_is_15h_48m_34s  current_time
+ *     Time_is_15h_48m_35s  current_time
+ *
+ * However, if you try to access a file by name the kernel will
+ * report that it still exists:
+ *
+ *     $ file=$(ls mnt/); echo $file
+ *     Time_is_15h_50m_09s
+ *     $ sleep 5; stat mnt/$file
+ *       File: ‘mnt/Time_is_15h_50m_09s’
+ *       Size: 32                Blocks: 0          IO Block: 4096   regular file
+ *     Device: 2ah/42d	Inode: 3           Links: 1
+ *     Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
+ *     Access: 1969-12-31 16:00:00.000000000 -0800
+ *     Modify: 1969-12-31 16:00:00.000000000 -0800
+ *     Change: 1969-12-31 16:00:00.000000000 -0800
+ *      Birth: -
+ *
+ * Only once the kernel cache timeout has been reached will the stat
+ * call fail:
+ *
+ *     $ sleep 30; stat mnt/$file
+ *     stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
+ *
+ * In contrast, if you enable notifications you will be unable to stat
+ * the file as soon as the file system updates its name:
+ *
+ *     $ notify_inval_entry --update-interval=1 --timeout 30 --no-notify mnt/
+ *     $ file=$(ls mnt/); stat mnt/$file
+ *       File: ‘mnt/Time_is_20h_42m_11s’
+ *       Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
+ *     Device: 2ah/42d	Inode: 2           Links: 1
+ *     Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
+ *     Access: 1969-12-31 16:00:00.000000000 -0800
+ *     Modify: 1969-12-31 16:00:00.000000000 -0800
+ *     Change: 1969-12-31 16:00:00.000000000 -0800
+ *      Birth: -
+ *     $ sleep 1; stat mnt/$file
+ *     stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
+ *
+ * ## Compilation ##
+ *
+ *     gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
+ *
+ * ## Source code ##
+ * \include notify_inval_entry.c
+ */
+
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#define MAX_STR_LEN 128
+static char file_name[MAX_STR_LEN];
+static fuse_ino_t file_ino = 2;
+static int lookup_cnt = 0;
+
+/* Command line parsing */
+struct options {
+    int no_notify;
+    float timeout;
+    int update_interval;
+};
+static struct options options = {
+    .timeout = 5,
+    .no_notify = 0,
+    .update_interval = 1,
+};
+
+#define OPTION(t, p)                           \
+    { t, offsetof(struct options, p), 1 }
+static const struct fuse_opt option_spec[] = {
+    OPTION("--no-notify", no_notify),
+    OPTION("--update-interval=%d", update_interval),
+    OPTION("--timeout=%f", timeout),
+    FUSE_OPT_END
+};
+
+static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+    stbuf->st_ino = ino;
+    if (ino == FUSE_ROOT_ID) {
+        stbuf->st_mode = S_IFDIR | 0755;
+        stbuf->st_nlink = 1;
+    }
+
+    else if (ino == file_ino) {
+        stbuf->st_mode = S_IFREG | 0000;
+        stbuf->st_nlink = 1;
+        stbuf->st_size = 0;
+    }
+
+    else
+        return -1;
+
+    return 0;
+}
+
+static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+                       const char *name) {
+    struct fuse_entry_param e;
+    memset(&e, 0, sizeof(e));
+
+    if (parent != FUSE_ROOT_ID)
+        goto err_out;
+    else if (strcmp(name, file_name) == 0) {
+        e.ino = file_ino;
+        lookup_cnt++;
+    } else
+        goto err_out;
+
+    e.attr_timeout = options.timeout;
+    e.entry_timeout = options.timeout;
+    if (tfs_stat(e.ino, &e.attr) != 0)
+        goto err_out;
+    fuse_reply_entry(req, &e);
+    return;
+
+err_out:
+    fuse_reply_err(req, ENOENT);
+}
+
+static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+                        uint64_t nlookup) {
+    (void) req;
+    if(ino == file_ino)
+        lookup_cnt -= nlookup;
+    else
+        assert(ino == FUSE_ROOT_ID);
+    fuse_reply_none(req);
+}
+
+static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+                        struct fuse_file_info *fi) {
+    struct stat stbuf;
+
+    (void) fi;
+
+    memset(&stbuf, 0, sizeof(stbuf));
+    if (tfs_stat(ino, &stbuf) != 0)
+        fuse_reply_err(req, ENOENT);
+    else
+        fuse_reply_attr(req, &stbuf, options.timeout);
+}
+
+struct dirbuf {
+    char *p;
+    size_t size;
+};
+
+static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+                       fuse_ino_t ino) {
+    struct stat stbuf;
+    size_t oldsize = b->size;
+    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+    b->p = (char *) realloc(b->p, b->size);
+    memset(&stbuf, 0, sizeof(stbuf));
+    stbuf.st_ino = ino;
+    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+                      b->size);
+}
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+                             off_t off, size_t maxsize) {
+    if (off < bufsize)
+        return fuse_reply_buf(req, buf + off,
+                              min(bufsize - off, maxsize));
+    else
+        return fuse_reply_buf(req, NULL, 0);
+}
+
+static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+                        off_t off, struct fuse_file_info *fi) {
+    (void) fi;
+
+    if (ino != FUSE_ROOT_ID)
+        fuse_reply_err(req, ENOTDIR);
+    else {
+        struct dirbuf b;
+
+        memset(&b, 0, sizeof(b));
+        dirbuf_add(req, &b, file_name, file_ino);
+        reply_buf_limited(req, b.p, b.size, off, size);
+        free(b.p);
+    }
+}
+
+static struct fuse_lowlevel_ops tfs_oper = {
+    .lookup	= tfs_lookup,
+    .getattr	= tfs_getattr,
+    .readdir	= tfs_readdir,
+    .forget     = tfs_forget,
+};
+
+static void update_fs(void) {
+    time_t t;
+    struct tm *now;
+    ssize_t ret;
+
+    t = time(NULL);
+    now = localtime(&t);
+    assert(now != NULL);
+
+    ret = strftime(file_name, MAX_STR_LEN,
+                   "Time_is_%Hh_%Mm_%Ss", now);
+    assert(ret != 0);
+}
+
+static void* update_fs_loop(void *data) {
+    struct fuse_session *se = (struct fuse_session*) data;
+    char *old_name;
+
+    while(1) {
+        old_name = strdup(file_name);
+        update_fs();
+        if (!options.no_notify && lookup_cnt)
+            assert(fuse_lowlevel_notify_inval_entry
+                   (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
+        free(old_name);
+        sleep(options.update_interval);
+    }
+    return NULL;
+}
+
+static void show_help(const char *progname)
+{
+    printf("usage: %s [options] <mountpoint>\n\n", progname);
+    printf("File-system specific options:\n"
+               "    --timeout=<secs>       Timeout for kernel caches\n"
+               "    --update-interval=<secs>  Update-rate of file system contents\n"
+               "    --no-notify            Disable kernel notifications\n"
+               "\n");
+}
+
+int main(int argc, char *argv[]) {
+    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+    struct fuse_session *se;
+    struct fuse_cmdline_opts opts;
+    pthread_t updater;
+    int ret = -1;
+
+    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+        return 1;
+
+    if (fuse_parse_cmdline(&args, &opts) != 0)
+        return 1;
+    if (opts.show_help) {
+        show_help(argv[0]);
+        fuse_cmdline_help();
+        fuse_lowlevel_help();
+        ret = 0;
+        goto err_out1;
+    } else if (opts.show_version) {
+        printf("FUSE library version %s\n", fuse_pkgversion());
+        fuse_lowlevel_version();
+        ret = 0;
+        goto err_out1;
+    }
+
+    /* Initial contents */
+    update_fs();
+
+    se = fuse_session_new(&args, &tfs_oper,
+                          sizeof(tfs_oper), NULL);
+    if (se == NULL)
+        goto err_out1;
+
+    if (fuse_set_signal_handlers(se) != 0)
+        goto err_out2;
+
+    if (fuse_session_mount(se, opts.mountpoint) != 0)
+        goto err_out3;
+
+    fuse_daemonize(opts.foreground);
+
+    /* Start thread to update file contents */
+    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+    if (ret != 0) {
+        fprintf(stderr, "pthread_create failed with %s\n",
+                strerror(ret));
+        goto err_out3;
+    }
+
+    /* Block until ctrl+c or fusermount -u */
+    if (opts.singlethread)
+        ret = fuse_session_loop(se);
+    else
+        ret = fuse_session_loop_mt(se, opts.clone_fd);
+
+    fuse_session_unmount(se);
+err_out3:
+    fuse_remove_signal_handlers(se);
+err_out2:
+    fuse_session_destroy(se);
+err_out1:
+    free(opts.mountpoint);
+    fuse_opt_free_args(&args);
+
+    return ret ? 1 : 0;
+}
+
+
+/**
+ * Local Variables:
+ * mode: c
+ * indent-tabs-mode: nil
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/example/notify_inval_inode.c b/example/notify_inval_inode.c
new file mode 100644
index 0000000..9bb9316
--- /dev/null
+++ b/example/notify_inval_inode.c
@@ -0,0 +1,371 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This example implements a file system with a single file whose
+ * contents change dynamically: it always contains the current time.
+ *
+ * While notify_store_retrieve.c uses fuse_lowlevel_notify_store() to
+ * actively push the updated data into the kernel cache, this example
+ * uses fuse_lowlevel_notify_inval_inode() to notify the kernel that
+ * the cache has to be invalidated - but the kernel still has to
+ * explicitly request the updated data on the next read.
+ *
+ * To see the effect, first start the file system with the
+ *  ``--no-notify`` option:
+ *
+ *     $ notify_inval_inode --update-interval=1 --no-notify mnt/
+ *
+ * Observe that the output never changes, even though the file system
+ * updates it once per second. This is because the contents are cached
+ * in the kernel:
+ *
+ *     $ for i in 1 2 3 4 5; do
+ *     >     cat mnt/current_time
+ *     >     sleep 1
+ *     > done
+ *     The current time is 15:58:18
+ *     The current time is 15:58:18
+ *     The current time is 15:58:18
+ *     The current time is 15:58:18
+ *     The current time is 15:58:18
+ *
+ * If you instead enable the notification functions, the changes become
+ * visible:
+ *
+ *      $ notify_inval_inode --update-interval=1 mnt/
+ *      $ for i in 1 2 3 4 5; do
+ *      >     cat mnt/current_time
+ *      >     sleep 1
+ *      > done
+ *      The current time is 15:58:40
+ *      The current time is 15:58:41
+ *      The current time is 15:58:42
+ *      The current time is 15:58:43
+ *      The current time is 15:58:44
+ *
+ * ## Compilation ##
+ *
+ *     gcc -Wall notify_inval_inode.c `pkg-config fuse3 --cflags --libs` -o notify_inval_inode
+ *
+ * ## Source code ##
+ * \include notify_inval_inode.c
+ */
+
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+/* We can't actually tell the kernel that there is no
+   timeout, so we just send a big value */
+#define NO_TIMEOUT 500000
+
+/* We cannot check directly if e.g. O_RDONLY is set, since this is not
+ * an individual bit (cf. open(2)) */
+#define ACCESS_MASK (O_RDONLY | O_WRONLY | O_RDWR)
+
+#define MAX_STR_LEN 128
+#define FILE_INO 2
+#define FILE_NAME "current_time"
+static char file_contents[MAX_STR_LEN];
+static int lookup_cnt = 0;
+static size_t file_size;
+
+/* Command line parsing */
+struct options {
+    int no_notify;
+    int update_interval;
+};
+static struct options options = {
+    .no_notify = 0,
+    .update_interval = 1,
+};
+
+#define OPTION(t, p)                           \
+    { t, offsetof(struct options, p), 1 }
+static const struct fuse_opt option_spec[] = {
+    OPTION("--no-notify", no_notify),
+    OPTION("--update-interval=%d", update_interval),
+    FUSE_OPT_END
+};
+
+static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+    stbuf->st_ino = ino;
+    if (ino == FUSE_ROOT_ID) {
+        stbuf->st_mode = S_IFDIR | 0755;
+        stbuf->st_nlink = 1;
+    }
+
+    else if (ino == FILE_INO) {
+        stbuf->st_mode = S_IFREG | 0444;
+        stbuf->st_nlink = 1;
+        stbuf->st_size = file_size;
+    }
+
+    else
+        return -1;
+
+    return 0;
+}
+
+static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+                       const char *name) {
+    struct fuse_entry_param e;
+    memset(&e, 0, sizeof(e));
+
+    if (parent != FUSE_ROOT_ID)
+        goto err_out;
+    else if (strcmp(name, FILE_NAME) == 0) {
+        e.ino = FILE_INO;
+        lookup_cnt++;
+    } else
+        goto err_out;
+
+    e.attr_timeout = NO_TIMEOUT;
+    e.entry_timeout = NO_TIMEOUT;
+    if (tfs_stat(e.ino, &e.attr) != 0)
+        goto err_out;
+    fuse_reply_entry(req, &e);
+    return;
+
+err_out:
+    fuse_reply_err(req, ENOENT);
+}
+
+static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+                        uint64_t nlookup) {
+    (void) req;
+    if(ino == FILE_INO)
+        lookup_cnt -= nlookup;
+    else
+        assert(ino == FUSE_ROOT_ID);
+    fuse_reply_none(req);
+}
+
+static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+                        struct fuse_file_info *fi) {
+    struct stat stbuf;
+
+    (void) fi;
+
+    memset(&stbuf, 0, sizeof(stbuf));
+    if (tfs_stat(ino, &stbuf) != 0)
+        fuse_reply_err(req, ENOENT);
+    else
+        fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+}
+
+struct dirbuf {
+    char *p;
+    size_t size;
+};
+
+static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+                       fuse_ino_t ino) {
+    struct stat stbuf;
+    size_t oldsize = b->size;
+    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+    b->p = (char *) realloc(b->p, b->size);
+    memset(&stbuf, 0, sizeof(stbuf));
+    stbuf.st_ino = ino;
+    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+                      b->size);
+}
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+                             off_t off, size_t maxsize) {
+    if (off < bufsize)
+        return fuse_reply_buf(req, buf + off,
+                              min(bufsize - off, maxsize));
+    else
+        return fuse_reply_buf(req, NULL, 0);
+}
+
+static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+                        off_t off, struct fuse_file_info *fi) {
+    (void) fi;
+
+    if (ino != FUSE_ROOT_ID)
+        fuse_reply_err(req, ENOTDIR);
+    else {
+        struct dirbuf b;
+
+        memset(&b, 0, sizeof(b));
+        dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+        reply_buf_limited(req, b.p, b.size, off, size);
+        free(b.p);
+    }
+}
+
+static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+                     struct fuse_file_info *fi) {
+
+    /* Make cache persistent even if file is closed,
+       this makes it easier to see the effects */
+    fi->keep_cache = 1;
+
+    if (ino == FUSE_ROOT_ID)
+        fuse_reply_err(req, EISDIR);
+    else if ((fi->flags & ACCESS_MASK) != O_RDONLY)
+        fuse_reply_err(req, EACCES);
+    else if (ino == FILE_INO)
+        fuse_reply_open(req, fi);
+    else {
+        // This should not happen
+        fprintf(stderr, "Got open for non-existing inode!\n");
+        fuse_reply_err(req, ENOENT);
+    }
+}
+
+static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+                     off_t off, struct fuse_file_info *fi) {
+    (void) fi;
+
+    assert(ino == FILE_INO);
+    reply_buf_limited(req, file_contents, file_size, off, size);
+}
+
+static struct fuse_lowlevel_ops tfs_oper = {
+    .lookup	= tfs_lookup,
+    .getattr	= tfs_getattr,
+    .readdir	= tfs_readdir,
+    .open	= tfs_open,
+    .read	= tfs_read,
+    .forget     = tfs_forget,
+};
+
+static void update_fs(void) {
+    struct tm *now;
+    time_t t;
+    t = time(NULL);
+    now = localtime(&t);
+    assert(now != NULL);
+
+    file_size = strftime(file_contents, MAX_STR_LEN,
+                         "The current time is %H:%M:%S\n", now);
+    assert(file_size != 0);
+}
+
+static void* update_fs_loop(void *data) {
+    struct fuse_session *se = (struct fuse_session*) data;
+
+    while(1) {
+        update_fs();
+        if (!options.no_notify && lookup_cnt) {
+            /* Only send notification if the kernel
+               is aware of the inode */
+            assert(fuse_lowlevel_notify_inval_inode
+                   (se, FILE_INO, 0, 0) == 0);
+        }
+        sleep(options.update_interval);
+    }
+    return NULL;
+}
+
+static void show_help(const char *progname)
+{
+    printf("usage: %s [options] <mountpoint>\n\n", progname);
+    printf("File-system specific options:\n"
+               "    --update-interval=<secs>  Update-rate of file system contents\n"
+               "    --no-notify            Disable kernel notifications\n"
+               "\n");
+}
+
+int main(int argc, char *argv[]) {
+    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+    struct fuse_session *se;
+    struct fuse_cmdline_opts opts;
+    pthread_t updater;
+    int ret = -1;
+
+    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+        return 1;
+
+    if (fuse_parse_cmdline(&args, &opts) != 0) {
+        ret = 1;
+        goto err_out1;
+    }
+
+    if (opts.show_help) {
+        show_help(argv[0]);
+        fuse_cmdline_help();
+        fuse_lowlevel_help();
+        ret = 0;
+        goto err_out1;
+    } else if (opts.show_version) {
+        printf("FUSE library version %s\n", fuse_pkgversion());
+        fuse_lowlevel_version();
+        ret = 0;
+        goto err_out1;
+    }
+
+    /* Initial contents */
+    update_fs();
+
+    se = fuse_session_new(&args, &tfs_oper,
+                          sizeof(tfs_oper), NULL);
+    if (se == NULL)
+        goto err_out1;
+
+    if (fuse_set_signal_handlers(se) != 0)
+        goto err_out2;
+
+    if (fuse_session_mount(se, opts.mountpoint) != 0)
+        goto err_out3;
+
+    fuse_daemonize(opts.foreground);
+
+    /* Start thread to update file contents */
+    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+    if (ret != 0) {
+        fprintf(stderr, "pthread_create failed with %s\n",
+                strerror(ret));
+        goto err_out3;
+    }
+
+    /* Block until ctrl+c or fusermount -u */
+    if (opts.singlethread)
+        ret = fuse_session_loop(se);
+    else
+        ret = fuse_session_loop_mt(se, opts.clone_fd);
+
+    fuse_session_unmount(se);
+err_out3:
+    fuse_remove_signal_handlers(se);
+err_out2:
+    fuse_session_destroy(se);
+err_out1:
+    fuse_opt_free_args(&args);
+    free(opts.mountpoint);
+
+    return ret ? 1 : 0;
+}
+
+
+/**
+ * Local Variables:
+ * mode: c
+ * indent-tabs-mode: nil
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/example/notify_store_retrieve.c b/example/notify_store_retrieve.c
new file mode 100644
index 0000000..440a7f8
--- /dev/null
+++ b/example/notify_store_retrieve.c
@@ -0,0 +1,415 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ *  This example implements a file system with a single file whose
+ *  contents change dynamically: it always contains the current time.
+ *
+ *  While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode()
+ *  to let the kernel know that it has to invalidate the cache, this
+ *  example actively pushes the updated data into the kernel cache
+ *  using fuse_lowlevel_notify_store().
+ *
+ *  To see the effect, first start the file system with the
+ *  ``--no-notify`` option:
+ *
+ *      $ notify_store_retrieve --update-interval=1 --no-notify mnt/
+ *
+ *  Observe that the output never changes, even though the file system
+ *  updates it once per second. This is because the contents are cached
+ *  in the kernel:
+ *
+ *      $ for i in 1 2 3 4 5; do
+ *      >     cat mnt/current_time
+ *      >     sleep 1
+ *      > done
+ *      The current time is 15:58:18
+ *      The current time is 15:58:18
+ *      The current time is 15:58:18
+ *      The current time is 15:58:18
+ *      The current time is 15:58:18
+ *
+ *  If you instead enable the notification functions, the changes become
+ *  visible:
+ *
+ *      $ notify_store_retrieve --update-interval=1 mnt/
+ *      $ for i in 1 2 3 4 5; do
+ *      >     cat mnt/current_time
+ *      >     sleep 1
+ *      > done
+ *      The current time is 15:58:40
+ *      The current time is 15:58:41
+ *      The current time is 15:58:42
+ *      The current time is 15:58:43
+ *      The current time is 15:58:44
+ *
+ * ## Compilation ##
+ *
+ *     gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
+ *
+ * ## Source code ##
+ * \include notify_store_retrieve.c
+ */
+
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+
+/* We can't actually tell the kernel that there is no
+   timeout, so we just send a big value */
+#define NO_TIMEOUT 500000
+
+/* We cannot check directly if e.g. O_RDONLY is set, since this is not
+ * an individual bit (cf. open(2)) */
+#define ACCESS_MASK (O_RDONLY | O_WRONLY | O_RDWR)
+
+#define MAX_STR_LEN 128
+#define FILE_INO 2
+#define FILE_NAME "current_time"
+static char file_contents[MAX_STR_LEN];
+static int lookup_cnt = 0;
+static size_t file_size;
+
+/* Keep track if we ever stored data (==1), and
+   received it back correctly (==2) */
+static int retrieve_status = 0;
+
+/* Command line parsing */
+struct options {
+    int no_notify;
+    int update_interval;
+};
+static struct options options = {
+    .no_notify = 0,
+    .update_interval = 1,
+};
+
+#define OPTION(t, p)                           \
+    { t, offsetof(struct options, p), 1 }
+static const struct fuse_opt option_spec[] = {
+    OPTION("--no-notify", no_notify),
+    OPTION("--update-interval=%d", update_interval),
+    FUSE_OPT_END
+};
+
+static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+    stbuf->st_ino = ino;
+    if (ino == FUSE_ROOT_ID) {
+        stbuf->st_mode = S_IFDIR | 0755;
+        stbuf->st_nlink = 1;
+    }
+
+    else if (ino == FILE_INO) {
+        stbuf->st_mode = S_IFREG | 0444;
+        stbuf->st_nlink = 1;
+        stbuf->st_size = file_size;
+    }
+
+    else
+        return -1;
+
+    return 0;
+}
+
+static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+                       const char *name) {
+    struct fuse_entry_param e;
+    memset(&e, 0, sizeof(e));
+
+    if (parent != FUSE_ROOT_ID)
+        goto err_out;
+    else if (strcmp(name, FILE_NAME) == 0) {
+        e.ino = FILE_INO;
+        lookup_cnt++;
+    } else
+        goto err_out;
+
+    e.attr_timeout = NO_TIMEOUT;
+    e.entry_timeout = NO_TIMEOUT;
+    if (tfs_stat(e.ino, &e.attr) != 0)
+        goto err_out;
+    fuse_reply_entry(req, &e);
+    return;
+
+err_out:
+    fuse_reply_err(req, ENOENT);
+}
+
+static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
+                        uint64_t nlookup) {
+    (void) req;
+    if(ino == FILE_INO)
+        lookup_cnt -= nlookup;
+    else
+        assert(ino == FUSE_ROOT_ID);
+    fuse_reply_none(req);
+}
+
+static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+                        struct fuse_file_info *fi) {
+    struct stat stbuf;
+
+    (void) fi;
+
+    memset(&stbuf, 0, sizeof(stbuf));
+    if (tfs_stat(ino, &stbuf) != 0)
+        fuse_reply_err(req, ENOENT);
+    else
+        fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
+}
+
+struct dirbuf {
+    char *p;
+    size_t size;
+};
+
+static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
+                       fuse_ino_t ino) {
+    struct stat stbuf;
+    size_t oldsize = b->size;
+    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
+    b->p = (char *) realloc(b->p, b->size);
+    memset(&stbuf, 0, sizeof(stbuf));
+    stbuf.st_ino = ino;
+    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
+                      b->size);
+}
+
+#define min(x, y) ((x) < (y) ? (x) : (y))
+
+static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
+                             off_t off, size_t maxsize) {
+    if (off < bufsize)
+        return fuse_reply_buf(req, buf + off,
+                              min(bufsize - off, maxsize));
+    else
+        return fuse_reply_buf(req, NULL, 0);
+}
+
+static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+                        off_t off, struct fuse_file_info *fi) {
+    (void) fi;
+
+    if (ino != FUSE_ROOT_ID)
+        fuse_reply_err(req, ENOTDIR);
+    else {
+        struct dirbuf b;
+
+        memset(&b, 0, sizeof(b));
+        dirbuf_add(req, &b, FILE_NAME, FILE_INO);
+        reply_buf_limited(req, b.p, b.size, off, size);
+        free(b.p);
+    }
+}
+
+static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+                     struct fuse_file_info *fi) {
+
+    /* Make cache persistent even if file is closed,
+       this makes it easier to see the effects */
+    fi->keep_cache = 1;
+
+    if (ino == FUSE_ROOT_ID)
+        fuse_reply_err(req, EISDIR);
+    else if ((fi->flags & ACCESS_MASK) != O_RDONLY)
+        fuse_reply_err(req, EACCES);
+    else if (ino == FILE_INO)
+        fuse_reply_open(req, fi);
+    else {
+        // This should not happen
+        fprintf(stderr, "Got open for non-existing inode!\n");
+        fuse_reply_err(req, ENOENT);
+    }
+}
+
+static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+                     off_t off, struct fuse_file_info *fi) {
+    (void) fi;
+
+    assert(ino == FILE_INO);
+    reply_buf_limited(req, file_contents, file_size, off, size);
+}
+
+static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
+                               off_t offset, struct fuse_bufvec *data) {
+    struct fuse_bufvec bufv;
+    char buf[MAX_STR_LEN];
+    char *expected;
+    ssize_t ret;
+
+    assert(ino == FILE_INO);
+    assert(offset == 0);
+    expected = (char*) cookie;
+
+    bufv.count = 1;
+    bufv.idx = 0;
+    bufv.off = 0;
+    bufv.buf[0].size = MAX_STR_LEN;
+    bufv.buf[0].mem = buf;
+    bufv.buf[0].flags = 0;
+
+    ret = fuse_buf_copy(&bufv, data, 0);
+    assert(ret > 0);
+    assert(strncmp(buf, expected, ret) == 0);
+    free(expected);
+    retrieve_status = 2;
+    fuse_reply_none(req);
+}
+
+
+static struct fuse_lowlevel_ops tfs_oper = {
+    .lookup	= tfs_lookup,
+    .getattr	= tfs_getattr,
+    .readdir	= tfs_readdir,
+    .open	= tfs_open,
+    .read	= tfs_read,
+    .forget     = tfs_forget,
+    .retrieve_reply = tfs_retrieve_reply,
+};
+
+static void update_fs(void) {
+    struct tm *now;
+    time_t t;
+    t = time(NULL);
+    now = localtime(&t);
+    assert(now != NULL);
+
+    file_size = strftime(file_contents, MAX_STR_LEN,
+                         "The current time is %H:%M:%S\n", now);
+    assert(file_size != 0);
+}
+
+static void* update_fs_loop(void *data) {
+    struct fuse_session *se = (struct fuse_session*) data;
+    struct fuse_bufvec bufv;
+
+    while(1) {
+        update_fs();
+        if (!options.no_notify && lookup_cnt) {
+            /* Only send notification if the kernel
+               is aware of the inode */
+            bufv.count = 1;
+            bufv.idx = 0;
+            bufv.off = 0;
+            bufv.buf[0].size = file_size;
+            bufv.buf[0].mem = file_contents;
+            bufv.buf[0].flags = 0;
+            assert(fuse_lowlevel_notify_store(se, FILE_INO, 0,
+                                              &bufv, 0) == 0);
+
+            /* To make sure that everything worked correctly, ask the
+               kernel to send us back the stored data */
+            assert(fuse_lowlevel_notify_retrieve
+                   (se, FILE_INO, MAX_STR_LEN, 0,
+                    (void*) strdup(file_contents)) == 0);
+            if(retrieve_status == 0)
+                retrieve_status = 1;
+        }
+        sleep(options.update_interval);
+    }
+    return NULL;
+}
+
+static void show_help(const char *progname)
+{
+    printf("usage: %s [options] <mountpoint>\n\n", progname);
+    printf("File-system specific options:\n"
+               "    --update-interval=<secs>  Update-rate of file system contents\n"
+               "    --no-notify            Disable kernel notifications\n"
+               "\n");
+}
+
+int main(int argc, char *argv[]) {
+    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+    struct fuse_session *se;
+    struct fuse_cmdline_opts opts;
+    pthread_t updater;
+    int ret = -1;
+
+    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
+        return 1;
+
+    if (fuse_parse_cmdline(&args, &opts) != 0)
+        return 1;
+    if (opts.show_help) {
+        show_help(argv[0]);
+        fuse_cmdline_help();
+        fuse_lowlevel_help();
+        ret = 0;
+        goto err_out1;
+    } else if (opts.show_version) {
+        printf("FUSE library version %s\n", fuse_pkgversion());
+        fuse_lowlevel_version();
+        ret = 0;
+        goto err_out1;
+    }
+
+    /* Initial contents */
+    update_fs();
+
+    se = fuse_session_new(&args, &tfs_oper,
+                          sizeof(tfs_oper), NULL);
+    if (se == NULL)
+        goto err_out1;
+
+    if (fuse_set_signal_handlers(se) != 0)
+        goto err_out2;
+
+    if (fuse_session_mount(se, opts.mountpoint) != 0)
+        goto err_out3;
+
+    fuse_daemonize(opts.foreground);
+
+    /* Start thread to update file contents */
+    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
+    if (ret != 0) {
+        fprintf(stderr, "pthread_create failed with %s\n",
+                strerror(ret));
+        goto err_out3;
+    }
+
+    /* Block until ctrl+c or fusermount -u */
+    if (opts.singlethread)
+        ret = fuse_session_loop(se);
+    else
+        ret = fuse_session_loop_mt(se, opts.clone_fd);
+
+    assert(retrieve_status != 1);
+    fuse_session_unmount(se);
+err_out3:
+    fuse_remove_signal_handlers(se);
+err_out2:
+    fuse_session_destroy(se);
+err_out1:
+    free(opts.mountpoint);
+    fuse_opt_free_args(&args);
+
+    return ret ? 1 : 0;
+}
+
+
+/**
+ * Local Variables:
+ * mode: c
+ * indent-tabs-mode: nil
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/example/passthrough.c b/example/passthrough.c
new file mode 100644
index 0000000..15f424a
--- /dev/null
+++ b/example/passthrough.c
@@ -0,0 +1,450 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+  Copyright (C) 2011       Sebastian Pipping <sebastian@pipping.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This file system mirrors the existing file system hierarchy of the
+ * system, starting at the root file system. This is implemented by
+ * just "passing through" all requests to the corresponding user-space
+ * libc functions. Its performance is terrible.
+ *
+ * Compile with
+ *
+ *     gcc -Wall passthrough.c `pkg-config fuse3 --cflags --libs` -o passthrough
+ *
+ * ## Source code ##
+ * \include passthrough.c
+ */
+
+
+#define FUSE_USE_VERSION 30
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef linux
+/* For pread()/pwrite()/utimensat() */
+#define _XOPEN_SOURCE 700
+#endif
+
+#include <fuse.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#ifdef HAVE_SETXATTR
+#include <sys/xattr.h>
+#endif
+
+static void *xmp_init(struct fuse_conn_info *conn,
+		      struct fuse_config *cfg)
+{
+	(void) conn;
+	cfg->use_ino = 1;
+	return NULL;
+}
+
+static int xmp_getattr(const char *path, struct stat *stbuf,
+		       struct fuse_file_info *fi)
+{
+	(void) fi;
+	int res;
+
+	res = lstat(path, stbuf);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_access(const char *path, int mask)
+{
+	int res;
+
+	res = access(path, mask);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_readlink(const char *path, char *buf, size_t size)
+{
+	int res;
+
+	res = readlink(path, buf, size - 1);
+	if (res == -1)
+		return -errno;
+
+	buf[res] = '\0';
+	return 0;
+}
+
+
+static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+		       off_t offset, struct fuse_file_info *fi,
+		       enum fuse_readdir_flags flags)
+{
+	DIR *dp;
+	struct dirent *de;
+
+	(void) offset;
+	(void) fi;
+	(void) flags;
+
+	dp = opendir(path);
+	if (dp == NULL)
+		return -errno;
+
+	while ((de = readdir(dp)) != NULL) {
+		struct stat st;
+		memset(&st, 0, sizeof(st));
+		st.st_ino = de->d_ino;
+		st.st_mode = de->d_type << 12;
+		if (filler(buf, de->d_name, &st, 0, 0))
+			break;
+	}
+
+	closedir(dp);
+	return 0;
+}
+
+static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+	int res;
+
+	/* On Linux this could just be 'mknod(path, mode, rdev)' but this
+	   is more portable */
+	if (S_ISREG(mode)) {
+		res = open(path, O_CREAT | O_EXCL | O_WRONLY, mode);
+		if (res >= 0)
+			res = close(res);
+	} else if (S_ISFIFO(mode))
+		res = mkfifo(path, mode);
+	else
+		res = mknod(path, mode, rdev);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_mkdir(const char *path, mode_t mode)
+{
+	int res;
+
+	res = mkdir(path, mode);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_unlink(const char *path)
+{
+	int res;
+
+	res = unlink(path);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_rmdir(const char *path)
+{
+	int res;
+
+	res = rmdir(path);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_symlink(const char *from, const char *to)
+{
+	int res;
+
+	res = symlink(from, to);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_rename(const char *from, const char *to, unsigned int flags)
+{
+	int res;
+
+	if (flags)
+		return -EINVAL;
+
+	res = rename(from, to);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_link(const char *from, const char *to)
+{
+	int res;
+
+	res = link(from, to);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_chmod(const char *path, mode_t mode,
+		     struct fuse_file_info *fi)
+{
+	(void) fi;
+	int res;
+
+	res = chmod(path, mode);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+		     struct fuse_file_info *fi)
+{
+	(void) fi;
+	int res;
+
+	res = lchown(path, uid, gid);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_truncate(const char *path, off_t size,
+			struct fuse_file_info *fi)
+{
+	(void) fi;
+	int res;
+
+	res = truncate(path, size);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+#ifdef HAVE_UTIMENSAT
+static int xmp_utimens(const char *path, const struct timespec ts[2],
+		       struct fuse_file_info *fi)
+{
+	(void) fi;
+	int res;
+
+	/* don't use utime/utimes since they follow symlinks */
+	res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+#endif
+
+static int xmp_open(const char *path, struct fuse_file_info *fi)
+{
+	int res;
+
+	res = open(path, fi->flags);
+	if (res == -1)
+		return -errno;
+
+	close(res);
+	return 0;
+}
+
+static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+		    struct fuse_file_info *fi)
+{
+	int fd;
+	int res;
+
+	(void) fi;
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		return -errno;
+
+	res = pread(fd, buf, size, offset);
+	if (res == -1)
+		res = -errno;
+
+	close(fd);
+	return res;
+}
+
+static int xmp_write(const char *path, const char *buf, size_t size,
+		     off_t offset, struct fuse_file_info *fi)
+{
+	int fd;
+	int res;
+
+	(void) fi;
+	fd = open(path, O_WRONLY);
+	if (fd == -1)
+		return -errno;
+
+	res = pwrite(fd, buf, size, offset);
+	if (res == -1)
+		res = -errno;
+
+	close(fd);
+	return res;
+}
+
+static int xmp_statfs(const char *path, struct statvfs *stbuf)
+{
+	int res;
+
+	res = statvfs(path, stbuf);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_release(const char *path, struct fuse_file_info *fi)
+{
+	/* Just a stub.	 This method is optional and can safely be left
+	   unimplemented */
+
+	(void) path;
+	(void) fi;
+	return 0;
+}
+
+static int xmp_fsync(const char *path, int isdatasync,
+		     struct fuse_file_info *fi)
+{
+	/* Just a stub.	 This method is optional and can safely be left
+	   unimplemented */
+
+	(void) path;
+	(void) isdatasync;
+	(void) fi;
+	return 0;
+}
+
+#ifdef HAVE_POSIX_FALLOCATE
+static int xmp_fallocate(const char *path, int mode,
+			off_t offset, off_t length, struct fuse_file_info *fi)
+{
+	int fd;
+	int res;
+
+	(void) fi;
+
+	if (mode)
+		return -EOPNOTSUPP;
+
+	fd = open(path, O_WRONLY);
+	if (fd == -1)
+		return -errno;
+
+	res = -posix_fallocate(fd, offset, length);
+
+	close(fd);
+	return res;
+}
+#endif
+
+#ifdef HAVE_SETXATTR
+/* xattr operations are optional and can safely be left unimplemented */
+static int xmp_setxattr(const char *path, const char *name, const char *value,
+			size_t size, int flags)
+{
+	int res = lsetxattr(path, name, value, size, flags);
+	if (res == -1)
+		return -errno;
+	return 0;
+}
+
+static int xmp_getxattr(const char *path, const char *name, char *value,
+			size_t size)
+{
+	int res = lgetxattr(path, name, value, size);
+	if (res == -1)
+		return -errno;
+	return res;
+}
+
+static int xmp_listxattr(const char *path, char *list, size_t size)
+{
+	int res = llistxattr(path, list, size);
+	if (res == -1)
+		return -errno;
+	return res;
+}
+
+static int xmp_removexattr(const char *path, const char *name)
+{
+	int res = lremovexattr(path, name);
+	if (res == -1)
+		return -errno;
+	return 0;
+}
+#endif /* HAVE_SETXATTR */
+
+static struct fuse_operations xmp_oper = {
+	.init           = xmp_init,
+	.getattr	= xmp_getattr,
+	.access		= xmp_access,
+	.readlink	= xmp_readlink,
+	.readdir	= xmp_readdir,
+	.mknod		= xmp_mknod,
+	.mkdir		= xmp_mkdir,
+	.symlink	= xmp_symlink,
+	.unlink		= xmp_unlink,
+	.rmdir		= xmp_rmdir,
+	.rename		= xmp_rename,
+	.link		= xmp_link,
+	.chmod		= xmp_chmod,
+	.chown		= xmp_chown,
+	.truncate	= xmp_truncate,
+#ifdef HAVE_UTIMENSAT
+	.utimens	= xmp_utimens,
+#endif
+	.open		= xmp_open,
+	.read		= xmp_read,
+	.write		= xmp_write,
+	.statfs		= xmp_statfs,
+	.release	= xmp_release,
+	.fsync		= xmp_fsync,
+#ifdef HAVE_POSIX_FALLOCATE
+	.fallocate	= xmp_fallocate,
+#endif
+#ifdef HAVE_SETXATTR
+	.setxattr	= xmp_setxattr,
+	.getxattr	= xmp_getxattr,
+	.listxattr	= xmp_listxattr,
+	.removexattr	= xmp_removexattr,
+#endif
+};
+
+int main(int argc, char *argv[])
+{
+	umask(0);
+	return fuse_main(argc, argv, &xmp_oper, NULL);
+}
diff --git a/example/passthrough_fh.c b/example/passthrough_fh.c
new file mode 100644
index 0000000..c807d75
--- /dev/null
+++ b/example/passthrough_fh.c
@@ -0,0 +1,604 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+  Copyright (C) 2011       Sebastian Pipping <sebastian@pipping.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This file system mirrors the existing file system hierarchy of the
+ * system, starting at the root file system. This is implemented by
+ * just "passing through" all requests to the corresponding user-space
+ * libc functions. This implementation is a little more sophisticated
+ * than the one in passthrough.c, so performance is not quite as bad.
+ *
+ * Compile with:
+ *
+ *     gcc -Wall passthrough_fh.c `pkg-config fuse3 --cflags --libs` -lulockmgr -o passthrough_fh
+ *
+ * ## Source code ##
+ * \include passthrough_fh.c
+ */
+
+#define FUSE_USE_VERSION 30
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+
+#include <fuse.h>
+
+#ifdef HAVE_LIBULOCKMGR
+#include <ulockmgr.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/time.h>
+#ifdef HAVE_SETXATTR
+#include <sys/xattr.h>
+#endif
+#include <sys/file.h> /* flock(2) */
+
+static void *xmp_init(struct fuse_conn_info *conn,
+		      struct fuse_config *cfg)
+{
+	(void) conn;
+	cfg->use_ino = 1;
+	cfg->nullpath_ok = 1;
+	return NULL;
+}
+
+static int xmp_getattr(const char *path, struct stat *stbuf,
+			struct fuse_file_info *fi)
+{
+	int res;
+
+	(void) path;
+
+	if(fi)
+		res = fstat(fi->fh, stbuf);
+	else
+		res = lstat(path, stbuf);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_access(const char *path, int mask)
+{
+	int res;
+
+	res = access(path, mask);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_readlink(const char *path, char *buf, size_t size)
+{
+	int res;
+
+	res = readlink(path, buf, size - 1);
+	if (res == -1)
+		return -errno;
+
+	buf[res] = '\0';
+	return 0;
+}
+
+struct xmp_dirp {
+	DIR *dp;
+	struct dirent *entry;
+	off_t offset;
+};
+
+static int xmp_opendir(const char *path, struct fuse_file_info *fi)
+{
+	int res;
+	struct xmp_dirp *d = malloc(sizeof(struct xmp_dirp));
+	if (d == NULL)
+		return -ENOMEM;
+
+	d->dp = opendir(path);
+	if (d->dp == NULL) {
+		res = -errno;
+		free(d);
+		return res;
+	}
+	d->offset = 0;
+	d->entry = NULL;
+
+	fi->fh = (unsigned long) d;
+	return 0;
+}
+
+static inline struct xmp_dirp *get_dirp(struct fuse_file_info *fi)
+{
+	return (struct xmp_dirp *) (uintptr_t) fi->fh;
+}
+
+static int xmp_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 xmp_dirp *d = get_dirp(fi);
+
+	(void) path;
+	if (offset != d->offset) {
+		seekdir(d->dp, offset);
+		d->entry = NULL;
+		d->offset = offset;
+	}
+	while (1) {
+		struct stat st;
+		off_t nextoff;
+		enum fuse_fill_dir_flags fill_flags = 0;
+
+		if (!d->entry) {
+			d->entry = readdir(d->dp);
+			if (!d->entry)
+				break;
+		}
+#ifdef HAVE_FSTATAT
+		if (flags & FUSE_READDIR_PLUS) {
+			int res;
+
+			res = fstatat(dirfd(d->dp), d->entry->d_name, &st,
+				      AT_SYMLINK_NOFOLLOW);
+			if (res != -1)
+				fill_flags |= FUSE_FILL_DIR_PLUS;
+		}
+#endif
+		if (!(fill_flags & FUSE_FILL_DIR_PLUS)) {
+			memset(&st, 0, sizeof(st));
+			st.st_ino = d->entry->d_ino;
+			st.st_mode = d->entry->d_type << 12;
+		}
+		nextoff = telldir(d->dp);
+		if (filler(buf, d->entry->d_name, &st, nextoff, fill_flags))
+			break;
+
+		d->entry = NULL;
+		d->offset = nextoff;
+	}
+
+	return 0;
+}
+
+static int xmp_releasedir(const char *path, struct fuse_file_info *fi)
+{
+	struct xmp_dirp *d = get_dirp(fi);
+	(void) path;
+	closedir(d->dp);
+	free(d);
+	return 0;
+}
+
+static int xmp_mknod(const char *path, mode_t mode, dev_t rdev)
+{
+	int res;
+
+	if (S_ISFIFO(mode))
+		res = mkfifo(path, mode);
+	else
+		res = mknod(path, mode, rdev);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_mkdir(const char *path, mode_t mode)
+{
+	int res;
+
+	res = mkdir(path, mode);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_unlink(const char *path)
+{
+	int res;
+
+	res = unlink(path);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_rmdir(const char *path)
+{
+	int res;
+
+	res = rmdir(path);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_symlink(const char *from, const char *to)
+{
+	int res;
+
+	res = symlink(from, to);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_rename(const char *from, const char *to, unsigned int flags)
+{
+	int res;
+
+	/* When we have renameat2() in libc, then we can implement flags */
+	if (flags)
+		return -EINVAL;
+
+	res = rename(from, to);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_link(const char *from, const char *to)
+{
+	int res;
+
+	res = link(from, to);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_chmod(const char *path, mode_t mode,
+		     struct fuse_file_info *fi)
+{
+	int res;
+
+	if(fi)
+		res = fchmod(fi->fh, mode);
+	else
+		res = chmod(path, mode);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_chown(const char *path, uid_t uid, gid_t gid,
+		     struct fuse_file_info *fi)
+{
+	int res;
+
+	if (fi)
+		res = fchown(fi->fh, uid, gid);
+	else
+		res = lchown(path, uid, gid);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_truncate(const char *path, off_t size,
+			 struct fuse_file_info *fi)
+{
+	int res;
+
+	if(fi)
+		res = ftruncate(fi->fh, size);
+	else
+		res = truncate(path, size);
+
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+#ifdef HAVE_UTIMENSAT
+static int xmp_utimens(const char *path, const struct timespec ts[2],
+		       struct fuse_file_info *fi)
+{
+	int res;
+
+	/* don't use utime/utimes since they follow symlinks */
+	if (fi)
+		res = futimens(fi->fh, ts);
+	else
+		res = utimensat(0, path, ts, AT_SYMLINK_NOFOLLOW);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+#endif
+
+static int xmp_create(const char *path, mode_t mode, struct fuse_file_info *fi)
+{
+	int fd;
+
+	fd = open(path, fi->flags, mode);
+	if (fd == -1)
+		return -errno;
+
+	fi->fh = fd;
+	return 0;
+}
+
+static int xmp_open(const char *path, struct fuse_file_info *fi)
+{
+	int fd;
+
+	fd = open(path, fi->flags);
+	if (fd == -1)
+		return -errno;
+
+	fi->fh = fd;
+	return 0;
+}
+
+static int xmp_read(const char *path, char *buf, size_t size, off_t offset,
+		    struct fuse_file_info *fi)
+{
+	int res;
+
+	(void) path;
+	res = pread(fi->fh, buf, size, offset);
+	if (res == -1)
+		res = -errno;
+
+	return res;
+}
+
+static int xmp_read_buf(const char *path, struct fuse_bufvec **bufp,
+			size_t size, off_t offset, struct fuse_file_info *fi)
+{
+	struct fuse_bufvec *src;
+
+	(void) path;
+
+	src = malloc(sizeof(struct fuse_bufvec));
+	if (src == NULL)
+		return -ENOMEM;
+
+	*src = FUSE_BUFVEC_INIT(size);
+
+	src->buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
+	src->buf[0].fd = fi->fh;
+	src->buf[0].pos = offset;
+
+	*bufp = src;
+
+	return 0;
+}
+
+static int xmp_write(const char *path, const char *buf, size_t size,
+		     off_t offset, struct fuse_file_info *fi)
+{
+	int res;
+
+	(void) path;
+	res = pwrite(fi->fh, buf, size, offset);
+	if (res == -1)
+		res = -errno;
+
+	return res;
+}
+
+static int xmp_write_buf(const char *path, struct fuse_bufvec *buf,
+		     off_t offset, struct fuse_file_info *fi)
+{
+	struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(buf));
+
+	(void) path;
+
+	dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
+	dst.buf[0].fd = fi->fh;
+	dst.buf[0].pos = offset;
+
+	return fuse_buf_copy(&dst, buf, FUSE_BUF_SPLICE_NONBLOCK);
+}
+
+static int xmp_statfs(const char *path, struct statvfs *stbuf)
+{
+	int res;
+
+	res = statvfs(path, stbuf);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_flush(const char *path, struct fuse_file_info *fi)
+{
+	int res;
+
+	(void) path;
+	/* This is called from every close on an open file, so call the
+	   close on the underlying filesystem.	But since flush may be
+	   called multiple times for an open file, this must not really
+	   close the file.  This is important if used on a network
+	   filesystem like NFS which flush the data/metadata on close() */
+	res = close(dup(fi->fh));
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static int xmp_release(const char *path, struct fuse_file_info *fi)
+{
+	(void) path;
+	close(fi->fh);
+
+	return 0;
+}
+
+static int xmp_fsync(const char *path, int isdatasync,
+		     struct fuse_file_info *fi)
+{
+	int res;
+	(void) path;
+
+#ifndef HAVE_FDATASYNC
+	(void) isdatasync;
+#else
+	if (isdatasync)
+		res = fdatasync(fi->fh);
+	else
+#endif
+		res = fsync(fi->fh);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+#ifdef HAVE_POSIX_FALLOCATE
+static int xmp_fallocate(const char *path, int mode,
+			off_t offset, off_t length, struct fuse_file_info *fi)
+{
+	(void) path;
+
+	if (mode)
+		return -EOPNOTSUPP;
+
+	return -posix_fallocate(fi->fh, offset, length);
+}
+#endif
+
+#ifdef HAVE_SETXATTR
+/* xattr operations are optional and can safely be left unimplemented */
+static int xmp_setxattr(const char *path, const char *name, const char *value,
+			size_t size, int flags)
+{
+	int res = lsetxattr(path, name, value, size, flags);
+	if (res == -1)
+		return -errno;
+	return 0;
+}
+
+static int xmp_getxattr(const char *path, const char *name, char *value,
+			size_t size)
+{
+	int res = lgetxattr(path, name, value, size);
+	if (res == -1)
+		return -errno;
+	return res;
+}
+
+static int xmp_listxattr(const char *path, char *list, size_t size)
+{
+	int res = llistxattr(path, list, size);
+	if (res == -1)
+		return -errno;
+	return res;
+}
+
+static int xmp_removexattr(const char *path, const char *name)
+{
+	int res = lremovexattr(path, name);
+	if (res == -1)
+		return -errno;
+	return 0;
+}
+#endif /* HAVE_SETXATTR */
+
+#ifdef HAVE_LIBULOCKMGR
+static int xmp_lock(const char *path, struct fuse_file_info *fi, int cmd,
+		    struct flock *lock)
+{
+	(void) path;
+
+	return ulockmgr_op(fi->fh, cmd, lock, &fi->lock_owner,
+			   sizeof(fi->lock_owner));
+}
+#endif
+
+static int xmp_flock(const char *path, struct fuse_file_info *fi, int op)
+{
+	int res;
+	(void) path;
+
+	res = flock(fi->fh, op);
+	if (res == -1)
+		return -errno;
+
+	return 0;
+}
+
+static struct fuse_operations xmp_oper = {
+	.init           = xmp_init,
+	.getattr	= xmp_getattr,
+	.access		= xmp_access,
+	.readlink	= xmp_readlink,
+	.opendir	= xmp_opendir,
+	.readdir	= xmp_readdir,
+	.releasedir	= xmp_releasedir,
+	.mknod		= xmp_mknod,
+	.mkdir		= xmp_mkdir,
+	.symlink	= xmp_symlink,
+	.unlink		= xmp_unlink,
+	.rmdir		= xmp_rmdir,
+	.rename		= xmp_rename,
+	.link		= xmp_link,
+	.chmod		= xmp_chmod,
+	.chown		= xmp_chown,
+	.truncate	= xmp_truncate,
+#ifdef HAVE_UTIMENSAT
+	.utimens	= xmp_utimens,
+#endif
+	.create		= xmp_create,
+	.open		= xmp_open,
+	.read		= xmp_read,
+	.read_buf	= xmp_read_buf,
+	.write		= xmp_write,
+	.write_buf	= xmp_write_buf,
+	.statfs		= xmp_statfs,
+	.flush		= xmp_flush,
+	.release	= xmp_release,
+	.fsync		= xmp_fsync,
+#ifdef HAVE_POSIX_FALLOCATE
+	.fallocate	= xmp_fallocate,
+#endif
+#ifdef HAVE_SETXATTR
+	.setxattr	= xmp_setxattr,
+	.getxattr	= xmp_getxattr,
+	.listxattr	= xmp_listxattr,
+	.removexattr	= xmp_removexattr,
+#endif
+#ifdef HAVE_LIBULOCKMGR
+	.lock		= xmp_lock,
+#endif
+	.flock		= xmp_flock,
+};
+
+int main(int argc, char *argv[])
+{
+	umask(0);
+	return fuse_main(argc, argv, &xmp_oper, NULL);
+}
diff --git a/example/passthrough_ll.c b/example/passthrough_ll.c
new file mode 100644
index 0000000..b40cf76
--- /dev/null
+++ b/example/passthrough_ll.c
@@ -0,0 +1,524 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This file system mirrors the existing file system hierarchy of the
+ * system, starting at the root file system. This is implemented by
+ * just "passing through" all requests to the corresponding user-space
+ * libc functions. In contrast to passthrough.c and passthrough_fh.c,
+ * this implementation ises the low-level API. Its performance should
+ * be the least bad among the three.
+ *
+ * Compile with:
+ *
+ *     gcc -Wall passthrough_ll.c `pkg-config fuse3 --cflags --libs` -o passthrough_ll
+ *
+ * ## Source code ##
+ * \include passthrough_ll.c
+ */
+
+#define _GNU_SOURCE
+#define FUSE_USE_VERSION 30
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <fuse_lowlevel.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <limits.h>
+#include <dirent.h>
+#include <assert.h>
+#include <errno.h>
+#include <err.h>
+
+/* Compat stuff.  Doesn't make it work, just makes it compile. */
+#ifndef HAVE_FSTATAT
+#warning fstatat(2) needed by this program
+int fstatat(int dirfd, const char *pathname, struct stat *buf, int flags)
+{
+	errno = ENOSYS;
+	return -1;
+}
+#endif
+#ifndef HAVE_OPENAT
+#warning openat(2) needed by this program
+int openat(int dirfd, const char *pathname, int flags, ...)
+{
+	errno = ENOSYS;
+	return -1;
+}
+#endif
+#ifndef HAVE_READLINKAT
+#warning readlinkat(2) needed by this program
+ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz)
+{
+	errno = ENOSYS;
+	return -1;
+}
+#endif
+#ifndef AT_EMPTY_PATH
+#warning AT_EMPTY_PATH needed by this program
+#define AT_EMPTY_PATH 0
+#endif
+#ifndef AT_SYMLINK_NOFOLLOW
+#warning AT_SYMLINK_NOFOLLOW needed by this program
+#define AT_SYMLINK_NOFOLLOW 0
+#endif
+#ifndef O_PATH
+#warning O_PATH needed by this program
+#define O_PATH 0
+#endif
+#ifndef O_NOFOLLOW
+#warning O_NOFOLLOW needed by this program
+#define O_NOFOLLOW 0
+#endif
+
+struct lo_inode {
+	struct lo_inode *next;
+	struct lo_inode *prev;
+	int fd;
+	ino_t ino;
+	dev_t dev;
+	uint64_t nlookup;
+};
+
+struct lo_data {
+	int debug;
+	struct lo_inode root;
+};
+
+static struct lo_data *lo_data(fuse_req_t req)
+{
+	return (struct lo_data *) fuse_req_userdata(req);
+}
+
+static struct lo_inode *lo_inode(fuse_req_t req, fuse_ino_t ino)
+{
+	if (ino == FUSE_ROOT_ID)
+		return &lo_data(req)->root;
+	else
+		return (struct lo_inode *) (uintptr_t) ino;
+}
+
+static int lo_fd(fuse_req_t req, fuse_ino_t ino)
+{
+	return lo_inode(req, ino)->fd;
+}
+
+static bool lo_debug(fuse_req_t req)
+{
+	return lo_data(req)->debug != 0;
+}
+
+static void lo_getattr(fuse_req_t req, fuse_ino_t ino,
+			     struct fuse_file_info *fi)
+{
+	int res;
+	struct stat buf;
+	(void) fi;
+
+	res = fstatat(lo_fd(req, ino), "", &buf, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+	if (res == -1)
+		return (void) fuse_reply_err(req, errno);
+
+	fuse_reply_attr(req, &buf, 1.0);
+}
+
+static struct lo_inode *lo_find(struct lo_data *lo, struct stat *st)
+{
+	struct lo_inode *p;
+
+	for (p = lo->root.next; p != &lo->root; p = p->next) {
+		if (p->ino == st->st_ino && p->dev == st->st_dev)
+			return p;
+	}
+	return NULL;
+}
+
+static int lo_do_lookup(fuse_req_t req, fuse_ino_t parent, const char *name,
+			 struct fuse_entry_param *e)
+{
+	int newfd;
+	int res;
+	int saverr;
+	struct lo_inode *inode;
+
+	memset(e, 0, sizeof(*e));
+	e->attr_timeout = 1.0;
+	e->entry_timeout = 1.0;
+
+	newfd = openat(lo_fd(req, parent), name, O_PATH | O_NOFOLLOW);
+	if (newfd == -1)
+		goto out_err;
+
+	res = fstatat(newfd, "", &e->attr, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW);
+	if (res == -1)
+		goto out_err;
+
+	inode = lo_find(lo_data(req), &e->attr);
+	if (inode) {
+		close(newfd);
+		newfd = -1;
+	} else {
+		struct lo_inode *prev = &lo_data(req)->root;
+		struct lo_inode *next = prev->next;
+		saverr = ENOMEM;
+		inode = calloc(1, sizeof(struct lo_inode));
+		if (!inode)
+			goto out_err;
+
+		inode->fd = newfd;
+		inode->ino = e->attr.st_ino;
+		inode->dev = e->attr.st_dev;
+
+		next->prev = inode;
+		inode->next = next;
+		inode->prev = prev;
+		prev->next = inode;
+	}
+	inode->nlookup++;
+	e->ino = (uintptr_t) inode;
+
+	if (lo_debug(req))
+		fprintf(stderr, "  %lli/%s -> %lli\n",
+			(unsigned long long) parent, name, (unsigned long long) e->ino);
+
+	return 0;
+
+out_err:
+	saverr = errno;
+	if (newfd != -1)
+		close(newfd);
+	return saverr;
+}
+
+static void lo_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
+{
+	struct fuse_entry_param e;
+	int err;
+
+	err = lo_do_lookup(req, parent, name, &e);
+	if (err)
+		fuse_reply_err(req, err);
+	else
+		fuse_reply_entry(req, &e);
+}
+
+static void lo_free(struct lo_inode *inode)
+{
+	struct lo_inode *prev = inode->prev;
+	struct lo_inode *next = inode->next;
+
+	next->prev = prev;
+	prev->next = next;
+	close(inode->fd);
+	free(inode);
+}
+
+static void lo_forget(fuse_req_t req, fuse_ino_t ino, uint64_t nlookup)
+{
+	struct lo_inode *inode = lo_inode(req, ino);
+
+	if (lo_debug(req)) {
+		fprintf(stderr, "  forget %lli %lli -%lli\n",
+			(unsigned long long) ino, (unsigned long long) inode->nlookup,
+			(unsigned long long) nlookup);
+	}
+
+	assert(inode->nlookup >= nlookup);
+	inode->nlookup -= nlookup;
+
+	if (!inode->nlookup)
+		lo_free(inode);
+
+	fuse_reply_none(req);
+}
+
+static void lo_readlink(fuse_req_t req, fuse_ino_t ino)
+{
+	char buf[PATH_MAX + 1];
+	int res;
+
+	res = readlinkat(lo_fd(req, ino), "", buf, sizeof(buf));
+	if (res == -1)
+		return (void) fuse_reply_err(req, errno);
+
+	if (res == sizeof(buf))
+		return (void) fuse_reply_err(req, ENAMETOOLONG);
+
+	buf[res] = '\0';
+
+	fuse_reply_readlink(req, buf);
+}
+
+struct lo_dirp {
+	int fd;
+	DIR *dp;
+	struct dirent *entry;
+	off_t offset;
+};
+
+static struct lo_dirp *lo_dirp(struct fuse_file_info *fi)
+{
+	return (struct lo_dirp *) (uintptr_t) fi->fh;
+}
+
+static void lo_opendir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+	int error = ENOMEM;
+	struct lo_dirp *d = calloc(1, sizeof(struct lo_dirp));
+	if (d == NULL)
+		goto out_err;
+
+	d->fd = openat(lo_fd(req, ino), ".", O_RDONLY);
+	if (d->fd == -1)
+		goto out_errno;
+
+	d->dp = fdopendir(d->fd);
+	if (d->dp == NULL)
+		goto out_errno;
+
+	d->offset = 0;
+	d->entry = NULL;
+
+	fi->fh = (uintptr_t) d;
+	fuse_reply_open(req, fi);
+	return;
+
+out_errno:
+	error = errno;
+out_err:
+	if (d) {
+		if (d->fd != -1)
+			close(d->fd);
+		free(d);
+	}
+	fuse_reply_err(req, error);
+}
+
+static void lo_do_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+			  off_t offset, struct fuse_file_info *fi, int plus)
+{
+	struct lo_dirp *d = lo_dirp(fi);
+	char *buf;
+	char *p;
+	size_t rem;
+	int err;
+
+	(void) ino;
+
+	buf = calloc(size, 1);
+	if (!buf)
+		return (void) fuse_reply_err(req, ENOMEM);
+
+	if (offset != d->offset) {
+		seekdir(d->dp, offset);
+		d->entry = NULL;
+		d->offset = offset;
+	}
+	p = buf;
+	rem = size;
+	while (1) {
+		size_t entsize;
+		off_t nextoff;
+
+		if (!d->entry) {
+			errno = 0;
+			d->entry = readdir(d->dp);
+			if (!d->entry) {
+				if (errno && rem == size) {
+					err = errno;
+					goto error;
+				}
+				break;
+			}
+		}
+		nextoff = telldir(d->dp);
+		if (plus) {
+			struct fuse_entry_param e;
+
+			err = lo_do_lookup(req, ino, d->entry->d_name, &e);
+			if (err)
+				goto error;
+
+			entsize = fuse_add_direntry_plus(req, p, rem,
+							 d->entry->d_name,
+							 &e, nextoff);
+		} else {
+			struct stat st = {
+				.st_ino = d->entry->d_ino,
+				.st_mode = d->entry->d_type << 12,
+			};
+			entsize = fuse_add_direntry(req, p, rem,
+						    d->entry->d_name,
+						    &st, nextoff);
+		}
+		if (entsize > rem)
+			break;
+
+		p += entsize;
+		rem -= entsize;
+
+		d->entry = NULL;
+		d->offset = nextoff;
+	}
+
+	fuse_reply_buf(req, buf, size - rem);
+	free(buf);
+	return;
+
+error:
+	free(buf);
+	fuse_reply_err(req, err);
+}
+
+static void lo_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
+		       off_t offset, struct fuse_file_info *fi)
+{
+	lo_do_readdir(req, ino, size, offset, fi, 0);
+}
+
+static void lo_readdirplus(fuse_req_t req, fuse_ino_t ino, size_t size,
+			   off_t offset, struct fuse_file_info *fi)
+{
+	lo_do_readdir(req, ino, size, offset, fi, 1);
+}
+
+static void lo_releasedir(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+	struct lo_dirp *d = lo_dirp(fi);
+	(void) ino;
+	closedir(d->dp);
+	free(d);
+	fuse_reply_err(req, 0);
+}
+
+static void lo_open(fuse_req_t req, fuse_ino_t ino,
+			  struct fuse_file_info *fi)
+{
+	int fd;
+	char buf[64];
+
+	sprintf(buf, "/proc/self/fd/%i", lo_fd(req, ino));
+	fd = open(buf, fi->flags & ~O_NOFOLLOW);
+	if (fd == -1)
+		return (void) fuse_reply_err(req, errno);
+
+	fi->fh = fd;
+	fuse_reply_open(req, fi);
+}
+
+static void lo_release(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
+{
+	(void) ino;
+
+	close(fi->fh);
+	fuse_reply_err(req, 0);
+}
+
+static void lo_read(fuse_req_t req, fuse_ino_t ino, size_t size,
+			  off_t offset, struct fuse_file_info *fi)
+{
+	struct fuse_bufvec buf = FUSE_BUFVEC_INIT(size);
+
+	(void) ino;
+
+	buf.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK;
+	buf.buf[0].fd = fi->fh;
+	buf.buf[0].pos = offset;
+
+	fuse_reply_data(req, &buf, FUSE_BUF_SPLICE_MOVE);
+}
+
+static struct fuse_lowlevel_ops lo_oper = {
+	.lookup		= lo_lookup,
+	.forget		= lo_forget,
+	.getattr	= lo_getattr,
+	.readlink	= lo_readlink,
+	.opendir	= lo_opendir,
+	.readdir	= lo_readdir,
+	.readdirplus	= lo_readdirplus,
+	.releasedir	= lo_releasedir,
+	.open		= lo_open,
+	.release	= lo_release,
+	.read		= lo_read,
+};
+
+int main(int argc, char *argv[])
+{
+	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+	struct fuse_session *se;
+	struct fuse_cmdline_opts opts;
+	struct lo_data lo = { .debug = 0 };
+	int ret = -1;
+
+	lo.root.next = lo.root.prev = &lo.root;
+	lo.root.fd = -1;
+
+	if (fuse_parse_cmdline(&args, &opts) != 0)
+		return 1;
+	if (opts.show_help) {
+		printf("usage: %s [options] <mountpoint>\n\n", argv[0]);
+		fuse_cmdline_help();
+		fuse_lowlevel_help();
+		ret = 0;
+		goto err_out1;
+	} else if (opts.show_version) {
+		printf("FUSE library version %s\n", fuse_pkgversion());
+		fuse_lowlevel_version();
+		ret = 0;
+		goto err_out1;
+	}
+
+	lo.debug = opts.debug;
+	lo.root.fd = open("/", O_PATH);
+	lo.root.nlookup = 2;
+	if (lo.root.fd == -1)
+		err(1, "open(\"/\", O_PATH)");
+
+	se = fuse_session_new(&args, &lo_oper, sizeof(lo_oper), &lo);
+	if (se == NULL)
+	    goto err_out1;
+
+	if (fuse_set_signal_handlers(se) != 0)
+	    goto err_out2;
+
+	if (fuse_session_mount(se, opts.mountpoint) != 0)
+	    goto err_out3;
+
+	fuse_daemonize(opts.foreground);
+
+	/* Block until ctrl+c or fusermount -u */
+	if (opts.singlethread)
+		ret = fuse_session_loop(se);
+	else
+		ret = fuse_session_loop_mt(se, opts.clone_fd);
+
+	fuse_session_unmount(se);
+err_out3:
+	fuse_remove_signal_handlers(se);
+err_out2:
+	fuse_session_destroy(se);
+err_out1:
+	free(opts.mountpoint);
+	fuse_opt_free_args(&args);
+
+	while (lo.root.next != &lo.root)
+		lo_free(lo.root.next);
+	if (lo.root.fd >= 0)
+		close(lo.root.fd);
+
+	return ret ? 1 : 0;
+}
diff --git a/example/poll.c b/example/poll.c
new file mode 100644
index 0000000..36a1371
--- /dev/null
+++ b/example/poll.c
@@ -0,0 +1,296 @@
+/*
+  FUSE fsel: FUSE select example
+  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 GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This example illustrates how to write a FUSE file system that
+ * supports polling for changes that don't come through the kernel. It
+ * can be tested with the poll_client.c program.
+ *
+ * Compile with:
+ *
+ *     gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll
+ *
+ * ## Source code ##
+ * \include poll.c
+ */
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+
+#include <fuse.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <time.h>
+#include <pthread.h>
+#include <poll.h>
+
+/*
+ * fsel_open_mask is used to limit the number of opens to 1 per file.
+ * This is to use file index (0-F) as fh as poll support requires
+ * unique fh per open file.  Lifting this would require proper open
+ * file management.
+ */
+static unsigned fsel_open_mask;
+static const char fsel_hex_map[] = "0123456789ABCDEF";
+static struct fuse *fsel_fuse;	/* needed for poll notification */
+
+#define FSEL_CNT_MAX	10	/* each file can store upto 10 chars */
+#define FSEL_FILES	16
+
+static pthread_mutex_t fsel_mutex;	/* protects notify_mask and cnt array */
+static unsigned fsel_poll_notify_mask;	/* poll notification scheduled? */
+static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */
+static unsigned fsel_cnt[FSEL_FILES];	/* nbytes stored in each file */
+
+static int fsel_path_index(const char *path)
+{
+	char ch = path[1];
+
+	if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch))
+		return -1;
+	return ch <= '9' ? ch - '0' : ch - 'A' + 10;
+}
+
+static int fsel_getattr(const char *path, struct stat *stbuf,
+			struct fuse_file_info *fi)
+{
+	(void) fi;
+	int idx;
+
+	memset(stbuf, 0, sizeof(struct stat));
+
+	if (strcmp(path, "/") == 0) {
+		stbuf->st_mode = S_IFDIR | 0555;
+		stbuf->st_nlink = 2;
+		return 0;
+	}
+
+	idx = fsel_path_index(path);
+	if (idx < 0)
+		return -ENOENT;
+
+	stbuf->st_mode = S_IFREG | 0444;
+	stbuf->st_nlink = 1;
+	stbuf->st_size = fsel_cnt[idx];
+	return 0;
+}
+
+static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
+			off_t offset, struct fuse_file_info *fi,
+			enum fuse_readdir_flags flags)
+{
+	char name[2] = { };
+	int i;
+
+	(void) offset;
+	(void) fi;
+	(void) flags;
+
+	if (strcmp(path, "/") != 0)
+		return -ENOENT;
+
+	for (i = 0; i < FSEL_FILES; i++) {
+		name[0] = fsel_hex_map[i];
+		filler(buf, name, NULL, 0, 0);
+	}
+
+	return 0;
+}
+
+static int fsel_open(const char *path, struct fuse_file_info *fi)
+{
+	int idx = fsel_path_index(path);
+
+	if (idx < 0)
+		return -ENOENT;
+	if ((fi->flags & 3) != O_RDONLY)
+		return -EACCES;
+	if (fsel_open_mask & (1 << idx))
+		return -EBUSY;
+	fsel_open_mask |= (1 << idx);
+
+	/*
+	 * fsel files are nonseekable somewhat pipe-like files which
+	 * gets filled up periodically by producer thread and consumed
+	 * on read.  Tell FUSE as such.
+	 */
+	fi->fh = idx;
+	fi->direct_io = 1;
+	fi->nonseekable = 1;
+
+	return 0;
+}
+
+static int fsel_release(const char *path, struct fuse_file_info *fi)
+{
+	int idx = fi->fh;
+
+	(void) path;
+
+	fsel_open_mask &= ~(1 << idx);
+	return 0;
+}
+
+static int fsel_read(const char *path, char *buf, size_t size, off_t offset,
+		     struct fuse_file_info *fi)
+{
+	int idx = fi->fh;
+
+	(void) path;
+	(void) offset;
+
+	pthread_mutex_lock(&fsel_mutex);
+	if (fsel_cnt[idx] < size)
+		size = fsel_cnt[idx];
+	printf("READ   %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]);
+	fsel_cnt[idx] -= size;
+	pthread_mutex_unlock(&fsel_mutex);
+
+	memset(buf, fsel_hex_map[idx], size);
+	return size;
+}
+
+static int fsel_poll(const char *path, struct fuse_file_info *fi,
+		     struct fuse_pollhandle *ph, unsigned *reventsp)
+{
+	static unsigned polled_zero;
+	int idx = fi->fh;
+
+	(void) path;
+
+	/*
+	 * Poll notification requires pointer to struct fuse which
+	 * can't be obtained when using fuse_main().  As notification
+	 * happens only after poll is called, fill it here from
+	 * fuse_context.
+	 */
+	if (!fsel_fuse) {
+		struct fuse_context *cxt = fuse_get_context();
+		if (cxt)
+			fsel_fuse = cxt->fuse;
+	}
+
+	pthread_mutex_lock(&fsel_mutex);
+
+	if (ph != NULL) {
+		struct fuse_pollhandle *oldph = fsel_poll_handle[idx];
+
+		if (oldph)
+			fuse_pollhandle_destroy(oldph);
+
+		fsel_poll_notify_mask |= (1 << idx);
+		fsel_poll_handle[idx] = ph;
+	}
+
+	if (fsel_cnt[idx]) {
+		*reventsp |= POLLIN;
+		printf("POLL   %X cnt=%u polled_zero=%u\n",
+		       idx, fsel_cnt[idx], polled_zero);
+		polled_zero = 0;
+	} else
+		polled_zero++;
+
+	pthread_mutex_unlock(&fsel_mutex);
+	return 0;
+}
+
+static struct fuse_operations fsel_oper = {
+	.getattr	= fsel_getattr,
+	.readdir	= fsel_readdir,
+	.open		= fsel_open,
+	.release	= fsel_release,
+	.read		= fsel_read,
+	.poll		= fsel_poll,
+};
+
+static void *fsel_producer(void *data)
+{
+	const struct timespec interval = { 0, 250000000 };
+	unsigned idx = 0, nr = 1;
+
+	(void) data;
+
+	while (1) {
+		int i, t;
+
+		pthread_mutex_lock(&fsel_mutex);
+
+		/*
+		 * This is the main producer loop which is executed
+		 * ever 500ms.  On each iteration, it fills one byte
+		 * to 1, 2 or 4 files and sends poll notification if
+		 * requested.
+		 */
+		for (i = 0, t = idx; i < nr;
+		     i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) {
+			if (fsel_cnt[t] == FSEL_CNT_MAX)
+				continue;
+
+			fsel_cnt[t]++;
+			if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) {
+				struct fuse_pollhandle *ph;
+
+				printf("NOTIFY %X\n", t);
+				ph = fsel_poll_handle[t];
+				fuse_notify_poll(ph);
+				fuse_pollhandle_destroy(ph);
+				fsel_poll_notify_mask &= ~(1 << t);
+				fsel_poll_handle[t] = NULL;
+			}
+		}
+
+		idx = (idx + 1) % FSEL_FILES;
+		if (idx == 0)
+			nr = (nr * 2) % 7;	/* cycle through 1, 2 and 4 */
+
+		pthread_mutex_unlock(&fsel_mutex);
+
+		nanosleep(&interval, NULL);
+	}
+
+	return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+	pthread_t producer;
+	pthread_attr_t attr;
+	int ret;
+
+	errno = pthread_mutex_init(&fsel_mutex, NULL);
+	if (errno) {
+		perror("pthread_mutex_init");
+		return 1;
+	}
+
+	errno = pthread_attr_init(&attr);
+	if (errno) {
+		perror("pthread_attr_init");
+		return 1;
+	}
+
+	errno = pthread_create(&producer, &attr, fsel_producer, NULL);
+	if (errno) {
+		perror("pthread_create");
+		return 1;
+	}
+
+	ret = fuse_main(argc, argv, &fsel_oper, NULL);
+
+	pthread_cancel(producer);
+	pthread_join(producer, NULL);
+
+	return ret;
+}
diff --git a/example/poll_client.c b/example/poll_client.c
new file mode 100644
index 0000000..1d7a153
--- /dev/null
+++ b/example/poll_client.c
@@ -0,0 +1,84 @@
+/*
+  FUSE fselclient: FUSE select example client
+  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 GPL.
+  See the file COPYING.
+*/
+
+/** @file
+ *
+ * This program tests the poll.c example file systsem.
+ *
+ * Compile with:
+ *
+ *      gcc -Wall poll_client.c -o poll_client
+ *
+ * ## Source code ##
+ * \include poll_client.c
+ */
+
+#include <config.h>
+
+#include <sys/select.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#define FSEL_FILES	16
+
+int main(void)
+{
+	static const char hex_map[FSEL_FILES] = "0123456789ABCDEF";
+	int fds[FSEL_FILES];
+	int i, nfds, tries;
+
+	for (i = 0; i < FSEL_FILES; i++) {
+		char name[] = { hex_map[i], '\0' };
+		fds[i] = open(name, O_RDONLY);
+		if (fds[i] < 0) {
+			perror("open");
+			return 1;
+		}
+	}
+	nfds = fds[FSEL_FILES - 1] + 1;
+
+	for(tries=0; tries < 16; tries++) {
+		static char buf[4096];
+		fd_set rfds;
+		int rc;
+
+		FD_ZERO(&rfds);
+		for (i = 0; i < FSEL_FILES; i++)
+			FD_SET(fds[i], &rfds);
+
+		rc = select(nfds, &rfds, NULL, NULL, NULL);
+
+		if (rc < 0) {
+			perror("select");
+			return 1;
+		}
+
+		for (i = 0; i < FSEL_FILES; i++) {
+			if (!FD_ISSET(fds[i], &rfds)) {
+				printf("_:   ");
+				continue;
+			}
+			printf("%X:", i);
+			rc = read(fds[i], buf, sizeof(buf));
+			if (rc < 0) {
+				perror("read");
+				return 1;
+			}
+			printf("%02d ", rc);
+		}
+		printf("\n");
+	}
+}
diff --git a/fuse3.pc.in b/fuse3.pc.in
new file mode 100644
index 0000000..b59a1f3
--- /dev/null
+++ b/fuse3.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: fuse
+Description: Filesystem in Userspace
+Version: @VERSION@
+Libs: -L${libdir} -lfuse3 -pthread
+Libs.private: @libfuse_libs@
+Cflags: -I${includedir}/fuse3
diff --git a/include/Makefile.am b/include/Makefile.am
new file mode 100644
index 0000000..ffbfafa
--- /dev/null
+++ b/include/Makefile.am
@@ -0,0 +1,12 @@
+## Process this file with automake to produce Makefile.in
+
+fuseincludedir=$(includedir)/fuse3
+
+fuseinclude_HEADERS = \
+	fuse.h			\
+	fuse_common.h		\
+	fuse_lowlevel.h		\
+	fuse_opt.h		\
+	cuse_lowlevel.h
+
+noinst_HEADERS = fuse_kernel.h
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..89798ef
--- /dev/null
+++ b/include/fuse.h
@@ -0,0 +1,1122 @@
+/*
+  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
+ *
+ * @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.
+	 */
+	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.
+	 *
+	 * `fi` will always be NULL if the file is not currenly 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 */
+	int (*rename) (const char *, const char *, unsigned int);
+
+	/** 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 currenly 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 currenly 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 currenly 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);
+
+	/** File open operation
+	 *
+	 * No creation (O_CREAT, O_EXCL) and by default also no
+	 * truncation (O_TRUNC) flags will be passed to open(). If an
+	 * application specifies O_TRUNC, fuse first calls truncate()
+	 * and then open(). Only if 'atomic_o_trunc' has been
+	 * specified and kernel version is 2.6.24 or later, O_TRUNC is
+	 * passed on to open.
+	 *
+	 * Unless the 'default_permissions' mount option is given,
+	 * open should check if the operation is permitted for the
+	 * given flags. Optionally open may also return an arbitrary
+	 * filehandle in the fuse_file_info structure, which will be
+	 * passed to all file operations.
+	 */
+	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.  So if a
+	 * filesystem wants to return write errors in close() and the file
+	 * has cached dirty data, this is a good place to write back data
+	 * and return any errors.  Since many applications ignore close()
+	 * errors this is not always useful.
+	 *
+	 * NOTE: The flush() method may be called more than once for each
+	 * open().	This happens if more than one file descriptor refers
+	 * to an opened file 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 always be called
+	 * after some writes, or that if will be called at all.
+	 */
+	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 descriptor.	 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, closedir 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
+	 * fuse_context to all file operations and as a parameter to the
+	 * destroy() method.
+	 */
+	void *(*init) (struct fuse_conn_info *conn,
+		       struct fuse_config *cfg);
+
+	/**
+	 * Clean up filesystem
+	 *
+	 * Called on filesystem exit.
+	 */
+	void (*destroy) (void *);
+
+	/**
+	 * 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 currenly 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.
+	 */
+	int (*ioctl) (const char *, 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 *);
+};
+
+/** 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;
+
+	/** Thread ID of the calling process */
+	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.
+ *
+ * @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 user_data user data supplied in the context during the init() method
+ * @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 *user_data);
+*/
+#define fuse_main(argc, argv, op, user_data)				\
+	fuse_main_real(argc, argv, op, sizeof(*(op)), user_data)
+
+/* ----------------------------------------------------------- *
+ * More detailed API					       *
+ * ----------------------------------------------------------- */
+
+/**
+ * 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 user_data user data supplied in the context during the init() method
+ * @return the created FUSE handle
+ */
+struct fuse *fuse_new(struct fuse_args *args, const struct fuse_operations *op,
+		      size_t op_size, void *user_data);
+
+/**
+ * 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 clone_fd whether to use separate device fds for each thread
+ *                 (may increase performance)
+ * @return see fuse_session_loop()
+ *
+ * See also: fuse_loop()
+ */
+int fuse_loop_mt(struct fuse *f, int clone_fd);
+
+/**
+ * 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);
+
+/**
+ * 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 *user_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, 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);
+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 user_data user data supplied in the context during the init() method
+ * @return a new filesystem object
+ */
+struct fuse_fs *fuse_fs_new(const struct fuse_operations *op, size_t op_size,
+			    void *user_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);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* FUSE_H_ */
diff --git a/include/fuse_common.h b/include/fuse_common.h
new file mode 100644
index 0000000..7c2e86f
--- /dev/null
+++ b/include/fuse_common.h
@@ -0,0 +1,745 @@
+/*  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 0
+
+#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 open files
+ */
+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
+	    writepage */
+	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, to indicate that currently
+	    cached file data (that the filesystem provided the last
+	    time the file was open) need not be invalidated. */
+	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;
+
+	/** Padding.  Do not use*/
+	unsigned int padding : 27;
+
+	/** File handle.  May be filled in by filesystem in open().
+	    Available in all other file operations */
+	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;
+};
+
+
+
+/**************************************************************************
+ * 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
+ *
+ * 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 zero to
+ * avoid unneccessary 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
+ *
+ * This feature is enabled by default when supported by the kernel and if the
+ * filesystem implements a readdirplus() 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)
+
+/**
+ * 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. Asychronous 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.  A zero (default) value is
+	 * equivalent to 1000000000 (1sec).
+	 */
+	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..42fa977
--- /dev/null
+++ b/include/fuse_kernel.h
@@ -0,0 +1,789 @@
+/*
+    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
+ *
+ * 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 open, 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
+ */
+
+#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 26
+
+/** 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
+ */
+#define FOPEN_DIRECT_IO		(1 << 0)
+#define FOPEN_KEEP_CACHE	(1 << 1)
+#define FOPEN_NONSEEKABLE	(1 << 2)
+
+/**
+ * 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
+ */
+#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)
+
+/**
+ * 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
+ */
+#define FUSE_WRITE_CACHE	(1 << 0)
+#define FUSE_WRITE_LOCKOWNER	(1 << 1)
+
+/**
+ * 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_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_MAX_IOV	256
+
+/**
+ * Poll flags
+ *
+ * FUSE_POLL_SCHEDULE_NOTIFY: request poll notify
+ */
+#define FUSE_POLL_SCHEDULE_NOTIFY (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,
+
+	/* 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;
+	uint32_t	unused[9];
+};
+
+#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;
+};
+
+#endif /* _LINUX_FUSE_H */
diff --git a/include/fuse_lowlevel.h b/include/fuse_lowlevel.h
new file mode 100644
index 0000000..835b420
--- /dev/null
+++ b/include/fuse_lowlevel.h
@@ -0,0 +1,1933 @@
+/*
+  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 30 (recommended for any
+ * new application).
+ */
+
+#ifndef FUSE_USE_VERSION
+#define FUSE_USE_VERSION 30
+#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.
+	 *
+	 * The generation must be non-zero, otherwise FUSE will treat
+	 * it as an error.
+	 *
+	 */
+	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 the attributes */
+	double attr_timeout;
+
+	/** Validity timeout (in seconds) for the name */
+	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
+	 *
+	 * 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, remdir 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.
+	 *
+	 * 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.  Creation (O_CREAT,
+	 * O_EXCL, O_NOCTTY) and by default also truncation (O_TRUNC)
+	 * flags will be filtered out. If an application specifies
+	 * O_TRUNC, fuse first calls truncate() and then open().
+	 *
+	 * If filesystem is able to handle O_TRUNC directly, the
+	 * init() handler should set the `FUSE_CAP_ATOMIC_O_TRUNC` bit
+	 * in ``conn->want``.
+	 *
+	 * 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.
+	 *
+	 * 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.
+	 *
+	 * 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
+	 */
+	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.
+	 *
+	 * 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).
+	 *
+	 * Filesystem may also implement stateless directory I/O and not
+	 * store anything in fi->fh, though that makes it impossible to
+	 * implement standard conforming directory stream operations in
+	 * case the contents of the directory can change between opendir
+	 * and releasedir.
+	 *
+	 * 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.
+	 *
+	 * The function does not have to report the '.' and '..'
+	 * entries, but is allowed to do so.
+	 *
+	 * 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.
+	 *
+	 * 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() 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
+	 *
+	 * 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.
+	 *
+	 * Open flags (with the exception of O_NOCTTY) are available in
+	 * fi->flags.
+	 *
+	 * 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).
+	 *
+	 * 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 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
+	 */
+	void (*ioctl) (fuse_req_t req, fuse_ino_t ino, 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);
+};
+
+/**
+ * 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.
+ *
+ * Note: offsets do not necessarily represent physical offsets, and
+ * could be any marker, that enables the implementation to find a
+ * specific point in the directory stream.
+ *
+ * @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
+ *
+ * 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.
+ *
+ * Note: offsets do not necessarily represent physical offsets, and
+ * could be any marker, that enables the implementation to find a
+ * specific point in the directory stream.
+ *
+ * @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
+ *
+ * @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 don't call this function from a filesystem operation and
+ * don't call it with a lock held that can also be held by a filesystem
+ * operation.
+ *
+ * @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);
+
+/**
+ * As of kernel 4.8, this function behaves like
+ * fuse_lowlevel_notify_inval_entry() with the following additional
+ * effect:
+ *
+ * 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 don't call this function from a filesystem operation and
+ * don't call it with a lock held that can also be held by a filesystem
+ * operation.
+ *
+ * @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.
+ *
+ * @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.
+ *
+ * @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;
+};
+
+/**
+ * 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").
+ *
+ * @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 accidentially 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 clone_fd whether to use separate device fds for each thread
+ *                 (may increase performance)
+ * @return see fuse_session_loop()
+ */
+int fuse_session_loop_mt(struct fuse_session *se, int clone_fd);
+
+/**
+ * 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/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..e7f6fd4
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,43 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/include -DFUSERMOUNT_DIR=\"$(bindir)\" \
+ -D_REENTRANT -DFUSE_USE_VERSION=30
+
+lib_LTLIBRARIES = libfuse3.la
+
+if BSD
+mount_source = mount_bsd.c
+else
+mount_source = mount.c mount_util.c mount_util.h
+endif
+
+if ICONV
+iconv_source = modules/iconv.c
+else
+iconv_source =
+endif
+
+libfuse3_la_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	\
+	$(iconv_source)		\
+	$(mount_source)
+
+libfuse3_la_LDFLAGS = -pthread @libfuse_libs@ -version-number 3:0:0 \
+	-Wl,--version-script,$(srcdir)/fuse_versionscript
+
+if NETBSD
+libfuse3_la_LIBADD = -lperfuse -lpuffs
+endif
+
+EXTRA_DIST = fuse_versionscript
diff --git a/lib/buffer.c b/lib/buffer.c
new file mode 100644
index 0000000..85309ac
--- /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, src->mem + src_off, len,
+				     dst->pos + dst_off);
+		} else {
+			res = write(dst->fd, 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, dst->mem + dst_off, len,
+				     src->pos + src_off);
+		} else {
+			res = read(src->fd, 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) {
+		void *dstmem = dst->mem + dst_off;
+		void *srcmem = 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/cuse_lowlevel.c b/lib/cuse_lowlevel.c
new file mode 100644
index 0000000..b6aaf77
--- /dev/null
+++ b/lib/cuse_lowlevel.c
@@ -0,0 +1,362 @@
+/*
+  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, 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)
+		res = fuse_session_loop_mt(se, 0);
+	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 100644
index 0000000..ab5a593
--- /dev/null
+++ b/lib/fuse.c
@@ -0,0 +1,4798 @@
+/*
+  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;
+	int allocated;
+	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;
+extern fuse_module_factory_t fuse_module_iconv_factory;
+
+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 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);
+	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);
+	assert(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)
+					*mp = (*mp)->next;
+				else
+					mp = &(*mp)->next;
+			}
+			dlclose(so->handle);
+			free(so);
+		}
+	}
+	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 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);
+}
+
+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);
+	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);
+	}
+	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++)
+			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) > (int) 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)
+{
+	int res;
+	struct fuse_bufvec *buf = NULL;
+
+	res = fuse_fs_read_buf(fs, 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);
+
+	return res;
+}
+
+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, 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;
+}
+
+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);
+	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->first) {
+			dh->error = -EIO;
+			return 1;
+		}
+
+		if (extend_contents(dh, dh->needlen) == -1)
+			return 1;
+
+		dh->filled = 0;
+		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 {
+		if (!dh->filled) {
+			dh->error = -EIO;
+			return 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->first) {
+			dh->error = -EIO;
+			return 1;
+		}
+		if (extend_contents(dh, dh->needlen) == -1)
+			return 1;
+
+		dh->filled = 0;
+		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 {
+		if (!dh->filled) {
+			dh->error = -EIO;
+			return 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 = 1;
+		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, 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)
+		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 ?: (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 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,
+};
+
+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);
+}
+
+int fuse_loop_mt(struct fuse *f, int clone_fd)
+{
+	if (f == NULL)
+		return -1;
+
+	int res = fuse_start_cleanup_thread(f);
+	if (res)
+		return -1;
+
+	res = fuse_session_loop_mt(fuse_get_session(f), clone_fd);
+	fuse_stop_cleanup_thread(f);
+	return res;
+}
+
+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;
+}
+
+#define FUSE_LIB_OPT(t, p, v) { t, offsetof(struct fuse_config, p), v }
+
+static const struct fuse_opt fuse_lib_opts[] = {
+	FUSE_LIB_OPT("-h",		      show_help, 1),
+	FUSE_LIB_OPT("--help",		      show_help, 1),
+	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 void fuse_lib_help(void)
+{
+	/* 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");
+}
+
+static void fuse_lib_help_modules(void)
+{
+	struct fuse_module *m;
+	printf("\nModule options:\n");
+	pthread_mutex_lock(&fuse_context_lock);
+	for (m = fuse_modules; m; m = m->next) {
+		struct fuse_fs *fs = NULL;
+		struct fuse_fs *newfs;
+		struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
+		if (fuse_opt_add_arg(&args, "") != -1 &&
+		    fuse_opt_add_arg(&args, "-h") != -1) {
+			printf("\n[%s]\n", m->name);
+			newfs = m->factory(&args, &fs);
+			assert(newfs == NULL);
+		}
+		fuse_opt_free_args(&args);
+	}
+	pthread_mutex_unlock(&fuse_context_lock);
+}
+
+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 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);
+	}
+}
+
+struct fuse *fuse_new(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;
+	}
+
+	/* Parse options */
+	if (fuse_opt_parse(args, &f->conf, fuse_lib_opts,
+			   fuse_lib_opt_proc) == -1)
+		goto out_free;
+
+	if (f->conf.show_help) {
+		fuse_lib_help();
+		fuse_lowlevel_help();
+		/* Defer printing module help until modules
+		   have been loaded */
+	}
+
+	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);
+		fuse_register_module("iconv", fuse_module_iconv_factory, NULL);
+		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->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;
+
+	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.show_help) {
+		fuse_lib_help_modules();
+		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;
+}
+
+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));
+
+	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) {
+	return 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..54466f6
--- /dev/null
+++ b/lib/fuse_i.h
@@ -0,0 +1,127 @@
+/*
+  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);
diff --git a/lib/fuse_loop.c b/lib/fuse_loop.c
new file mode 100644
index 0000000..c847bd8
--- /dev/null
+++ b/lib/fuse_loop.c
@@ -0,0 +1,42 @@
+/*
+  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(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..42d3e03
--- /dev/null
+++ b/lib/fuse_loop_mt.c
@@ -0,0 +1,346 @@
+/*
+  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;
+	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;
+};
+
+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 = -1;
+			}
+			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 > 10) {
+			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);
+}
+
+int fuse_session_loop_mt(struct fuse_session *se, int clone_fd)
+{
+	int err;
+	struct fuse_mt mt;
+	struct fuse_worker *w;
+
+	memset(&mt, 0, sizeof(struct fuse_mt));
+	mt.se = se;
+	mt.clone_fd = clone_fd;
+	mt.error = 0;
+	mt.numworker = 0;
+	mt.numavail = 0;
+	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;
+}
diff --git a/lib/fuse_lowlevel.c b/lib/fuse_lowlevel.c
new file mode 100644
index 0000000..a960123
--- /dev/null
+++ b/lib/fuse_lowlevel.c
@@ -0,0 +1,2994 @@
+/*
+  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 <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 & 0170000) >> 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 & 0170000) >> 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->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 & 1) != 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 & 1;
+
+	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;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+
+	if (req->se->op.fsync)
+		req->se->op.fsync(req, nodeid, arg->fsync_flags & 1, &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;
+
+	memset(&fi, 0, sizeof(fi));
+	fi.fh = arg->fh;
+
+	if (req->se->op.fsyncdir)
+		req->se->op.fsyncdir(req, nodeid, arg->fsync_flags & 1, &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_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;
+	} 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, FUSE_CAP_READDIRPLUS_AUTO);
+
+	if (bufsize < FUSE_MIN_READ_BUFFER) {
+		fprintf(stderr, "fuse: 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);
+
+	if (se->conn.want & (~se->conn.capable)) {
+		fprintf(stderr, "fuse: error: filesystem requested capabilites "
+			"that are not supported by kernel, aborting.\n");
+		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;
+	}
+
+	/* 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;
+	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;
+
+	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;
+
+	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_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_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_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"    },
+	[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)
+{
+	int res = fuse_buf_copy(dst, src, 0);
+	if (res < 0) {
+		fprintf(stderr, "fuse: copy from pipe: %s\n", strerror(-res));
+		return res;
+	}
+	if (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 = 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;
+}
+
+#define MIN_BUFSIZE 0x21000
+
+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 = getpagesize() + 0x1000;
+	se->bufsize = se->bufsize < MIN_BUFSIZE ? MIN_BUFSIZE : se->bufsize;
+
+	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);
+
+	/* 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)
+{
+	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 == -1) {
+		ret = -EIO;
+		goto out_free;
+	}
+
+	if (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[])
+{
+	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..1839a67
--- /dev/null
+++ b/lib/fuse_misc.h
@@ -0,0 +1,48 @@
+/*
+  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>
+
+#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..3d4a3dd
--- /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 = 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..a1bf1d5
--- /dev/null
+++ b/lib/fuse_signals.c
@@ -0,0 +1,82 @@
+/*
+  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)
+{
+	(void) 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 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 (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, SIG_IGN, 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, SIG_IGN, 1);
+}
diff --git a/lib/fuse_versionscript b/lib/fuse_versionscript
new file mode 100644
index 0000000..1edaf5e
--- /dev/null
+++ b/lib/fuse_versionscript
@@ -0,0 +1,139 @@
+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_fgetattr;
+		fuse_fs_flush;
+		fuse_fs_fsync;
+		fuse_fs_fsyncdir;
+		fuse_fs_ftruncate;
+		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_register_module;
+		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:
+		*;
+};
+
+# Local Variables:
+# indent-tabs-mode: t
+# End:
diff --git a/lib/helper.c b/lib/helper.c
new file mode 100644
index 0000000..31640b6
--- /dev/null
+++ b/lib/helper.c
@@ -0,0 +1,400 @@
+/*
+  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 <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_HELPER_OPT("subtype=",	nodefault_subtype),
+	FUSE_OPT_KEY("fsname=",		FUSE_OPT_KEY_KEEP),
+	FUSE_OPT_KEY("subtype=",	FUSE_OPT_KEY_KEEP),
+	FUSE_HELPER_OPT("clone_fd",	clone_fd),
+	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");
+}
+
+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) {
+			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;
+	}
+}
+
+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;
+	}
+	sprintf(subtype_opt, "-osubtype=%s", basename);
+	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));
+	if (fuse_opt_parse(args, opts, fuse_helper_opts,
+			   fuse_helper_opt_proc) == -1)
+		return -1;
+
+	/* If neither -o subtype nor -o fsname are specified,
+	   set subtype 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 initializatation */
+		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;
+	}
+
+	/* Re-add --help for later processing by fuse_new()
+	   (that way we also get help for modules options) */
+	if (opts.show_help) {
+		if(args.argv[0] != '\0')
+			printf("usage: %s [options] <mountpoint>\n\n",
+			       args.argv[0]);
+		printf("FUSE options:\n");
+		fuse_cmdline_help();
+		if (fuse_opt_add_arg(&args, "--help") == -1) {
+			res = 1;
+			goto out1;
+		}
+	}
+
+	if (!opts.show_help &&
+	    !opts.mountpoint) {
+		fprintf(stderr, "error: no mountpoint specified\n");
+		res = 1;
+		goto out1;
+	}
+
+
+	/* --help is processed here and will result in NULL */
+	fuse = fuse_new(&args, op, op_size, user_data);
+	if (fuse == NULL) {
+		res = opts.show_help ? 0 : 1;
+		goto out1;
+	}
+
+	if (fuse_mount(fuse,opts.mountpoint) != 0) {
+		res = 1;
+		goto out2;
+	}
+
+	if (fuse_daemonize(opts.foreground) != 0) {
+		res = 1;
+		goto out3;
+	}
+
+	struct fuse_session *se = fuse_get_session(fuse);
+	if (fuse_set_signal_handlers(se) != 0) {
+		res = 1;
+		goto out3;
+	}
+
+	if (opts.singlethread)
+		res = fuse_loop(fuse);
+	else
+		res = fuse_loop_mt(fuse, opts.clone_fd);
+	if (res)
+		res = 1;
+
+	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;
+}
diff --git a/lib/modules/iconv.c b/lib/modules/iconv.c
new file mode 100644
index 0000000..5d1e959
--- /dev/null
+++ b/lib/modules/iconv.c
@@ -0,0 +1,720 @@
+/*
+  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
+*/
+
+#define FUSE_USE_VERSION 30
+
+#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..9478e4e
--- /dev/null
+++ b/lib/modules/subdir.c
@@ -0,0 +1,677 @@
+/*
+  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
+*/
+
+#define FUSE_USE_VERSION 30
+
+#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..2f7e068
--- /dev/null
+++ b/lib/mount.c
@@ -0,0 +1,604 @@
+/*
+  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 <sys/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 __NetBSD__
+#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 */
+#endif /* __NetBSD__ */
+	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..f0eea6f
--- /dev/null
+++ b/lib/mount_bsd.c
@@ -0,0 +1,325 @@
+/*
+  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/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),
+	/* 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)
+{
+	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;
+}
+
+static void do_unmount(char *dev, int fd)
+{
+	char device_path[SPECNAMELEN + 12];
+	const char *argv[4];
+	const char umount_cmd[] = "/sbin/umount";
+	pid_t pid;
+
+	snprintf(device_path, SPECNAMELEN + 12, _PATH_DEV "%s", dev);
+
+	argv[0] = umount_cmd;
+	argv[1] = "-f";
+	argv[2] = device_path;
+	argv[3] = NULL;
+
+	pid = fork();
+
+	if (pid == -1)
+		return;
+
+	if (pid == 0) {
+		close(fd);
+		execvp(umount_cmd, (char **)argv);
+		exit(1);
+	}
+
+	waitpid(pid, NULL, 0);
+}
+
+void fuse_kern_unmount(const char *mountpoint, int fd)
+{
+	char *ep, dev[128];
+	struct stat sbuf;
+
+	(void)mountpoint;
+
+	if (fstat(fd, &sbuf) == -1)
+		goto out;
+
+	devname_r(sbuf.st_rdev, S_IFCHR, dev, 128);
+
+	if (strncmp(dev, "fuse", 4))
+		goto out;
+
+	strtol(dev + 4, &ep, 10);
+	if (*ep != '\0')
+		goto out;
+
+	do_unmount(dev, fd);
+
+out:
+	close(fd);
+}
+
+/* Check if kernel is doing init in background */
+static int init_backgrounded(void)
+{
+	unsigned ibg, 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;
+
+			if (! fdnam && asprintf(&fdnam, "%d", fd) == -1) {
+				perror("fuse: failed to assemble mount arguments");
+				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");
+			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..8b64ca2
--- /dev/null
+++ b/lib/mount_util.c
@@ -0,0 +1,351 @@
+/*
+  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>
+#ifndef __NetBSD__
+#include <mntent.h>
+#endif
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+
+#ifdef __NetBSD__
+#define umount2(mnt, flags) unmount(mnt, (flags == 2) ? MNT_FORCE : 0)
+#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 /* __NetBSD__ */
+
+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;
+}
diff --git a/lib/mount_util.h b/lib/mount_util.h
new file mode 100644
index 0000000..55c6c5e
--- /dev/null
+++ b/lib/mount_util.h
@@ -0,0 +1,17 @@
+/*
+  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);
diff --git a/makeconf.sh b/makeconf.sh
new file mode 100755
index 0000000..3388390
--- /dev/null
+++ b/makeconf.sh
@@ -0,0 +1,26 @@
+#! /bin/sh
+
+echo "Running libtoolize..."
+libtoolize -c
+
+# We use iconv directly rather than via gettext, so
+# we need to manually copy config.rpath.
+CONFIG_RPATH=/usr/share/gettext/config.rpath
+if ! [ -f $CONFIG_RPATH ]; then
+    CONFIG_RPATH=/usr/local/share/gettext/config.rpath
+fi
+if ! [ -f $CONFIG_RPATH ]; then
+    if  [ -f config.rpath ]; then
+        CONFIG_RPATH=
+    else
+        echo "config.rpath not found! - is gettext installed?" >&2
+        exit 1
+    fi
+fi
+if ! [ -z "$CONFIG_RPATH" ]; then
+    cp "$CONFIG_RPATH" .
+fi
+
+echo "Running autoreconf..."
+autoreconf -i
+
diff --git a/test/Makefile.am b/test/Makefile.am
new file mode 100644
index 0000000..9eb0a73
--- /dev/null
+++ b/test/Makefile.am
@@ -0,0 +1,7 @@
+## Process this file with automake to produce Makefile.in
+
+AM_CPPFLAGS = -I$(top_srcdir)/include -D_REENTRANT
+noinst_PROGRAMS = test test_write_cache test_setattr
+
+test_write_cache_LDADD = ../lib/libfuse3.la
+test_setattr_LDADD = ../lib/libfuse3.la
diff --git a/test/conftest.py b/test/conftest.py
new file mode 100644
index 0000000..70cd0c6
--- /dev/null
+++ b/test/conftest.py
@@ -0,0 +1,89 @@
+import sys
+import pytest
+import time
+import re
+
+# If a test fails, wait a moment before retrieving the captured
+# stdout/stderr. When using a server process, this makes sure that we capture
+# any potential output of the server that comes *after* a test has failed. For
+# example, if a request handler raises an exception, the server first signals an
+# error to FUSE (causing the test to fail), and then logs the exception. Without
+# the extra delay, the exception will go into nowhere.
+@pytest.mark.hookwrapper
+def pytest_pyfunc_call(pyfuncitem):
+    outcome = yield
+    failed = outcome.excinfo is not None
+    if failed:
+        time.sleep(1)
+
+@pytest.fixture()
+def pass_capfd(request, capfd):
+    '''Provide capfd object to UnitTest instances'''
+    request.instance.capfd = capfd
+
+def check_test_output(capfd):
+    (stdout, stderr) = capfd.readouterr()
+
+    # Write back what we've read (so that it will still be printed.
+    sys.stdout.write(stdout)
+    sys.stderr.write(stderr)
+
+    # Strip out false positives
+    for (pattern, flags, count) in capfd.false_positives:
+        cp = re.compile(pattern, flags)
+        (stdout, cnt) = cp.subn('', stdout, count=count)
+        if count == 0 or count - cnt > 0:
+            stderr = cp.sub('', stderr, count=count - cnt)
+
+    patterns = [ r'\b{}\b'.format(x) for x in
+                 ('exception', 'error', 'warning', 'fatal', 'traceback',
+                    'fault', 'crash(?:ed)?', 'abort(?:ed)',
+                    'uninitiali[zs]ed') ]
+    patterns += ['^==[0-9]+== ']
+    for pattern in patterns:
+        cp = re.compile(pattern, re.IGNORECASE | re.MULTILINE)
+        hit = cp.search(stderr)
+        if hit:
+            raise AssertionError('Suspicious output to stderr (matched "%s")' % hit.group(0))
+        hit = cp.search(stdout)
+        if hit:
+            raise AssertionError('Suspicious output to stdout (matched "%s")' % hit.group(0))
+
+def register_output(self, pattern, count=1, flags=re.MULTILINE):
+    '''Register *pattern* as false positive for output checking
+
+    This prevents the test from failing because the output otherwise
+    appears suspicious.
+    '''
+
+    self.false_positives.append((pattern, flags, count))
+
+# This is a terrible hack that allows us to access the fixtures from the
+# pytest_runtest_call hook. Among a lot of other hidden assumptions, it probably
+# relies on tests running sequential (i.e., don't dare to use e.g. the xdist
+# plugin)
+current_capfd = None
+@pytest.yield_fixture(autouse=True)
+def save_cap_fixtures(request, capfd):
+    global current_capfd
+    capfd.false_positives = []
+
+    # Monkeypatch in a function to register false positives
+    type(capfd).register_output = register_output
+
+    if request.config.getoption('capture') == 'no':
+        capfd = None
+    current_capfd = capfd
+    bak = current_capfd
+    yield
+
+    # Try to catch problems with this hack (e.g. when running tests
+    # simultaneously)
+    assert bak is current_capfd
+    current_capfd = None
+
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_call(item):
+    capfd = current_capfd
+    if capfd is not None:
+        check_test_output(capfd)
diff --git a/test/pytest.ini b/test/pytest.ini
new file mode 100644
index 0000000..bc4af36
--- /dev/null
+++ b/test/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+addopts = --verbose --assert=rewrite --tb=native -x
diff --git a/test/stracedecode.c b/test/stracedecode.c
new file mode 100644
index 0000000..940438a
--- /dev/null
+++ b/test/stracedecode.c
@@ -0,0 +1,198 @@
+#include <stdio.h>
+#include <string.h>
+#include "fuse_kernel.h"
+
+static struct {
+	const char *name;
+} fuse_ll_ops[] = {
+	[FUSE_LOOKUP]	   = { "LOOKUP"	     },
+	[FUSE_FORGET]	   = { "FORGET"	     },
+	[FUSE_GETATTR]	   = { "GETATTR"     },
+	[FUSE_SETATTR]	   = { "SETATTR"     },
+	[FUSE_READLINK]	   = { "READLINK"    },
+	[FUSE_SYMLINK]	   = { "SYMLINK"     },
+	[FUSE_MKNOD]	   = { "MKNOD"	     },
+	[FUSE_MKDIR]	   = { "MKDIR"	     },
+	[FUSE_UNLINK]	   = { "UNLINK"	     },
+	[FUSE_RMDIR]	   = { "RMDIR"	     },
+	[FUSE_RENAME]	   = { "RENAME"	     },
+	[FUSE_LINK]	   = { "LINK"	     },
+	[FUSE_OPEN]	   = { "OPEN"	     },
+	[FUSE_READ]	   = { "READ"	     },
+	[FUSE_WRITE]	   = { "WRITE"	     },
+	[FUSE_STATFS]	   = { "STATFS"	     },
+	[FUSE_RELEASE]	   = { "RELEASE"     },
+	[FUSE_FSYNC]	   = { "FSYNC"	     },
+	[FUSE_SETXATTR]	   = { "SETXATTR"    },
+	[FUSE_GETXATTR]	   = { "GETXATTR"    },
+	[FUSE_LISTXATTR]   = { "LISTXATTR"   },
+	[FUSE_REMOVEXATTR] = { "REMOVEXATTR" },
+	[FUSE_FLUSH]	   = { "FLUSH"	     },
+	[FUSE_INIT]	   = { "INIT"	     },
+	[FUSE_OPENDIR]	   = { "OPENDIR"     },
+	[FUSE_READDIR]	   = { "READDIR"     },
+	[FUSE_RELEASEDIR]  = { "RELEASEDIR"  },
+	[FUSE_FSYNCDIR]	   = { "FSYNCDIR"    },
+	[FUSE_GETLK]	   = { "GETLK"	     },
+	[FUSE_SETLK]	   = { "SETLK"	     },
+	[FUSE_SETLKW]	   = { "SETLKW"	     },
+	[FUSE_ACCESS]	   = { "ACCESS"	     },
+	[FUSE_CREATE]	   = { "CREATE"	     },
+	[FUSE_INTERRUPT]   = { "INTERRUPT"   },
+	[FUSE_BMAP]	   = { "BMAP"	     },
+	[FUSE_DESTROY]	   = { "DESTROY"     },
+	[FUSE_READDIRPLUS] = { "READDIRPLUS" },
+};
+
+#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 void process_buf(int dir, char *buf, int len)
+{
+	static unsigned long long prevuniq = -1;
+	static int prevopcode;
+
+	if (!dir) {
+		struct fuse_in_header *in = (struct fuse_in_header *) buf;
+		buf += sizeof(struct fuse_in_header);
+
+		printf("unique: %llu, opcode: %s (%i), nodeid: %lu, len: %i, insize: %i\n",
+		       (unsigned long long) in->unique,
+		       opname((enum fuse_opcode) in->opcode), in->opcode,
+		       (unsigned long) in->nodeid, in->len, len);
+
+		switch (in->opcode) {
+		case FUSE_READ: {
+			struct fuse_read_in *arg = (struct fuse_read_in *) buf;
+			printf("-READ fh:%llu off:%llu siz:%u rfl:%u own:%llu fl:%u\n",
+			       arg->fh, arg->offset, arg->size, arg->read_flags,
+			       arg->lock_owner, arg->flags);
+			break;
+		}
+		case FUSE_WRITE: {
+			struct fuse_write_in *arg = (struct fuse_write_in *) buf;
+			printf("-WRITE fh:%llu off:%llu siz:%u wfl:%u own:%llu fl:%u\n",
+			       arg->fh, arg->offset, arg->size, arg->write_flags,
+			       arg->lock_owner, arg->flags);
+			break;
+		}
+		}
+		prevuniq = in->unique;
+		prevopcode = in->opcode;
+	} else {
+		struct fuse_out_header *out = (struct fuse_out_header *) buf;
+		buf += sizeof(struct fuse_out_header);
+
+		printf("   unique: %llu, error: %i (%s), len: %i, outsize: %i\n",
+		       (unsigned long long) out->unique, out->error,
+		       strerror(-out->error), out->len, len);
+
+		if (out->unique == prevuniq) {
+			switch (prevopcode) {
+			case FUSE_GETATTR: {
+				struct fuse_attr_out *arg = (struct fuse_attr_out *) buf;
+				printf("+ATTR v:%llu.%09u i:%llu s:%llu b:%llu\n",
+				       arg->attr_valid, arg->attr_valid_nsec,
+				       arg->attr.ino, arg->attr.size, arg->attr.blocks);
+				break;
+			}
+			case FUSE_LOOKUP: {
+				struct fuse_entry_out *arg = (struct fuse_entry_out *) buf;
+				printf("+ENTRY nodeid:%llu v:%llu.%09u i:%llu s:%llu b:%llu\n",
+				       arg->nodeid, arg->attr_valid, arg->attr_valid_nsec,
+				       arg->attr.ino, arg->attr.size, arg->attr.blocks);
+				break;
+			}
+			}
+		}
+	}
+
+}
+
+int main(void)
+{
+	FILE *in = stdin;
+	while (1) {
+		int dir;
+		int res;
+		char buf[1048576];
+		unsigned len = 0;
+
+		memset(buf, 0, sizeof(buf));
+		while (1) {
+			char str[32];
+
+			res = fscanf(in, "%30s", str);
+			if (res != 1 && feof(in))
+				return 0;
+
+			if (res == 0)
+				continue;
+
+			if (strncmp(str, "read(", 5) == 0) {
+				dir = 0;
+				break;
+			} else if (strncmp(str, "writev(", 7) == 0) {
+				dir = 1;
+				break;
+			}
+		}
+
+		while (1) {
+			int c = getc(in);
+			if (c == '"') {
+				while (1) {
+					int val;
+
+					c = getc(in);
+					if (c == EOF) {
+						fprintf(stderr, "eof in string\n");
+						break;
+					}
+					if (c == '\n') {
+						fprintf(stderr, "eol in string\n");
+						break;
+					}
+					if (c == '"')
+						break;
+					if (c != '\\') {
+						val = c;
+					} else {
+						c = getc(in);
+						switch (c) {
+						case 'n': val = '\n'; break;
+						case 'r': val = '\r'; break;
+						case 't': val = '\t'; break;
+						case '"': val = '"'; break;
+						case '\\': val = '\\'; break;
+						case 'x':
+							res = scanf("%x", &val);
+							if (res != 1) {
+								fprintf(stderr, "parse error\n");
+								continue;
+							}
+							break;
+						default:
+							fprintf(stderr, "unknown sequence: '\\%c'\n", c);
+							continue;
+						}
+					}
+					buf[len++] = val;
+				}
+			}
+			if (c == '\n')
+				break;
+		}
+		process_buf(dir, buf, len);
+		memset(buf, 0, len);
+		len = 0;
+	}
+}
diff --git a/test/test.c b/test/test.c
new file mode 100644
index 0000000..281f218
--- /dev/null
+++ b/test/test.c
@@ -0,0 +1,1522 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <utime.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+static char testfile[1024];
+static char testfile2[1024];
+static char testdir[1024];
+static char testdir2[1024];
+static char subfile[1024];
+
+static char testfile_r[1024];
+static char testfile2_r[1024];
+static char testdir_r[1024];
+static char testdir2_r[1024];
+static char subfile_r[1024];
+
+static char testname[256];
+static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
+static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
+static const char *testdir_files[] = { "f1", "f2", NULL};
+static char zerodata[4096];
+static int testdatalen = sizeof(testdata) - 1;
+static int testdata2len = sizeof(testdata2) - 1;
+static unsigned int testnum = 1;
+static unsigned int select_test = 0;
+static unsigned int skip_test = 0;
+
+#define MAX_ENTRIES 1024
+
+static void test_perror(const char *func, const char *msg)
+{
+	fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
+		strerror(errno));
+}
+
+static void test_error(const char *func, const char *msg, ...)
+	__attribute__ ((format (printf, 2, 3)));
+
+static void __start_test(const char *fmt, ...)
+	__attribute__ ((format (printf, 1, 2)));
+
+static void test_error(const char *func, const char *msg, ...)
+{
+	va_list ap;
+	fprintf(stderr, "%s %s() - ", testname, func);
+	va_start(ap, msg);
+	vfprintf(stderr, msg, ap);
+	va_end(ap);
+	fprintf(stderr, "\n");
+}
+
+static void success(void)
+{
+	fprintf(stderr, "%s OK\n", testname);
+}
+
+static void __start_test(const char *fmt, ...)
+{
+	unsigned int n;
+	va_list ap;
+	n = sprintf(testname, "%3i [", testnum++);
+	va_start(ap, fmt);
+	n += vsprintf(testname + n, fmt, ap);
+	va_end(ap);
+	sprintf(testname + n, "]");
+}
+
+#define start_test(msg, args...) { \
+	if ((select_test && testnum != select_test) || \
+	    (testnum == skip_test)) { \
+		testnum++; \
+		return 0; \
+	} \
+	__start_test(msg, ##args);		\
+}
+
+#define PERROR(msg) test_perror(__FUNCTION__, msg)
+#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
+
+static int check_size(const char *path, int len)
+{
+	struct stat stbuf;
+	int res = stat(path, &stbuf);
+	if (res == -1) {
+		PERROR("stat");
+		return -1;
+	}
+	if (stbuf.st_size != len) {
+		ERROR("length %u instead of %u", (int) stbuf.st_size,
+		      (int) len);
+		return -1;
+	}
+	return 0;
+}
+
+static int fcheck_size(int fd, int len)
+{
+	struct stat stbuf;
+	int res = fstat(fd, &stbuf);
+	if (res == -1) {
+		PERROR("fstat");
+		return -1;
+	}
+	if (stbuf.st_size != len) {
+		ERROR("length %u instead of %u", (int) stbuf.st_size,
+		      (int) len);
+		return -1;
+	}
+	return 0;
+}
+
+static int check_type(const char *path, mode_t type)
+{
+	struct stat stbuf;
+	int res = lstat(path, &stbuf);
+	if (res == -1) {
+		PERROR("lstat");
+		return -1;
+	}
+	if ((stbuf.st_mode & S_IFMT) != type) {
+		ERROR("type 0%o instead of 0%o", stbuf.st_mode & S_IFMT, type);
+		return -1;
+	}
+	return 0;
+}
+
+static int fcheck_type(int fd, mode_t type)
+{
+	struct stat stbuf;
+	int res = fstat(fd, &stbuf);
+	if (res == -1) {
+		PERROR("fstat");
+		return -1;
+	}
+	if ((stbuf.st_mode & S_IFMT) != type) {
+		ERROR("type 0%o instead of 0%o", stbuf.st_mode & S_IFMT, type);
+		return -1;
+	}
+	return 0;
+}
+
+static int check_mode(const char *path, mode_t mode)
+{
+	struct stat stbuf;
+	int res = lstat(path, &stbuf);
+	if (res == -1) {
+		PERROR("lstat");
+		return -1;
+	}
+	if ((stbuf.st_mode & 07777) != mode) {
+		ERROR("mode 0%o instead of 0%o", stbuf.st_mode & 07777, mode);
+		return -1;
+	}
+	return 0;
+}
+
+static int fcheck_mode(int fd, mode_t mode)
+{
+	struct stat stbuf;
+	int res = fstat(fd, &stbuf);
+	if (res == -1) {
+		PERROR("fstat");
+		return -1;
+	}
+	if ((stbuf.st_mode & 07777) != mode) {
+		ERROR("mode 0%o instead of 0%o", stbuf.st_mode & 07777, mode);
+		return -1;
+	}
+	return 0;
+}
+
+static int check_times(const char *path, time_t atime, time_t mtime)
+{
+	int err = 0;
+	struct stat stbuf;
+	int res = lstat(path, &stbuf);
+	if (res == -1) {
+		PERROR("lstat");
+		return -1;
+	}
+	if (stbuf.st_atime != atime) {
+		ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+		err--;
+	}
+	if (stbuf.st_mtime != mtime) {
+		ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+		err--;
+	}
+	if (err)
+		return -1;
+
+	return 0;
+}
+
+#if 0
+static int fcheck_times(int fd, time_t atime, time_t mtime)
+{
+	int err = 0;
+	struct stat stbuf;
+	int res = fstat(fd, &stbuf);
+	if (res == -1) {
+		PERROR("fstat");
+		return -1;
+	}
+	if (stbuf.st_atime != atime) {
+		ERROR("atime %li instead of %li", stbuf.st_atime, atime);
+		err--;
+	}
+	if (stbuf.st_mtime != mtime) {
+		ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
+		err--;
+	}
+	if (err)
+		return -1;
+
+	return 0;
+}
+#endif
+
+static int check_nlink(const char *path, nlink_t nlink)
+{
+	struct stat stbuf;
+	int res = lstat(path, &stbuf);
+	if (res == -1) {
+		PERROR("lstat");
+		return -1;
+	}
+	if (stbuf.st_nlink != nlink) {
+		ERROR("nlink %li instead of %li", (long) stbuf.st_nlink,
+		      (long) nlink);
+		return -1;
+	}
+	return 0;
+}
+
+static int fcheck_nlink(int fd, nlink_t nlink)
+{
+	struct stat stbuf;
+	int res = fstat(fd, &stbuf);
+	if (res == -1) {
+		PERROR("fstat");
+		return -1;
+	}
+	if (stbuf.st_nlink != nlink) {
+		ERROR("nlink %li instead of %li", (long) stbuf.st_nlink,
+		      (long) nlink);
+		return -1;
+	}
+	return 0;
+}
+
+static int check_nonexist(const char *path)
+{
+	struct stat stbuf;
+	int res = lstat(path, &stbuf);
+	if (res == 0) {
+		ERROR("file should not exist");
+		return -1;
+	}
+	if (errno != ENOENT) {
+		ERROR("file should not exist: %s", strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+static int check_buffer(const char *buf, const char *data, unsigned len)
+{
+	if (memcmp(buf, data, len) != 0) {
+		ERROR("data mismatch");
+		return -1;
+	}
+	return 0;
+}
+
+static int check_data(const char *path, const char *data, int offset,
+		      unsigned len)
+{
+	char buf[4096];
+	int res;
+	int fd = open(path, O_RDONLY);
+	if (fd == -1) {
+		PERROR("open");
+		return -1;
+	}
+	if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+		PERROR("lseek");
+		close(fd);
+		return -1;
+	}
+	while (len) {
+		int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+		res = read(fd, buf, rdlen);
+		if (res == -1) {
+			PERROR("read");
+			close(fd);
+			return -1;
+		}
+		if (res != rdlen) {
+			ERROR("short read: %u instead of %u", res, rdlen);
+			close(fd);
+			return -1;
+		}
+		if (check_buffer(buf, data, rdlen) != 0) {
+			close(fd);
+			return -1;
+		}
+		data += rdlen;
+		len -= rdlen;
+	}
+	res = close(fd);
+	if (res == -1) {
+		PERROR("close");
+		return -1;
+	}
+	return 0;
+}
+
+static int fcheck_data(int fd, const char *data, int offset,
+		       unsigned len)
+{
+	char buf[4096];
+	int res;
+	if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
+		PERROR("lseek");
+		return -1;
+	}
+	while (len) {
+		int rdlen = len < sizeof(buf) ? len : sizeof(buf);
+		res = read(fd, buf, rdlen);
+		if (res == -1) {
+			PERROR("read");
+			return -1;
+		}
+		if (res != rdlen) {
+			ERROR("short read: %u instead of %u", res, rdlen);
+			return -1;
+		}
+		if (check_buffer(buf, data, rdlen) != 0) {
+			return -1;
+		}
+		data += rdlen;
+		len -= rdlen;
+	}
+	return 0;
+}
+
+static int check_dir_contents(const char *path, const char **contents)
+{
+	int i;
+	int res;
+	int err = 0;
+	int found[MAX_ENTRIES];
+	const char *cont[MAX_ENTRIES];
+	DIR *dp;
+
+	for (i = 0; contents[i]; i++) {
+		assert(i < MAX_ENTRIES - 3);
+		found[i] = 0;
+		cont[i] = contents[i];
+	}
+	found[i] = 0;
+	cont[i++] = ".";
+	found[i] = 0;
+	cont[i++] = "..";
+	cont[i] = NULL;
+
+	dp = opendir(path);
+	if (dp == NULL) {
+		PERROR("opendir");
+		return -1;
+	}
+	memset(found, 0, sizeof(found));
+	while(1) {
+		struct dirent *de;
+		errno = 0;
+		de = readdir(dp);
+		if (de == NULL) {
+			if (errno) {
+				PERROR("readdir");
+				closedir(dp);
+				return -1;
+			}
+			break;
+		}
+		for (i = 0; cont[i] != NULL; i++) {
+			assert(i < MAX_ENTRIES);
+			if (strcmp(cont[i], de->d_name) == 0) {
+				if (found[i]) {
+					ERROR("duplicate entry <%s>",
+					      de->d_name);
+					err--;
+				} else
+					found[i] = 1;
+				break;
+			}
+		}
+		if (!cont[i]) {
+			ERROR("unexpected entry <%s>", de->d_name);
+			err --;
+		}
+	}
+	for (i = 0; cont[i] != NULL; i++) {
+		if (!found[i]) {
+			ERROR("missing entry <%s>", cont[i]);
+			err--;
+		}
+	}
+	res = closedir(dp);
+	if (res == -1) {
+		PERROR("closedir");
+		return -1;
+	}
+	if (err)
+		return -1;
+
+	return 0;
+}
+
+static int create_file(const char *path, const char *data, int len)
+{
+	int res;
+	int fd;
+
+	unlink(path);
+	fd = creat(path, 0644);
+	if (fd == -1) {
+		PERROR("creat");
+		return -1;
+	}
+	if (len) {
+		res = write(fd, data, len);
+		if (res == -1) {
+			PERROR("write");
+			close(fd);
+			return -1;
+		}
+		if (res != len) {
+			ERROR("write is short: %u instead of %u", res, len);
+			close(fd);
+			return -1;
+		}
+	}
+	res = close(fd);
+	if (res == -1) {
+		PERROR("close");
+		return -1;
+	}
+	res = check_type(path, S_IFREG);
+	if (res == -1)
+		return -1;
+	res = check_mode(path, 0644);
+	if (res == -1)
+		return -1;
+	res = check_nlink(path, 1);
+	if (res == -1)
+		return -1;
+	res = check_size(path, len);
+	if (res == -1)
+		return -1;
+
+	if (len) {
+		res = check_data(path, data, 0, len);
+		if (res == -1)
+			return -1;
+	}
+
+	return 0;
+}
+
+static int cleanup_dir(const char *path, const char **dir_files, int quiet)
+{
+	int i;
+	int err = 0;
+
+	for (i = 0; dir_files[i]; i++) {
+		int res;
+		char fpath[1024];
+		sprintf(fpath, "%s/%s", path, dir_files[i]);
+		res = unlink(fpath);
+		if (res == -1 && !quiet) {
+			PERROR("unlink");
+			err --;
+		}
+	}
+	if (err)
+		return -1;
+
+	return 0;
+}
+
+static int create_dir(const char *path, const char **dir_files)
+{
+	int res;
+	int i;
+
+	rmdir(path);
+	res = mkdir(path, 0755);
+	if (res == -1) {
+		PERROR("mkdir");
+		return -1;
+	}
+	res = check_type(path, S_IFDIR);
+	if (res == -1)
+		return -1;
+	res = check_mode(path, 0755);
+	if (res == -1)
+		return -1;
+
+	for (i = 0; dir_files[i]; i++) {
+		char fpath[1024];
+		sprintf(fpath, "%s/%s", path, dir_files[i]);
+		res = create_file(fpath, "", 0);
+		if (res == -1) {
+			cleanup_dir(path, dir_files, 1);
+			return -1;
+		}
+	}
+	res = check_dir_contents(path, dir_files);
+	if (res == -1) {
+		cleanup_dir(path, dir_files, 1);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int test_truncate(int len)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int res;
+
+	start_test("truncate(%u)", (int) len);
+	res = create_file(testfile, data, datalen);
+	if (res == -1)
+		return -1;
+
+	res = truncate(testfile, len);
+	if (res == -1) {
+		PERROR("truncate");
+		return -1;
+	}
+	res = check_size(testfile, len);
+	if (res == -1)
+		return -1;
+
+	if (len > 0) {
+		if (len <= datalen) {
+			res = check_data(testfile, data, 0, len);
+			if (res == -1)
+				return -1;
+		} else {
+			res = check_data(testfile, data, 0, datalen);
+			if (res == -1)
+				return -1;
+			res = check_data(testfile, zerodata, datalen,
+					 len - datalen);
+			if (res == -1)
+				return -1;
+		}
+	}
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_ftruncate(int len, int mode)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int res;
+	int fd;
+
+	start_test("ftruncate(%u) mode: 0%03o", len, mode);
+	res = create_file(testfile, data, datalen);
+	if (res == -1)
+		return -1;
+
+	fd = open(testfile, O_WRONLY);
+	if (fd == -1) {
+		PERROR("open");
+		return -1;
+	}
+
+	res = fchmod(fd, mode);
+	if (res == -1) {
+		PERROR("fchmod");
+		close(fd);
+		return -1;
+	}
+	res = check_mode(testfile, mode);
+	if (res == -1) {
+		close(fd);
+		return -1;
+	}
+	res = ftruncate(fd, len);
+	if (res == -1) {
+		PERROR("ftruncate");
+		close(fd);
+		return -1;
+	}
+	close(fd);
+	res = check_size(testfile, len);
+	if (res == -1)
+		return -1;
+
+	if (len > 0) {
+		if (len <= datalen) {
+			res = check_data(testfile, data, 0, len);
+			if (res == -1)
+				return -1;
+		} else {
+			res = check_data(testfile, data, 0, datalen);
+			if (res == -1)
+				return -1;
+			res = check_data(testfile, zerodata, datalen,
+					 len - datalen);
+			if (res == -1)
+				return -1;
+		}
+	}
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_utime(void)
+{
+	struct utimbuf utm;
+	time_t atime = 987631200;
+	time_t mtime = 123116400;
+	int res;
+
+	start_test("utime");
+	res = create_file(testfile, NULL, 0);
+	if (res == -1)
+		return -1;
+
+	utm.actime = atime;
+	utm.modtime = mtime;
+	res = utime(testfile, &utm);
+	if (res == -1) {
+		PERROR("utime");
+		return -1;
+	}
+	res = check_times(testfile, atime, mtime);
+	if (res == -1) {
+		return -1;
+	}
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_create(void)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int err = 0;
+	int res;
+	int fd;
+
+	start_test("create");
+	unlink(testfile);
+	fd = creat(testfile, 0644);
+	if (fd == -1) {
+		PERROR("creat");
+		return -1;
+	}
+	res = write(fd, data, datalen);
+	if (res == -1) {
+		PERROR("write");
+		close(fd);
+		return -1;
+	}
+	if (res != datalen) {
+		ERROR("write is short: %u instead of %u", res, datalen);
+		close(fd);
+		return -1;
+	}
+	res = close(fd);
+	if (res == -1) {
+		PERROR("close");
+		return -1;
+	}
+	res = check_type(testfile, S_IFREG);
+	if (res == -1)
+		return -1;
+	err += check_mode(testfile, 0644);
+	err += check_nlink(testfile, 1);
+	err += check_size(testfile, datalen);
+	err += check_data(testfile, data, 0, datalen);
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_create_unlink(void)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int err = 0;
+	int res;
+	int fd;
+
+	start_test("create+unlink");
+	unlink(testfile);
+	fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
+	if (fd == -1) {
+		PERROR("creat");
+		return -1;
+	}
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		close(fd);
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	res = write(fd, data, datalen);
+	if (res == -1) {
+		PERROR("write");
+		close(fd);
+		return -1;
+	}
+	if (res != datalen) {
+		ERROR("write is short: %u instead of %u", res, datalen);
+		close(fd);
+		return -1;
+	}
+	err += fcheck_type(fd, S_IFREG);
+	err += fcheck_mode(fd, 0644);
+	err += fcheck_nlink(fd, 0);
+	err += fcheck_size(fd, datalen);
+	err += fcheck_data(fd, data, 0, datalen);
+	res = close(fd);
+	if (res == -1) {
+		PERROR("close");
+		err--;
+	}
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_mknod(void)
+{
+	int err = 0;
+	int res;
+
+	start_test("mknod");
+	unlink(testfile);
+	res = mknod(testfile, 0644, 0);
+	if (res == -1) {
+		PERROR("mknod");
+		return -1;
+	}
+	res = check_type(testfile, S_IFREG);
+	if (res == -1)
+		return -1;
+	err += check_mode(testfile, 0644);
+	err += check_nlink(testfile, 1);
+	err += check_size(testfile, 0);
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+#define test_open(exist, flags, mode)  do_test_open(exist, flags, #flags, mode)
+
+static int do_test_open(int exist, int flags, const char *flags_str, int mode)
+{
+	char buf[4096];
+	const char *data = testdata;
+	int datalen = testdatalen;
+	unsigned currlen = 0;
+	int err = 0;
+	int res;
+	int fd;
+	off_t off;
+
+	start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
+	unlink(testfile);
+	if (exist) {
+		res = create_file(testfile_r, testdata2, testdata2len);
+		if (res == -1)
+			return -1;
+
+		currlen = testdata2len;
+	}
+
+	fd = open(testfile, flags, mode);
+	if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
+		if (fd != -1) {
+			ERROR("open should have failed");
+			close(fd);
+			return -1;
+		} else if (errno == EEXIST)
+			goto succ;
+	}
+	if (!(flags & O_CREAT) && !exist) {
+		if (fd != -1) {
+			ERROR("open should have failed");
+			close(fd);
+			return -1;
+		} else if (errno == ENOENT)
+			goto succ;
+	}
+	if (fd == -1) {
+		PERROR("open");
+		return -1;
+	}
+
+	if (flags & O_TRUNC)
+		currlen = 0;
+
+	err += check_type(testfile, S_IFREG);
+	if (exist)
+		err += check_mode(testfile, 0644);
+	else
+		err += check_mode(testfile, mode);
+	err += check_nlink(testfile, 1);
+	err += check_size(testfile, currlen);
+	if (exist && !(flags & O_TRUNC) && (mode & 0400))
+		err += check_data(testfile, testdata2, 0, testdata2len);
+
+	res = write(fd, data, datalen);
+	if ((flags & O_ACCMODE) != O_RDONLY) {
+		if (res == -1) {
+			PERROR("write");
+			err --;
+		} else if (res != datalen) {
+			ERROR("write is short: %u instead of %u", res, datalen);
+			err --;
+		} else {
+			if (datalen > (int) currlen)
+				currlen = datalen;
+
+			err += check_size(testfile, currlen);
+
+			if (mode & 0400) {
+				err += check_data(testfile, data, 0, datalen);
+				if (exist && !(flags & O_TRUNC) &&
+				    testdata2len > datalen)
+					err += check_data(testfile,
+							  testdata2 + datalen,
+							  datalen,
+							  testdata2len - datalen);
+			}
+		}
+	} else {
+		if (res != -1) {
+			ERROR("write should have failed");
+			err --;
+		} else if (errno != EBADF) {
+			PERROR("write");
+			err --;
+		}
+	}
+	off = lseek(fd, SEEK_SET, 0);
+	if (off == (off_t) -1) {
+		PERROR("lseek");
+		err--;
+	} else if (off != 0) {
+		ERROR("offset should have returned 0");
+		err --;
+	}
+	res = read(fd, buf, sizeof(buf));
+	if ((flags & O_ACCMODE) != O_WRONLY) {
+		if (res == -1) {
+			PERROR("read");
+			err--;
+		} else {
+			int readsize =
+				currlen < sizeof(buf) ? currlen : sizeof(buf);
+			if (res != readsize) {
+				ERROR("read is short: %i instead of %u",
+				      res, readsize);
+				err--;
+			} else {
+				if ((flags & O_ACCMODE) != O_RDONLY) {
+					err += check_buffer(buf, data, datalen);
+					if (exist && !(flags & O_TRUNC) &&
+					    testdata2len > datalen)
+						err += check_buffer(buf + datalen,
+								    testdata2 + datalen,
+								    testdata2len - datalen);
+				} else if (exist)
+					err += check_buffer(buf, testdata2,
+							    testdata2len);
+			}
+		}
+	} else {
+		if (res != -1) {
+			ERROR("read should have failed");
+			err --;
+		} else if (errno != EBADF) {
+			PERROR("read");
+			err --;
+		}
+	}
+
+	res = close(fd);
+	if (res == -1) {
+		PERROR("close");
+		return -1;
+	}
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	res = check_nonexist(testfile_r);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+succ:
+	success();
+	return 0;
+}
+
+#define test_open_acc(flags, mode, err)	 \
+	do_test_open_acc(flags, #flags, mode, err)
+
+static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int res;
+	int fd;
+
+	start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
+		   strerror(err));
+	unlink(testfile);
+	res = create_file(testfile, data, datalen);
+	if (res == -1)
+		return -1;
+
+	res = chmod(testfile, mode);
+	if (res == -1) {
+		PERROR("chmod");
+		return -1;
+	}
+
+	res = check_mode(testfile, mode);
+	if (res == -1)
+		return -1;
+
+	fd = open(testfile, flags);
+	if (fd == -1) {
+		if (err != errno) {
+			PERROR("open");
+			return -1;
+		}
+	} else {
+		if (err) {
+			ERROR("open should have failed");
+			close(fd);
+			return -1;
+		}
+		close(fd);
+	}
+	success();
+	return 0;
+}
+
+static int test_symlink(void)
+{
+	char buf[1024];
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int linklen = strlen(testfile);
+	int err = 0;
+	int res;
+
+	start_test("symlink");
+	res = create_file(testfile, data, datalen);
+	if (res == -1)
+		return -1;
+
+	unlink(testfile2);
+	res = symlink(testfile, testfile2);
+	if (res == -1) {
+		PERROR("symlink");
+		return -1;
+	}
+	res = check_type(testfile2, S_IFLNK);
+	if (res == -1)
+		return -1;
+	err += check_mode(testfile2, 0777);
+	err += check_nlink(testfile2, 1);
+	res = readlink(testfile2, buf, sizeof(buf));
+	if (res == -1) {
+		PERROR("readlink");
+		err--;
+	}
+	if (res != linklen) {
+		ERROR("short readlink: %u instead of %u", res, linklen);
+		err--;
+	}
+	if (memcmp(buf, testfile, linklen) != 0) {
+		ERROR("link mismatch");
+		err--;
+	}
+	err += check_size(testfile2, datalen);
+	err += check_data(testfile2, data, 0, datalen);
+	res = unlink(testfile2);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile2);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_link(void)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int err = 0;
+	int res;
+
+	start_test("link");
+	res = create_file(testfile, data, datalen);
+	if (res == -1)
+		return -1;
+
+	unlink(testfile2);
+	res = link(testfile, testfile2);
+	if (res == -1) {
+		PERROR("link");
+		return -1;
+	}
+	res = check_type(testfile2, S_IFREG);
+	if (res == -1)
+		return -1;
+	err += check_mode(testfile2, 0644);
+	err += check_nlink(testfile2, 2);
+	err += check_size(testfile2, datalen);
+	err += check_data(testfile2, data, 0, datalen);
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+
+	err += check_nlink(testfile2, 1);
+	res = unlink(testfile2);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile2);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_link2(void)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int err = 0;
+	int res;
+
+	start_test("link-unlink-link");
+	res = create_file(testfile, data, datalen);
+	if (res == -1)
+		return -1;
+
+	unlink(testfile2);
+	res = link(testfile, testfile2);
+	if (res == -1) {
+		PERROR("link");
+		return -1;
+	}
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	res = link(testfile2, testfile);
+	if (res == -1) {
+		PERROR("link");
+	}
+	res = check_type(testfile, S_IFREG);
+	if (res == -1)
+		return -1;
+	err += check_mode(testfile, 0644);
+	err += check_nlink(testfile, 2);
+	err += check_size(testfile, datalen);
+	err += check_data(testfile, data, 0, datalen);
+
+	res = unlink(testfile2);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	err += check_nlink(testfile, 1);
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_rename_file(void)
+{
+	const char *data = testdata;
+	int datalen = testdatalen;
+	int err = 0;
+	int res;
+
+	start_test("rename file");
+	res = create_file(testfile, data, datalen);
+	if (res == -1)
+		return -1;
+
+	unlink(testfile2);
+	res = rename(testfile, testfile2);
+	if (res == -1) {
+		PERROR("rename");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	res = check_type(testfile2, S_IFREG);
+	if (res == -1)
+		return -1;
+	err += check_mode(testfile2, 0644);
+	err += check_nlink(testfile2, 1);
+	err += check_size(testfile2, datalen);
+	err += check_data(testfile2, data, 0, datalen);
+	res = unlink(testfile2);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile2);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_rename_dir(void)
+{
+	int err = 0;
+	int res;
+
+	start_test("rename dir");
+	res = create_dir(testdir, testdir_files);
+	if (res == -1)
+		return -1;
+
+	rmdir(testdir2);
+	res = rename(testdir, testdir2);
+	if (res == -1) {
+		PERROR("rename");
+		cleanup_dir(testdir, testdir_files, 1);
+		return -1;
+	}
+	res = check_nonexist(testdir);
+	if (res == -1) {
+		cleanup_dir(testdir, testdir_files, 1);
+		return -1;
+	}
+	res = check_type(testdir2, S_IFDIR);
+	if (res == -1) {
+		cleanup_dir(testdir2, testdir_files, 1);
+		return -1;
+	}
+	err += check_mode(testdir2, 0755);
+	err += check_dir_contents(testdir2, testdir_files);
+	err += cleanup_dir(testdir2, testdir_files, 0);
+	res = rmdir(testdir2);
+	if (res == -1) {
+		PERROR("rmdir");
+		return -1;
+	}
+	res = check_nonexist(testdir2);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_mkfifo(void)
+{
+	int res;
+	int err = 0;
+
+	start_test("mkfifo");
+	unlink(testfile);
+	res = mkfifo(testfile, 0644);
+	if (res == -1) {
+		PERROR("mkfifo");
+		return -1;
+	}
+	res = check_type(testfile, S_IFIFO);
+	if (res == -1)
+		return -1;
+	err += check_mode(testfile, 0644);
+	err += check_nlink(testfile, 1);
+	res = unlink(testfile);
+	if (res == -1) {
+		PERROR("unlink");
+		return -1;
+	}
+	res = check_nonexist(testfile);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+static int test_mkdir(void)
+{
+	int res;
+	int err = 0;
+	const char *dir_contents[] = {NULL};
+
+	start_test("mkdir");
+	rmdir(testdir);
+	res = mkdir(testdir, 0755);
+	if (res == -1) {
+		PERROR("mkdir");
+		return -1;
+	}
+	res = check_type(testdir, S_IFDIR);
+	if (res == -1)
+		return -1;
+	err += check_mode(testdir, 0755);
+	err += check_nlink(testdir, 2);
+	err += check_dir_contents(testdir, dir_contents);
+	res = rmdir(testdir);
+	if (res == -1) {
+		PERROR("rmdir");
+		return -1;
+	}
+	res = check_nonexist(testdir);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+#define test_create_ro_dir(flags)	 \
+	do_test_create_ro_dir(flags, #flags)
+
+static int do_test_create_ro_dir(int flags, const char *flags_str)
+{
+	int res;
+	int err = 0;
+	int fd;
+
+	start_test("open(%s) in read-only directory", flags_str);
+	rmdir(testdir);
+	res = mkdir(testdir, 0555);
+	if (res == -1) {
+		PERROR("mkdir");
+		return -1;
+	}
+	fd = open(subfile, flags, 0644);
+	if (fd != -1) {
+		close(fd);
+		unlink(subfile);
+		ERROR("open should have failed");
+		err--;
+	} else {
+		res = check_nonexist(subfile);
+		if (res == -1)
+			err--;
+	}
+	unlink(subfile);
+	res = rmdir(testdir);
+	if (res == -1) {
+		PERROR("rmdir");
+		return -1;
+	}
+	res = check_nonexist(testdir);
+	if (res == -1)
+		return -1;
+	if (err)
+		return -1;
+
+	success();
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	const char *basepath;
+	const char *realpath;
+	int err = 0;
+	int a;
+	int is_root;
+
+	umask(0);
+	if (argc < 2 || argc > 4) {
+		fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#]\n", argv[0]);
+		return 1;
+	}
+	basepath = argv[1];
+	realpath = basepath;
+	for (a = 2; a < argc; a++) {
+		char *endptr;
+		char *arg = argv[a];
+		if (arg[0] == ':') {
+			realpath = arg + 1;
+		} else {
+			if (arg[0] == '-') {
+				arg++;
+				skip_test = strtoul(arg, &endptr, 10);
+			} else {
+				select_test = strtoul(arg, &endptr, 10);
+			}
+			if (arg[0] == '\0' || *endptr != '\0') {
+				fprintf(stderr, "invalid number: '%s'\n", arg);
+				return 1;
+			}
+		}
+	}
+	assert(strlen(basepath) < 512);
+	assert(strlen(realpath) < 512);
+	if (basepath[0] != '/') {
+		fprintf(stderr, "testdir must be an absolute path\n");
+		return 1;
+	}
+
+	sprintf(testfile, "%s/testfile", basepath);
+	sprintf(testfile2, "%s/testfile2", basepath);
+	sprintf(testdir, "%s/testdir", basepath);
+	sprintf(testdir2, "%s/testdir2", basepath);
+	sprintf(subfile, "%s/subfile", testdir2);
+
+	sprintf(testfile_r, "%s/testfile", realpath);
+	sprintf(testfile2_r, "%s/testfile2", realpath);
+	sprintf(testdir_r, "%s/testdir", realpath);
+	sprintf(testdir2_r, "%s/testdir2", realpath);
+	sprintf(subfile_r, "%s/subfile", testdir2_r);
+
+	is_root = (geteuid() == 0);
+
+	err += test_create();
+	err += test_create_unlink();
+	err += test_mknod();
+	err += test_symlink();
+	err += test_link();
+	err += test_link2();
+	err += test_mkfifo();
+	err += test_mkdir();
+	err += test_rename_file();
+	err += test_rename_dir();
+	err += test_utime();
+	err += test_truncate(0);
+	err += test_truncate(testdatalen / 2);
+	err += test_truncate(testdatalen);
+	err += test_truncate(testdatalen + 100);
+	err += test_ftruncate(0, 0600);
+	err += test_ftruncate(testdatalen / 2, 0600);
+	err += test_ftruncate(testdatalen, 0600);
+	err += test_ftruncate(testdatalen + 100, 0600);
+	err += test_ftruncate(0, 0400);
+	err += test_ftruncate(0, 0200);
+	err += test_ftruncate(0, 0000);
+	err += test_open(0, O_RDONLY, 0);
+	err += test_open(1, O_RDONLY, 0);
+	err += test_open(1, O_RDWR, 0);
+	err += test_open(1, O_WRONLY, 0);
+	err += test_open(0, O_RDWR | O_CREAT, 0600);
+	err += test_open(1, O_RDWR | O_CREAT, 0600);
+	err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
+	err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
+	err += test_open(0, O_RDONLY | O_CREAT, 0600);
+	err += test_open(0, O_RDONLY | O_CREAT, 0400);
+	err += test_open(0, O_RDONLY | O_CREAT, 0200);
+	err += test_open(0, O_RDONLY | O_CREAT, 0000);
+	err += test_open(0, O_WRONLY | O_CREAT, 0600);
+	err += test_open(0, O_WRONLY | O_CREAT, 0400);
+	err += test_open(0, O_WRONLY | O_CREAT, 0200);
+	err += test_open(0, O_WRONLY | O_CREAT, 0000);
+	err += test_open(0, O_RDWR | O_CREAT, 0400);
+	err += test_open(0, O_RDWR | O_CREAT, 0200);
+	err += test_open(0, O_RDWR | O_CREAT, 0000);
+	err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
+	err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
+	err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
+	err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
+	err += test_open_acc(O_RDONLY, 0600, 0);
+	err += test_open_acc(O_WRONLY, 0600, 0);
+	err += test_open_acc(O_RDWR,   0600, 0);
+	err += test_open_acc(O_RDONLY, 0400, 0);
+	err += test_open_acc(O_WRONLY, 0200, 0);
+	if(!is_root) {
+		err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
+		err += test_open_acc(O_WRONLY, 0400, EACCES);
+		err += test_open_acc(O_RDWR,   0400, EACCES);
+		err += test_open_acc(O_RDONLY, 0200, EACCES);
+		err += test_open_acc(O_RDWR,   0200, EACCES);
+		err += test_open_acc(O_RDONLY, 0000, EACCES);
+		err += test_open_acc(O_WRONLY, 0000, EACCES);
+		err += test_open_acc(O_RDWR,   0000, EACCES);
+	}
+	err += test_create_ro_dir(O_CREAT);
+	err += test_create_ro_dir(O_CREAT | O_EXCL);
+	err += test_create_ro_dir(O_CREAT | O_WRONLY);
+	err += test_create_ro_dir(O_CREAT | O_TRUNC);
+
+	unlink(testfile);
+	unlink(testfile2);
+	rmdir(testdir);
+	rmdir(testdir2);
+
+	if (err) {
+		fprintf(stderr, "%i tests failed\n", -err);
+		return 1;
+	}
+
+	return 0;
+}
diff --git a/test/test_examples.py b/test/test_examples.py
new file mode 100755
index 0000000..457c2a0
--- /dev/null
+++ b/test/test_examples.py
@@ -0,0 +1,479 @@
+#!/usr/bin/env python3
+
+if __name__ == '__main__':
+    import pytest
+    import sys
+    sys.exit(pytest.main([__file__] + sys.argv[1:]))
+
+import subprocess
+import os
+import sys
+import pytest
+import stat
+import shutil
+import filecmp
+import errno
+import platform
+from distutils.version import LooseVersion
+from tempfile import NamedTemporaryFile
+from util import (wait_for_mount, umount, cleanup, base_cmdline,
+                  safe_sleep, basename)
+from os.path import join as pjoin
+
+TEST_FILE = __file__
+
+with open(TEST_FILE, 'rb') as fh:
+    TEST_DATA = fh.read()
+
+def name_generator(__ctr=[0]):
+    __ctr[0] += 1
+    return 'testfile_%d' % __ctr[0]
+
+@pytest.mark.parametrize("name", ('hello', 'hello_ll'))
+@pytest.mark.parametrize("options", ([], ['-o', 'clone_fd']))
+def test_hello(tmpdir, name, options):
+    mnt_dir = str(tmpdir)
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', name),
+                '-f', mnt_dir ] + options
+    if name == 'hello_ll':
+        # supports single-threading only
+        cmdline.append('-s')
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+        assert os.listdir(mnt_dir) == [ 'hello' ]
+        filename = pjoin(mnt_dir, 'hello')
+        with open(filename, 'r') as fh:
+            assert fh.read() == 'Hello World!\n'
+        with pytest.raises(IOError) as exc_info:
+            open(filename, 'r+')
+        assert exc_info.value.errno == errno.EACCES
+        with pytest.raises(IOError) as exc_info:
+            open(filename + 'does-not-exist', 'r+')
+        assert exc_info.value.errno == errno.ENOENT
+    except:
+        cleanup(mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+@pytest.mark.parametrize("name", ('passthrough', 'passthrough_fh',
+                                  'passthrough_ll'))
+@pytest.mark.parametrize("debug", (True, False))
+def test_passthrough(tmpdir, name, debug):
+    mnt_dir = str(tmpdir.mkdir('mnt'))
+    src_dir = str(tmpdir.mkdir('src'))
+
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', name),
+                '-f', mnt_dir ]
+    if debug:
+        cmdline.append('-d')
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+        work_dir = pjoin(mnt_dir, src_dir)
+
+        subprocess.check_call([ os.path.join(basename, 'test', 'test'),
+                    work_dir, ':' + src_dir ])
+
+        tst_write(work_dir)
+        tst_mkdir(work_dir)
+        tst_symlink(work_dir)
+        tst_mknod(work_dir)
+        if os.getuid() == 0:
+            tst_chown(work_dir)
+        # Underlying fs may not have full nanosecond resolution
+        tst_utimens(work_dir, ns_tol=1000)
+        tst_link(work_dir)
+        tst_readdir(work_dir)
+        tst_statvfs(work_dir)
+        tst_truncate_path(work_dir)
+        tst_truncate_fd(work_dir)
+        tst_unlink(work_dir)
+        tst_passthrough(src_dir, work_dir)
+    except:
+        cleanup(mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+def test_ioctl(tmpdir):
+    mnt_dir = str(tmpdir)
+    testfile = pjoin(mnt_dir, 'fioc')
+    cmdline = base_cmdline + \
+              [pjoin(basename, 'example', 'ioctl'), '-f', mnt_dir ]
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+
+        cmdline = base_cmdline + \
+                  [ pjoin(basename, 'example', 'ioctl_client'),
+                    testfile ]
+        assert subprocess.check_output(cmdline) == b'0\n'
+        with open(testfile, 'wb') as fh:
+            fh.write(b'foobar')
+        assert subprocess.check_output(cmdline) == b'6\n'
+        subprocess.check_call(cmdline + [ '3' ])
+        with open(testfile, 'rb') as fh:
+            assert fh.read()== b'foo'
+    except:
+        cleanup(mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+def test_poll(tmpdir):
+    mnt_dir = str(tmpdir)
+    cmdline = base_cmdline + [pjoin(basename, 'example', 'poll'),
+               '-f', mnt_dir ]
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+        cmdline = base_cmdline + \
+                  [ pjoin(basename, 'example', 'poll_client') ]
+        subprocess.check_call(cmdline, cwd=mnt_dir)
+    except:
+        cleanup(mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+@pytest.mark.parametrize("name",
+                         ('notify_inval_inode',
+                          'notify_store_retrieve'))
+@pytest.mark.parametrize("notify", (True, False))
+def test_notify1(tmpdir, name, notify):
+    mnt_dir = str(tmpdir)
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', name),
+                '-f', '--update-interval=1', mnt_dir ]
+    if not notify:
+        cmdline.append('--no-notify')
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+        filename = pjoin(mnt_dir, 'current_time')
+        with open(filename, 'r') as fh:
+            read1 = fh.read()
+        safe_sleep(2)
+        with open(filename, 'r') as fh:
+            read2 = fh.read()
+        if notify:
+            assert read1 != read2
+        else:
+            assert read1 == read2
+    except:
+        cleanup(mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+@pytest.mark.parametrize("notify", (True, False))
+def test_notify_inval_entry(tmpdir, notify):
+    mnt_dir = str(tmpdir)
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', 'notify_inval_entry'),
+                '-f', '--update-interval=1',
+                '--timeout=5', mnt_dir ]
+    if not notify:
+        cmdline.append('--no-notify')
+    mount_process = subprocess.Popen(cmdline)
+    try:
+        wait_for_mount(mount_process, mnt_dir)
+        fname = pjoin(mnt_dir, os.listdir(mnt_dir)[0])
+        try:
+            os.stat(fname)
+        except FileNotFoundError:
+            # We may have hit a race condition and issued
+            # readdir just before the name changed
+            fname = pjoin(mnt_dir, os.listdir(mnt_dir)[0])
+            os.stat(fname)
+
+        safe_sleep(2)
+        if not notify:
+            os.stat(fname)
+            safe_sleep(5)
+        with pytest.raises(FileNotFoundError):
+            os.stat(fname)
+    except:
+        cleanup(mnt_dir)
+        raise
+    else:
+        umount(mount_process, mnt_dir)
+
+@pytest.mark.parametrize("writeback", (False, True))
+def test_write_cache(tmpdir, writeback):
+    if writeback and LooseVersion(platform.release()) < '3.14':
+        pytest.skip('Requires kernel 3.14 or newer')
+    # This test hangs under Valgrind when running close(fd)
+    # test_write_cache.c:test_fs(). Most likely this is because of an internal
+    # deadlock in valgrind, it probably assumes that until close() returns,
+    # control does not come to the program.
+    mnt_dir = str(tmpdir)
+    cmdline = [ pjoin(basename, 'test', 'test_write_cache'),
+                mnt_dir ]
+    if writeback:
+        cmdline.append('-owriteback_cache')
+    subprocess.check_call(cmdline)
+
+@pytest.mark.skipif(os.getuid() != 0,
+                    reason='needs to run as root')
+def test_cuse(capfd):
+
+    # Valgrind warns about unknown ioctls, that's ok
+    capfd.register_output(r'^==([0-9]+).+unhandled ioctl.+\n'
+                          r'==\1== \s{3}.+\n'
+                          r'==\1== \s{3}.+$', count=0)
+
+    devname = 'cuse-test-%d' % os.getpid()
+    devpath = '/dev/%s' % devname
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', 'cuse'),
+                '-f', '--name=%s' % devname ]
+    mount_process = subprocess.Popen(cmdline)
+
+    cmdline = base_cmdline + \
+              [ pjoin(basename, 'example', 'cuse_client'),
+                devpath ]
+    try:
+        wait_for_mount(mount_process, devpath,
+                       test_fn=os.path.exists)
+        assert subprocess.check_output(cmdline + ['s']) == b'0\n'
+        data = b'some test data'
+        off = 5
+        proc = subprocess.Popen(cmdline + [ 'w', str(len(data)), str(off) ],
+                                stdin=subprocess.PIPE)
+        proc.stdin.write(data)
+        proc.stdin.close()
+        assert proc.wait(timeout=10) == 0
+        size = str(off + len(data)).encode() + b'\n'
+        assert subprocess.check_output(cmdline + ['s']) == size
+        out = subprocess.check_output(
+            cmdline + [ 'r', str(off + len(data) + 2), '0' ])
+        assert out == (b'\0' * off) + data
+    finally:
+        mount_process.terminate()
+
+def checked_unlink(filename, path, isdir=False):
+    fullname = pjoin(path, filename)
+    if isdir:
+        os.rmdir(fullname)
+    else:
+        os.unlink(fullname)
+    with pytest.raises(OSError) as exc_info:
+        os.stat(fullname)
+    assert exc_info.value.errno == errno.ENOENT
+    assert filename not in os.listdir(path)
+
+def tst_mkdir(mnt_dir):
+    dirname = name_generator()
+    fullname = mnt_dir + "/" + dirname
+    os.mkdir(fullname)
+    fstat = os.stat(fullname)
+    assert stat.S_ISDIR(fstat.st_mode)
+    assert os.listdir(fullname) ==  []
+    assert fstat.st_nlink in (1,2)
+    assert dirname in os.listdir(mnt_dir)
+    checked_unlink(dirname, mnt_dir, isdir=True)
+
+def tst_symlink(mnt_dir):
+    linkname = name_generator()
+    fullname = mnt_dir + "/" + linkname
+    os.symlink("/imaginary/dest", fullname)
+    fstat = os.lstat(fullname)
+    assert stat.S_ISLNK(fstat.st_mode)
+    assert os.readlink(fullname) == "/imaginary/dest"
+    assert fstat.st_nlink == 1
+    assert linkname in os.listdir(mnt_dir)
+    checked_unlink(linkname, mnt_dir)
+
+def tst_mknod(mnt_dir):
+    filename = pjoin(mnt_dir, name_generator())
+    shutil.copyfile(TEST_FILE, filename)
+    fstat = os.lstat(filename)
+    assert stat.S_ISREG(fstat.st_mode)
+    assert fstat.st_nlink == 1
+    assert os.path.basename(filename) in os.listdir(mnt_dir)
+    assert filecmp.cmp(TEST_FILE, filename, False)
+    checked_unlink(filename, mnt_dir)
+
+def tst_chown(mnt_dir):
+    filename = pjoin(mnt_dir, name_generator())
+    os.mkdir(filename)
+    fstat = os.lstat(filename)
+    uid = fstat.st_uid
+    gid = fstat.st_gid
+
+    uid_new = uid + 1
+    os.chown(filename, uid_new, -1)
+    fstat = os.lstat(filename)
+    assert fstat.st_uid == uid_new
+    assert fstat.st_gid == gid
+
+    gid_new = gid + 1
+    os.chown(filename, -1, gid_new)
+    fstat = os.lstat(filename)
+    assert fstat.st_uid == uid_new
+    assert fstat.st_gid == gid_new
+
+    checked_unlink(filename, mnt_dir, isdir=True)
+
+def tst_write(mnt_dir):
+    name = pjoin(mnt_dir, name_generator())
+    shutil.copyfile(TEST_FILE, name)
+    assert filecmp.cmp(name, TEST_FILE, False)
+    checked_unlink(name, mnt_dir)
+
+def tst_unlink(mnt_dir):
+    name = pjoin(mnt_dir, name_generator())
+    data1 = b'foo'
+    data2 = b'bar'
+
+    with open(pjoin(mnt_dir, name), 'wb+', buffering=0) as fh:
+        fh.write(data1)
+        checked_unlink(name, mnt_dir)
+        fh.write(data2)
+        fh.seek(0)
+        assert fh.read() == data1+data2
+
+def tst_statvfs(mnt_dir):
+    os.statvfs(mnt_dir)
+
+def tst_link(mnt_dir):
+    name1 = pjoin(mnt_dir, name_generator())
+    name2 = pjoin(mnt_dir, name_generator())
+    shutil.copyfile(TEST_FILE, name1)
+    assert filecmp.cmp(name1, TEST_FILE, False)
+    os.link(name1, name2)
+
+    fstat1 = os.lstat(name1)
+    fstat2 = os.lstat(name2)
+
+    assert fstat1 == fstat2
+    assert fstat1.st_nlink == 2
+
+    assert os.path.basename(name2) in os.listdir(mnt_dir)
+    assert filecmp.cmp(name1, name2, False)
+    os.unlink(name2)
+    fstat1 = os.lstat(name1)
+    assert fstat1.st_nlink == 1
+    os.unlink(name1)
+
+def tst_readdir(mnt_dir):
+    dir_ = pjoin(mnt_dir, name_generator())
+    file_ = dir_ + "/" + name_generator()
+    subdir = dir_ + "/" + name_generator()
+    subfile = subdir + "/" + name_generator()
+
+    os.mkdir(dir_)
+    shutil.copyfile(TEST_FILE, file_)
+    os.mkdir(subdir)
+    shutil.copyfile(TEST_FILE, subfile)
+
+    listdir_is = os.listdir(dir_)
+    listdir_is.sort()
+    listdir_should = [ os.path.basename(file_), os.path.basename(subdir) ]
+    listdir_should.sort()
+    assert listdir_is == listdir_should
+
+    os.unlink(file_)
+    os.unlink(subfile)
+    os.rmdir(subdir)
+    os.rmdir(dir_)
+
+def tst_truncate_path(mnt_dir):
+    assert len(TEST_DATA) > 1024
+
+    filename = pjoin(mnt_dir, name_generator())
+    with open(filename, 'wb') as fh:
+        fh.write(TEST_DATA)
+
+    fstat = os.stat(filename)
+    size = fstat.st_size
+    assert size == len(TEST_DATA)
+
+    # Add zeros at the end
+    os.truncate(filename, size + 1024)
+    assert os.stat(filename).st_size == size + 1024
+    with open(filename, 'rb') as fh:
+        assert fh.read(size) == TEST_DATA
+        assert fh.read(1025) == b'\0' * 1024
+
+    # Truncate data
+    os.truncate(filename, size - 1024)
+    assert os.stat(filename).st_size == size - 1024
+    with open(filename, 'rb') as fh:
+        assert fh.read(size) == TEST_DATA[:size-1024]
+
+    os.unlink(filename)
+
+def tst_truncate_fd(mnt_dir):
+    assert len(TEST_DATA) > 1024
+    with NamedTemporaryFile('w+b', 0, dir=mnt_dir) as fh:
+        fd = fh.fileno()
+        fh.write(TEST_DATA)
+        fstat = os.fstat(fd)
+        size = fstat.st_size
+        assert size == len(TEST_DATA)
+
+        # Add zeros at the end
+        os.ftruncate(fd, size + 1024)
+        assert os.fstat(fd).st_size == size + 1024
+        fh.seek(0)
+        assert fh.read(size) == TEST_DATA
+        assert fh.read(1025) == b'\0' * 1024
+
+        # Truncate data
+        os.ftruncate(fd, size - 1024)
+        assert os.fstat(fd).st_size == size - 1024
+        fh.seek(0)
+        assert fh.read(size) == TEST_DATA[:size-1024]
+
+def tst_utimens(mnt_dir, ns_tol=0):
+    filename = pjoin(mnt_dir, name_generator())
+    os.mkdir(filename)
+    fstat = os.lstat(filename)
+
+    atime = fstat.st_atime + 42.28
+    mtime = fstat.st_mtime - 42.23
+    if sys.version_info < (3,3):
+        os.utime(filename, (atime, mtime))
+    else:
+        atime_ns = fstat.st_atime_ns + int(42.28*1e9)
+        mtime_ns = fstat.st_mtime_ns - int(42.23*1e9)
+        os.utime(filename, None, ns=(atime_ns, mtime_ns))
+
+    fstat = os.lstat(filename)
+
+    assert abs(fstat.st_atime - atime) < 1e-3
+    assert abs(fstat.st_mtime - mtime) < 1e-3
+    if sys.version_info >= (3,3):
+        assert abs(fstat.st_atime_ns - atime_ns) <= ns_tol
+        assert abs(fstat.st_mtime_ns - mtime_ns) <= ns_tol
+
+    checked_unlink(filename, mnt_dir, isdir=True)
+
+def tst_passthrough(src_dir, mnt_dir):
+    name = name_generator()
+    src_name = pjoin(src_dir, name)
+    mnt_name = pjoin(src_dir, name)
+    assert name not in os.listdir(src_dir)
+    assert name not in os.listdir(mnt_dir)
+    with open(src_name, 'w') as fh:
+        fh.write('Hello, world')
+    assert name in os.listdir(src_dir)
+    assert name in os.listdir(mnt_dir)
+    assert os.stat(src_name) == os.stat(mnt_name)
+
+    name = name_generator()
+    src_name = pjoin(src_dir, name)
+    mnt_name = pjoin(src_dir, name)
+    assert name not in os.listdir(src_dir)
+    assert name not in os.listdir(mnt_dir)
+    with open(mnt_name, 'w') as fh:
+        fh.write('Hello, world')
+    assert name in os.listdir(src_dir)
+    assert name in os.listdir(mnt_dir)
+    assert os.stat(src_name) == os.stat(mnt_name)
diff --git a/test/test_setattr.c b/test/test_setattr.c
new file mode 100644
index 0000000..1eef66a
--- /dev/null
+++ b/test/test_setattr.c
@@ -0,0 +1,184 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <linux/limits.h>
+
+#define FILE_INO 2
+#define FILE_NAME "truncate_me"
+
+static int got_fh;
+static mode_t file_mode = S_IFREG | 0644;
+
+static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+    stbuf->st_ino = ino;
+    if (ino == FUSE_ROOT_ID) {
+        stbuf->st_mode = S_IFDIR | 0755;
+        stbuf->st_nlink = 1;
+    }
+
+    else if (ino == FILE_INO) {
+        stbuf->st_mode = file_mode;
+        stbuf->st_nlink = 1;
+        stbuf->st_size = 0;
+    }
+
+    else
+        return -1;
+
+    return 0;
+}
+
+static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+                       const char *name) {
+    struct fuse_entry_param e;
+    memset(&e, 0, sizeof(e));
+
+    if (parent != FUSE_ROOT_ID)
+        goto err_out;
+    else if (strcmp(name, FILE_NAME) == 0)
+        e.ino = FILE_INO;
+    else
+        goto err_out;
+
+    if (tfs_stat(e.ino, &e.attr) != 0)
+        goto err_out;
+    fuse_reply_entry(req, &e);
+    return;
+
+err_out:
+    fuse_reply_err(req, ENOENT);
+}
+
+static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+                        struct fuse_file_info *fi) {
+    struct stat stbuf;
+
+    (void) fi;
+
+    memset(&stbuf, 0, sizeof(stbuf));
+    if (tfs_stat(ino, &stbuf) != 0)
+        fuse_reply_err(req, ENOENT);
+    else
+        fuse_reply_attr(req, &stbuf, 5);
+}
+
+static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+                     struct fuse_file_info *fi) {
+    if (ino == FUSE_ROOT_ID)
+        fuse_reply_err(req, EISDIR);
+    else {
+        assert(ino == FILE_INO);
+        fi->fh = FILE_INO;
+        fuse_reply_open(req, fi);
+    }
+}
+
+static void tfs_setattr (fuse_req_t req, fuse_ino_t ino, struct stat *attr,
+                         int to_set, struct fuse_file_info *fi) {
+    if(ino != FILE_INO ||
+       !(to_set & FUSE_SET_ATTR_MODE)) {
+        fuse_reply_err(req, EINVAL);
+        return;
+    }
+
+    if(fi == NULL)
+        fprintf(stderr, "setattr with fi == NULL\n");
+    else if (fi->fh != FILE_INO)
+        fprintf(stderr, "setattr with wrong fi->fh\n");
+    else {
+        fprintf(stderr, "setattr ok\n");
+        got_fh = 1;
+        file_mode = attr->st_mode;
+    }
+
+    tfs_getattr(req, ino, fi);
+}
+
+static struct fuse_lowlevel_ops tfs_oper = {
+    .lookup	= tfs_lookup,
+    .getattr	= tfs_getattr,
+    .open	= tfs_open,
+    .setattr	= tfs_setattr,
+};
+
+static void* run_fs(void *data) {
+    struct fuse_session *se = (struct fuse_session*) data;
+    assert(fuse_session_loop(se) == 0);
+    return NULL;
+}
+
+static void test_fs(char *mountpoint) {
+    char fname[PATH_MAX];
+    int fd;
+
+    assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+                     mountpoint) > 0);
+    fd = open(fname, O_WRONLY);
+    if (fd == -1) {
+        perror(fname);
+        assert(0);
+    }
+
+    assert(fchmod(fd, 0600) == 0);
+    close(fd);
+}
+
+int main(int argc, char *argv[]) {
+    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+    struct fuse_session *se;
+    struct fuse_cmdline_opts fuse_opts;
+    pthread_t fs_thread;
+
+    assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+    assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+    se = fuse_session_new(&args, &tfs_oper,
+                          sizeof(tfs_oper), NULL);
+    assert (se != NULL);
+    assert(fuse_set_signal_handlers(se) == 0);
+    assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
+    /* Start file-system thread */
+    assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
+    /* Do test */
+    test_fs(fuse_opts.mountpoint);
+
+    /* Stop file system */
+    assert(pthread_cancel(fs_thread) == 0);
+
+    fuse_session_unmount(se);
+    assert(got_fh == 1);
+    fuse_remove_signal_handlers(se);
+    fuse_session_destroy(se);
+
+    printf("Test completed successfully.\n");
+    return 0;
+}
+
+
+/**
+ * Local Variables:
+ * mode: c
+ * indent-tabs-mode: nil
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/test/test_write_cache.c b/test/test_write_cache.c
new file mode 100644
index 0000000..fa80327
--- /dev/null
+++ b/test/test_write_cache.c
@@ -0,0 +1,216 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+
+#define FUSE_USE_VERSION 30
+
+#include <config.h>
+#include <fuse_lowlevel.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <linux/limits.h>
+
+#define FILE_INO 2
+#define FILE_NAME "write_me"
+
+/* Command line parsing */
+struct options {
+    int writeback;
+    int data_size;
+} options = {
+    .writeback = 0,
+    .data_size = 4096,
+};
+
+#define OPTION(t, p)                           \
+    { t, offsetof(struct options, p), 1 }
+static const struct fuse_opt option_spec[] = {
+    OPTION("writeback_cache", writeback),
+    OPTION("--data-size=%d", data_size),
+    FUSE_OPT_END
+};
+static int got_write;
+
+static void tfs_init (void *userdata, struct fuse_conn_info *conn)
+{
+    (void) userdata;
+
+    if(options.writeback) {
+        assert(conn->capable & FUSE_CAP_WRITEBACK_CACHE);
+        conn->want |= FUSE_CAP_WRITEBACK_CACHE;
+    }
+}
+
+static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
+    stbuf->st_ino = ino;
+    if (ino == FUSE_ROOT_ID) {
+        stbuf->st_mode = S_IFDIR | 0755;
+        stbuf->st_nlink = 1;
+    }
+
+    else if (ino == FILE_INO) {
+        stbuf->st_mode = S_IFREG | 0222;
+        stbuf->st_nlink = 1;
+        stbuf->st_size = 0;
+    }
+
+    else
+        return -1;
+
+    return 0;
+}
+
+static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
+                       const char *name) {
+    struct fuse_entry_param e;
+    memset(&e, 0, sizeof(e));
+
+    if (parent != FUSE_ROOT_ID)
+        goto err_out;
+    else if (strcmp(name, FILE_NAME) == 0)
+        e.ino = FILE_INO;
+    else
+        goto err_out;
+
+    if (tfs_stat(e.ino, &e.attr) != 0)
+        goto err_out;
+    fuse_reply_entry(req, &e);
+    return;
+
+err_out:
+    fuse_reply_err(req, ENOENT);
+}
+
+static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
+                        struct fuse_file_info *fi) {
+    struct stat stbuf;
+
+    (void) fi;
+
+    memset(&stbuf, 0, sizeof(stbuf));
+    if (tfs_stat(ino, &stbuf) != 0)
+        fuse_reply_err(req, ENOENT);
+    else
+        fuse_reply_attr(req, &stbuf, 5);
+}
+
+static void tfs_open(fuse_req_t req, fuse_ino_t ino,
+                     struct fuse_file_info *fi) {
+    if (ino == FUSE_ROOT_ID)
+        fuse_reply_err(req, EISDIR);
+    else {
+        assert(ino == FILE_INO);
+        fuse_reply_open(req, fi);
+    }
+}
+
+static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
+                      size_t size, off_t off, struct fuse_file_info *fi) {
+    (void) fi; (void) buf; (void) off;
+    size_t expected;
+
+    assert(ino == FILE_INO);
+    expected = options.data_size;
+    if(options.writeback)
+        expected *= 2;
+
+    if(size != expected)
+        fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
+                expected, size);
+    else
+        got_write = 1;
+    fuse_reply_write(req, size);
+}
+
+static struct fuse_lowlevel_ops tfs_oper = {
+    .init       = tfs_init,
+    .lookup	= tfs_lookup,
+    .getattr	= tfs_getattr,
+    .open	= tfs_open,
+    .write	= tfs_write,
+};
+
+static void* run_fs(void *data) {
+    struct fuse_session *se = (struct fuse_session*) data;
+    assert(fuse_session_loop(se) == 0);
+    return NULL;
+}
+
+static void test_fs(char *mountpoint) {
+    char fname[PATH_MAX];
+    char *buf;
+    size_t dsize = options.data_size;
+    int fd;
+
+    buf = malloc(dsize);
+    assert(buf != NULL);
+    assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
+    assert(read(fd, buf, dsize) == dsize);
+    close(fd);
+
+    assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME,
+                     mountpoint) > 0);
+    fd = open(fname, O_WRONLY);
+    if (fd == -1) {
+        perror(fname);
+        assert(0);
+    }
+
+    assert(write(fd, buf, dsize) == dsize);
+    assert(write(fd, buf, dsize) == dsize);
+    close(fd);
+}
+
+int main(int argc, char *argv[]) {
+    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
+    struct fuse_session *se;
+    struct fuse_cmdline_opts fuse_opts;
+    pthread_t fs_thread;
+
+    assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
+    assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
+    assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
+    se = fuse_session_new(&args, &tfs_oper,
+                          sizeof(tfs_oper), NULL);
+    assert (se != NULL);
+    assert(fuse_set_signal_handlers(se) == 0);
+    assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
+
+    /* Start file-system thread */
+    assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
+
+    /* Write test data */
+    test_fs(fuse_opts.mountpoint);
+
+    /* Stop file system */
+    assert(pthread_cancel(fs_thread) == 0);
+
+    fuse_session_unmount(se);
+    assert(got_write == 1);
+    fuse_remove_signal_handlers(se);
+    fuse_session_destroy(se);
+
+    printf("Test completed successfully.\n");
+    return 0;
+}
+
+
+/**
+ * Local Variables:
+ * mode: c
+ * indent-tabs-mode: nil
+ * c-basic-offset: 4
+ * End:
+ */
diff --git a/test/util.py b/test/util.py
new file mode 100644
index 0000000..76b8ec1
--- /dev/null
+++ b/test/util.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+import subprocess
+import pytest
+import os
+import time
+from os.path import join as pjoin
+
+basename = pjoin(os.path.dirname(__file__), '..')
+
+def wait_for_mount(mount_process, mnt_dir,
+                   test_fn=os.path.ismount):
+    elapsed = 0
+    while elapsed < 30:
+        if test_fn(mnt_dir):
+            return True
+        if mount_process.poll() is not None:
+            pytest.fail('file system process terminated prematurely')
+        time.sleep(0.1)
+        elapsed += 0.1
+    pytest.fail("mountpoint failed to come up")
+
+def cleanup(mnt_dir):
+    # Don't bother trying Valgrind if things already went wrong
+
+    subprocess.call([pjoin(basename, 'util', 'fusermount3'),
+                     '-z', '-u', mnt_dir],
+                    stdout=subprocess.DEVNULL,
+                    stderr=subprocess.STDOUT)
+
+def umount(mount_process, mnt_dir):
+    # fusermount3 will be setuid root, so we can only trace it with
+    # valgrind if we're root
+    if os.getuid() == 0:
+        cmdline = base_cmdline
+    else:
+        cmdline = []
+
+    cmdline = cmdline + [ pjoin(basename, 'util', 'fusermount3'),
+                          '-z', '-u', mnt_dir ]
+    subprocess.check_call(cmdline)
+    assert not os.path.ismount(mnt_dir)
+
+    # Give mount process a little while to terminate. Popen.wait(timeout)
+    # was only added in 3.3...
+    elapsed = 0
+    while elapsed < 30:
+        code = mount_process.poll()
+        if code is not None:
+            if code == 0:
+                return
+            pytest.fail('file system process terminated with code %s' % (code,))
+        time.sleep(0.1)
+        elapsed += 0.1
+    pytest.fail('mount process did not terminate')
+
+
+def safe_sleep(secs):
+    '''Like time.sleep(), but sleep for at least *secs*
+
+    `time.sleep` may sleep less than the given period if a signal is
+    received. This function ensures that we sleep for at least the
+    desired time.
+    '''
+
+    now = time.time()
+    end = now + secs
+    while now < end:
+        time.sleep(end - now)
+        now = time.time()
+
+# If valgrind and libtool are available, use them
+def has_program(name):
+    try:
+        ret = subprocess.call([name, '--version'],
+                              stdout=subprocess.DEVNULL,
+                              stderr=subprocess.DEVNULL)
+    except FileNotFoundError:
+        return False
+    return ret == 0
+
+if has_program('valgrind') and has_program('libtool'):
+    base_cmdline = [ 'libtool', '--mode=execute',
+                     'valgrind', '-q', '--' ]
+else:
+    base_cmdline = []
+
+
+# Try to use local fusermount3
+os.environ['PATH'] = '%s:%s' % (pjoin(basename, 'util'), os.environ['PATH'])
diff --git a/util/Makefile.am b/util/Makefile.am
new file mode 100644
index 0000000..756afea
--- /dev/null
+++ b/util/Makefile.am
@@ -0,0 +1,53 @@
+## Process this file with automake to produce Makefile.in
+
+bin_PROGRAMS = fusermount3
+noinst_PROGRAMS = mount.fuse3
+
+# we re-use mount_util.c from the library, but do want to keep ourself
+# as stand-alone as possible. in order to make an out-of-source build
+# possible, we "generate" the file from its original location by
+# copying it over.
+fusermount3_SOURCES = fusermount.c mount_util.c
+fusermount3_CPPFLAGS = -I$(top_srcdir)/lib
+BUILT_SOURCES = mount_util.c
+mount_util.c: $(top_srcdir)/lib/mount_util.c
+	@cp $(top_srcdir)/lib/mount_util.c .
+
+mount_fuse3_SOURCES = mount.fuse.c
+
+install-exec-hook:
+	-chmod u+s $(DESTDIR)$(bindir)/fusermount3
+	@if test ! -e $(DESTDIR)/dev/fuse; then \
+		$(MKDIR_P) $(DESTDIR)/dev; \
+		echo "mknod $(DESTDIR)/dev/fuse -m 0666 c 10 229 || true"; \
+		mknod $(DESTDIR)/dev/fuse -m 0666 c 10 229 || true; \
+	fi
+
+EXTRA_DIST = udev.rules init_script
+
+MOUNT_FUSE_PATH = @MOUNT_FUSE_PATH@
+UDEV_RULES_PATH = @UDEV_RULES_PATH@
+INIT_D_PATH = @INIT_D_PATH@
+
+install-exec-local:
+	$(MKDIR_P) $(DESTDIR)$(MOUNT_FUSE_PATH)
+	$(INSTALL_PROGRAM) $(builddir)/mount.fuse3 $(DESTDIR)$(MOUNT_FUSE_PATH)/mount.fuse3
+	$(MKDIR_P) $(DESTDIR)$(INIT_D_PATH)
+	$(INSTALL_SCRIPT) $(srcdir)/init_script $(DESTDIR)$(INIT_D_PATH)/fuse3
+	@if test -x /usr/sbin/update-rc.d; then \
+		echo "/usr/sbin/update-rc.d fuse start 34 S . start 41 0 6 . || true"; \
+		/usr/sbin/update-rc.d fuse start 34 S . start 41 0 6 . || true; \
+	fi
+
+install-data-local:
+	$(MKDIR_P) $(DESTDIR)$(UDEV_RULES_PATH)
+	$(INSTALL_DATA) $(srcdir)/udev.rules $(DESTDIR)$(UDEV_RULES_PATH)/99-fuse3.rules
+
+uninstall-local:
+	rm -f $(DESTDIR)$(MOUNT_FUSE_PATH)/mount.fuse3
+	rm -f $(DESTDIR)$(UDEV_RULES_PATH)/99-fuse.rules
+	rm -f $(DESTDIR)$(INIT_D_PATH)/fuse
+	@if test -x /usr/sbin/update-rc.d; then \
+		echo "/usr/sbin/update-rc.d fuse remove || true"; \
+		/usr/sbin/update-rc.d fuse remove || true; \
+	fi
diff --git a/util/fusermount.c b/util/fusermount.c
new file mode 100644
index 0000000..0f65435
--- /dev/null
+++ b/util/fusermount.c
@@ -0,0 +1,1289 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+/* This program does the mounting and unmounting of FUSE filesystems */
+
+#define _GNU_SOURCE /* for clone */
+#include <config.h>
+
+#include "mount_util.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <paths.h>
+#include <mntent.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/mount.h>
+#include <sys/fsuid.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sched.h>
+
+#define FUSE_COMMFD_ENV		"_FUSE_COMMFD"
+
+#define FUSE_DEV "/dev/fuse"
+#define FUSE_CONF "/etc/fuse.conf"
+
+#ifndef MS_DIRSYNC
+#define MS_DIRSYNC 128
+#endif
+#ifndef MS_REC
+#define MS_REC 16384
+#endif
+#ifndef MS_PRIVATE
+#define MS_PRIVATE (1<<18)
+#endif
+
+#ifndef UMOUNT_DETACH
+#define UMOUNT_DETACH	0x00000002	/* Just detach from the tree */
+#endif
+#ifndef UMOUNT_NOFOLLOW
+#define UMOUNT_NOFOLLOW	0x00000008	/* Don't follow symlink on umount */
+#endif
+#ifndef UMOUNT_UNUSED
+#define UMOUNT_UNUSED	0x80000000	/* Flag guaranteed to be unused */
+#endif
+
+static const char *progname;
+
+static int user_allow_other = 0;
+static int mount_max = 1000;
+
+static int auto_unmount = 0;
+
+static const char *get_user_name(void)
+{
+	struct passwd *pw = getpwuid(getuid());
+	if (pw != NULL && pw->pw_name != NULL)
+		return pw->pw_name;
+	else {
+		fprintf(stderr, "%s: could not determine username\n", progname);
+		return NULL;
+	}
+}
+
+static uid_t oldfsuid;
+static gid_t oldfsgid;
+
+static void drop_privs(void)
+{
+	if (getuid() != 0) {
+		oldfsuid = setfsuid(getuid());
+		oldfsgid = setfsgid(getgid());
+	}
+}
+
+static void restore_privs(void)
+{
+	if (getuid() != 0) {
+		setfsuid(oldfsuid);
+		setfsgid(oldfsgid);
+	}
+}
+
+#ifndef IGNORE_MTAB
+/*
+ * Make sure that /etc/mtab is checked and updated atomically
+ */
+static int lock_umount(void)
+{
+	const char *mtab_lock = _PATH_MOUNTED ".fuselock";
+	int mtablock;
+	int res;
+	struct stat mtab_stat;
+
+	/* /etc/mtab could be a symlink to /proc/mounts */
+	if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
+		return -1;
+
+	mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
+	if (mtablock == -1) {
+		fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
+			progname, strerror(errno));
+		return -1;
+	}
+	res = lockf(mtablock, F_LOCK, 0);
+	if (res < 0) {
+		fprintf(stderr, "%s: error getting lock: %s\n", progname,
+			strerror(errno));
+		close(mtablock);
+		return -1;
+	}
+
+	return mtablock;
+}
+
+static void unlock_umount(int mtablock)
+{
+	if (mtablock >= 0) {
+		int res;
+
+		res = lockf(mtablock, F_ULOCK, 0);
+		if (res < 0) {
+			fprintf(stderr, "%s: error releasing lock: %s\n",
+				progname, strerror(errno));
+		}
+		close(mtablock);
+	}
+}
+
+static int add_mount(const char *source, const char *mnt, const char *type,
+		     const char *opts)
+{
+	return fuse_mnt_add_mount(progname, source, mnt, type, opts);
+}
+
+static int may_unmount(const char *mnt, int quiet)
+{
+	struct mntent *entp;
+	FILE *fp;
+	const char *user = NULL;
+	char uidstr[32];
+	unsigned uidlen = 0;
+	int found;
+	const char *mtab = _PATH_MOUNTED;
+
+	user = get_user_name();
+	if (user == NULL)
+		return -1;
+
+	fp = setmntent(mtab, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+			strerror(errno));
+		return -1;
+	}
+
+	uidlen = sprintf(uidstr, "%u", getuid());
+
+	found = 0;
+	while ((entp = getmntent(fp)) != NULL) {
+		if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
+		    (strcmp(entp->mnt_type, "fuse") == 0 ||
+		     strcmp(entp->mnt_type, "fuseblk") == 0 ||
+		     strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
+		     strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
+			char *p = strstr(entp->mnt_opts, "user=");
+			if (p &&
+			    (p == entp->mnt_opts || *(p-1) == ',') &&
+			    strcmp(p + 5, user) == 0) {
+				found = 1;
+				break;
+			}
+			/* /etc/mtab is a link pointing to
+			   /proc/mounts: */
+			else if ((p =
+				  strstr(entp->mnt_opts, "user_id=")) &&
+				 (p == entp->mnt_opts ||
+				  *(p-1) == ',') &&
+				 strncmp(p + 8, uidstr, uidlen) == 0 &&
+				 (*(p+8+uidlen) == ',' ||
+				  *(p+8+uidlen) == '\0')) {
+				found = 1;
+				break;
+			}
+		}
+	}
+	endmntent(fp);
+
+	if (!found) {
+		if (!quiet)
+			fprintf(stderr,
+				"%s: entry for %s not found in %s\n",
+				progname, mnt, mtab);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Check whether the file specified in "fusermount3 -u" is really a
+ * mountpoint and not a symlink.  This is necessary otherwise the user
+ * could move the mountpoint away and replace it with a symlink
+ * pointing to an arbitrary mount, thereby tricking fusermount3 into
+ * unmounting that (umount(2) will follow symlinks).
+ *
+ * This is the child process running in a separate mount namespace, so
+ * we don't mess with the global namespace and if the process is
+ * killed for any reason, mounts are automatically cleaned up.
+ *
+ * First make sure nothing is propagated back into the parent
+ * namespace by marking all mounts "private".
+ *
+ * Then bind mount parent onto a stable base where the user can't move
+ * it around.
+ *
+ * Finally check /proc/mounts for an entry matching the requested
+ * mountpoint.  If it's found then we are OK, and the user can't move
+ * it around within the parent directory as rename() will return
+ * EBUSY.  Be careful to ignore any mounts that existed before the
+ * bind.
+ */
+static int check_is_mount_child(void *p)
+{
+	const char **a = p;
+	const char *last = a[0];
+	const char *mnt = a[1];
+	int res;
+	const char *procmounts = "/proc/mounts";
+	int found;
+	FILE *fp;
+	struct mntent *entp;
+	int count;
+
+	res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to mark mounts private: %s\n",
+			progname, strerror(errno));
+		return 1;
+	}
+
+	fp = setmntent(procmounts, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+			procmounts, strerror(errno));
+		return 1;
+	}
+
+	count = 0;
+	while (getmntent(fp) != NULL)
+		count++;
+	endmntent(fp);
+
+	fp = setmntent(procmounts, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "%s: failed to open %s: %s\n", progname,
+			procmounts, strerror(errno));
+		return 1;
+	}
+
+	res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to bind parent to /: %s\n",
+			progname, strerror(errno));
+		return 1;
+	}
+
+	found = 0;
+	while ((entp = getmntent(fp)) != NULL) {
+		if (count > 0) {
+			count--;
+			continue;
+		}
+		if (entp->mnt_dir[0] == '/' &&
+		    strcmp(entp->mnt_dir + 1, last) == 0) {
+			found = 1;
+			break;
+		}
+	}
+	endmntent(fp);
+
+	if (!found) {
+		fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
+		return 1;
+	}
+
+	return 0;
+}
+
+static pid_t clone_newns(void *a)
+{
+	char buf[131072];
+	char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
+
+#ifdef __ia64__
+	extern int __clone2(int (*fn)(void *),
+			    void *child_stack_base, size_t stack_size,
+			    int flags, void *arg, pid_t *ptid,
+			    void *tls, pid_t *ctid);
+
+	return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
+			CLONE_NEWNS, a, NULL, NULL, NULL);
+#else
+	return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
+#endif
+}
+
+static int check_is_mount(const char *last, const char *mnt)
+{
+	pid_t pid, p;
+	int status;
+	const char *a[2] = { last, mnt };
+
+	pid = clone_newns((void *) a);
+	if (pid == (pid_t) -1) {
+		fprintf(stderr, "%s: failed to clone namespace: %s\n",
+			progname, strerror(errno));
+		return -1;
+	}
+	p = waitpid(pid, &status, __WCLONE);
+	if (p == (pid_t) -1) {
+		fprintf(stderr, "%s: waitpid failed: %s\n",
+			progname, strerror(errno));
+		return -1;
+	}
+	if (!WIFEXITED(status)) {
+		fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
+			progname, status);
+		return -1;
+	}
+	if (WEXITSTATUS(status) != 0)
+		return -1;
+
+	return 0;
+}
+
+static int chdir_to_parent(char *copy, const char **lastp)
+{
+	char *tmp;
+	const char *parent;
+	char buf[65536];
+	int res;
+
+	tmp = strrchr(copy, '/');
+	if (tmp == NULL || tmp[1] == '\0') {
+		fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
+			progname, copy);
+		return -1;
+	}
+	if (tmp != copy) {
+		*tmp = '\0';
+		parent = copy;
+		*lastp = tmp + 1;
+	} else if (tmp[1] != '\0') {
+		*lastp = tmp + 1;
+		parent = "/";
+	} else {
+		*lastp = ".";
+		parent = "/";
+	}
+
+	res = chdir(parent);
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to chdir to %s: %s\n",
+			progname, parent, strerror(errno));
+		return -1;
+	}
+
+	if (getcwd(buf, sizeof(buf)) == NULL) {
+		fprintf(stderr, "%s: failed to obtain current directory: %s\n",
+			progname, strerror(errno));
+		return -1;
+	}
+	if (strcmp(buf, parent) != 0) {
+		fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
+			parent, buf);
+		return -1;
+
+	}
+
+	return 0;
+}
+
+/* Check whether the kernel supports UMOUNT_NOFOLLOW flag */
+static int umount_nofollow_support(void)
+{
+	int res = umount2("", UMOUNT_UNUSED);
+	if (res != -1 || errno != EINVAL)
+		return 0;
+
+	res = umount2("", UMOUNT_NOFOLLOW);
+	if (res != -1 || errno != ENOENT)
+		return 0;
+
+	return 1;
+}
+
+static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
+{
+	int res;
+	char *copy;
+	const char *last;
+	int umount_flags = lazy ? UMOUNT_DETACH : 0;
+
+	if (getuid() != 0) {
+		res = may_unmount(mnt, quiet);
+		if (res == -1)
+			return -1;
+	}
+
+	copy = strdup(mnt);
+	if (copy == NULL) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		return -1;
+	}
+
+	res = chdir_to_parent(copy, &last);
+	if (res == -1)
+		goto out;
+
+	if (umount_nofollow_support()) {
+		umount_flags |= UMOUNT_NOFOLLOW;
+	} else {
+		res = check_is_mount(last, mnt);
+		if (res == -1)
+			goto out;
+	}
+
+	res = umount2(last, umount_flags);
+	if (res == -1 && !quiet) {
+		fprintf(stderr, "%s: failed to unmount %s: %s\n",
+			progname, mnt, strerror(errno));
+	}
+
+out:
+	if (res == -1)
+		return -1;
+
+	res = chdir("/");
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+		return -1;
+	}
+
+	return fuse_mnt_remove_mount(progname, mnt);
+}
+
+static int unmount_fuse(const char *mnt, int quiet, int lazy)
+{
+	int res;
+	int mtablock = lock_umount();
+
+	res = unmount_fuse_locked(mnt, quiet, lazy);
+	unlock_umount(mtablock);
+
+	return res;
+}
+
+static int count_fuse_fs(void)
+{
+	struct mntent *entp;
+	int count = 0;
+	const char *mtab = _PATH_MOUNTED;
+	FILE *fp = setmntent(mtab, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
+			strerror(errno));
+		return -1;
+	}
+	while ((entp = getmntent(fp)) != NULL) {
+		if (strcmp(entp->mnt_type, "fuse") == 0 ||
+		    strncmp(entp->mnt_type, "fuse.", 5) == 0)
+			count ++;
+	}
+	endmntent(fp);
+	return count;
+}
+
+
+#else /* IGNORE_MTAB */
+static int count_fuse_fs()
+{
+	return 0;
+}
+
+static int add_mount(const char *source, const char *mnt, const char *type,
+		     const char *opts)
+{
+	(void) source;
+	(void) mnt;
+	(void) type;
+	(void) opts;
+	return 0;
+}
+
+static int unmount_fuse(const char *mnt, int quiet, int lazy)
+{
+	return fuse_mnt_umount(progname, mnt, mnt, lazy);
+}
+#endif /* IGNORE_MTAB */
+
+static void strip_line(char *line)
+{
+	char *s = strchr(line, '#');
+	if (s != NULL)
+		s[0] = '\0';
+	for (s = line + strlen(line) - 1;
+	     s >= line && isspace((unsigned char) *s); s--);
+	s[1] = '\0';
+	for (s = line; isspace((unsigned char) *s); s++);
+	if (s != line)
+		memmove(line, s, strlen(s)+1);
+}
+
+static void parse_line(char *line, int linenum)
+{
+	int tmp;
+	if (strcmp(line, "user_allow_other") == 0)
+		user_allow_other = 1;
+	else if (sscanf(line, "mount_max = %i", &tmp) == 1)
+		mount_max = tmp;
+	else if(line[0])
+		fprintf(stderr,
+			"%s: unknown parameter in %s at line %i: '%s'\n",
+			progname, FUSE_CONF, linenum, line);
+}
+
+static void read_conf(void)
+{
+	FILE *fp = fopen(FUSE_CONF, "r");
+	if (fp != NULL) {
+		int linenum = 1;
+		char line[256];
+		int isnewline = 1;
+		while (fgets(line, sizeof(line), fp) != NULL) {
+			if (isnewline) {
+				if (line[strlen(line)-1] == '\n') {
+					strip_line(line);
+					parse_line(line, linenum);
+				} else {
+					isnewline = 0;
+				}
+			} else if(line[strlen(line)-1] == '\n') {
+				fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
+
+				isnewline = 1;
+			}
+			if (isnewline)
+				linenum ++;
+		}
+		if (!isnewline) {
+			fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
+
+		}
+		fclose(fp);
+	} else if (errno != ENOENT) {
+		fprintf(stderr, "%s: failed to open %s: %s\n",
+			progname, FUSE_CONF, strerror(errno));
+	}
+}
+
+static int begins_with(const char *s, const char *beg)
+{
+	if (strncmp(s, beg, strlen(beg)) == 0)
+		return 1;
+	else
+		return 0;
+}
+
+struct mount_flags {
+	const char *opt;
+	unsigned long flag;
+	int on;
+	int safe;
+};
+
+static struct mount_flags mount_flags[] = {
+	{"rw",	    MS_RDONLY,	    0, 1},
+	{"ro",	    MS_RDONLY,	    1, 1},
+	{"suid",    MS_NOSUID,	    0, 0},
+	{"nosuid",  MS_NOSUID,	    1, 1},
+	{"dev",	    MS_NODEV,	    0, 0},
+	{"nodev",   MS_NODEV,	    1, 1},
+	{"exec",    MS_NOEXEC,	    0, 1},
+	{"noexec",  MS_NOEXEC,	    1, 1},
+	{"async",   MS_SYNCHRONOUS, 0, 1},
+	{"sync",    MS_SYNCHRONOUS, 1, 1},
+	{"atime",   MS_NOATIME,	    0, 1},
+	{"noatime", MS_NOATIME,	    1, 1},
+	{"dirsync", MS_DIRSYNC,	    1, 1},
+	{NULL,	    0,		    0, 0}
+};
+
+static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
+{
+	int i;
+
+	for (i = 0; mount_flags[i].opt != NULL; i++) {
+		const char *opt = mount_flags[i].opt;
+		if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
+			*on = mount_flags[i].on;
+			*flag = mount_flags[i].flag;
+			if (!mount_flags[i].safe && getuid() != 0) {
+				*flag = 0;
+				fprintf(stderr,
+					"%s: unsafe option %s ignored\n",
+					progname, opt);
+			}
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int add_option(char **optsp, const char *opt, unsigned expand)
+{
+	char *newopts;
+	if (*optsp == NULL)
+		newopts = strdup(opt);
+	else {
+		unsigned oldsize = strlen(*optsp);
+		unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
+		newopts = (char *) realloc(*optsp, newsize);
+		if (newopts)
+			sprintf(newopts + oldsize, ",%s", opt);
+	}
+	if (newopts == NULL) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		return -1;
+	}
+	*optsp = newopts;
+	return 0;
+}
+
+static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
+{
+	int i;
+	int l;
+
+	if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
+		return -1;
+
+	for (i = 0; mount_flags[i].opt != NULL; i++) {
+		if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
+		    add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
+			return -1;
+	}
+
+	if (add_option(mnt_optsp, opts, 0) == -1)
+		return -1;
+	/* remove comma from end of opts*/
+	l = strlen(*mnt_optsp);
+	if ((*mnt_optsp)[l-1] == ',')
+		(*mnt_optsp)[l-1] = '\0';
+	if (getuid() != 0) {
+		const char *user = get_user_name();
+		if (user == NULL)
+			return -1;
+
+		if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
+			return -1;
+		strcat(*mnt_optsp, user);
+	}
+	return 0;
+}
+
+static int opt_eq(const char *s, unsigned len, const char *opt)
+{
+	if(strlen(opt) == len && strncmp(s, opt, len) == 0)
+		return 1;
+	else
+		return 0;
+}
+
+static int get_string_opt(const char *s, unsigned len, const char *opt,
+			  char **val)
+{
+	int i;
+	unsigned opt_len = strlen(opt);
+	char *d;
+
+	if (*val)
+		free(*val);
+	*val = (char *) malloc(len - opt_len + 1);
+	if (!*val) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		return 0;
+	}
+
+	d = *val;
+	s += opt_len;
+	len -= opt_len;
+	for (i = 0; i < len; i++) {
+		if (s[i] == '\\' && i + 1 < len)
+			i++;
+		*d++ = s[i];
+	}
+	*d = '\0';
+	return 1;
+}
+
+static int do_mount(const char *mnt, char **typep, mode_t rootmode,
+		    int fd, const char *opts, const char *dev, char **sourcep,
+		    char **mnt_optsp)
+{
+	int res;
+	int flags = MS_NOSUID | MS_NODEV;
+	char *optbuf;
+	char *mnt_opts = NULL;
+	const char *s;
+	char *d;
+	char *fsname = NULL;
+	char *subtype = NULL;
+	char *source = NULL;
+	char *type = NULL;
+	int blkdev = 0;
+
+	optbuf = (char *) malloc(strlen(opts) + 128);
+	if (!optbuf) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		return -1;
+	}
+
+	for (s = opts, d = optbuf; *s;) {
+		unsigned len;
+		const char *fsname_str = "fsname=";
+		const char *subtype_str = "subtype=";
+		for (len = 0; s[len]; len++) {
+			if (s[len] == '\\' && s[len + 1])
+				len++;
+			else if (s[len] == ',')
+				break;
+		}
+		if (begins_with(s, fsname_str)) {
+			if (!get_string_opt(s, len, fsname_str, &fsname))
+				goto err;
+		} else if (begins_with(s, subtype_str)) {
+			if (!get_string_opt(s, len, subtype_str, &subtype))
+				goto err;
+		} else if (opt_eq(s, len, "blkdev")) {
+			if (getuid() != 0) {
+				fprintf(stderr,
+					"%s: option blkdev is privileged\n",
+					progname);
+				goto err;
+			}
+			blkdev = 1;
+		} else if (opt_eq(s, len, "auto_unmount")) {
+			auto_unmount = 1;
+		} else if (!begins_with(s, "fd=") &&
+			   !begins_with(s, "rootmode=") &&
+			   !begins_with(s, "user_id=") &&
+			   !begins_with(s, "group_id=")) {
+			int on;
+			int flag;
+			int skip_option = 0;
+			if (opt_eq(s, len, "large_read")) {
+				struct utsname utsname;
+				unsigned kmaj, kmin;
+				res = uname(&utsname);
+				if (res == 0 &&
+				    sscanf(utsname.release, "%u.%u",
+					   &kmaj, &kmin) == 2 &&
+				    (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
+					fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
+					skip_option = 1;
+				}
+			}
+			if (getuid() != 0 && !user_allow_other &&
+			    (opt_eq(s, len, "allow_other") ||
+			     opt_eq(s, len, "allow_root"))) {
+				fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
+				goto err;
+			}
+			if (!skip_option) {
+				if (find_mount_flag(s, len, &on, &flag)) {
+					if (on)
+						flags |= flag;
+					else
+						flags  &= ~flag;
+				} else {
+					memcpy(d, s, len);
+					d += len;
+					*d++ = ',';
+				}
+			}
+		}
+		s += len;
+		if (*s)
+			s++;
+	}
+	*d = '\0';
+	res = get_mnt_opts(flags, optbuf, &mnt_opts);
+	if (res == -1)
+		goto err;
+
+	sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
+		fd, rootmode, getuid(), getgid());
+
+	source = malloc((fsname ? strlen(fsname) : 0) +
+			(subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
+
+	type = malloc((subtype ? strlen(subtype) : 0) + 32);
+	if (!type || !source) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		goto err;
+	}
+
+	if (subtype)
+		sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
+	else
+		strcpy(type, blkdev ? "fuseblk" : "fuse");
+
+	if (fsname)
+		strcpy(source, fsname);
+	else
+		strcpy(source, subtype ? subtype : dev);
+
+	res = mount(source, mnt, type, flags, optbuf);
+	if (res == -1 && errno == ENODEV && subtype) {
+		/* Probably missing subtype support */
+		strcpy(type, blkdev ? "fuseblk" : "fuse");
+		if (fsname) {
+			if (!blkdev)
+				sprintf(source, "%s#%s", subtype, fsname);
+		} else {
+			strcpy(source, type);
+		}
+
+		res = mount(source, mnt, type, flags, optbuf);
+	}
+	if (res == -1 && errno == EINVAL) {
+		/* It could be an old version not supporting group_id */
+		sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
+			fd, rootmode, getuid());
+		res = mount(source, mnt, type, flags, optbuf);
+	}
+	if (res == -1) {
+		int errno_save = errno;
+		if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
+			fprintf(stderr, "%s: 'fuseblk' support missing\n",
+				progname);
+		else
+			fprintf(stderr, "%s: mount failed: %s\n", progname,
+				strerror(errno_save));
+		goto err;
+	}
+	*sourcep = source;
+	*typep = type;
+	*mnt_optsp = mnt_opts;
+	free(fsname);
+	free(optbuf);
+
+	return 0;
+
+err:
+	free(fsname);
+	free(subtype);
+	free(source);
+	free(type);
+	free(mnt_opts);
+	free(optbuf);
+	return -1;
+}
+
+static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
+{
+	int res;
+	const char *mnt = *mntp;
+	const char *origmnt = mnt;
+
+	res = lstat(mnt, stbuf);
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
+			progname, mnt, strerror(errno));
+		return -1;
+	}
+
+	/* No permission checking is done for root */
+	if (getuid() == 0)
+		return 0;
+
+	if (S_ISDIR(stbuf->st_mode)) {
+		res = chdir(mnt);
+		if (res == -1) {
+			fprintf(stderr,
+				"%s: failed to chdir to mountpoint: %s\n",
+				progname, strerror(errno));
+			return -1;
+		}
+		mnt = *mntp = ".";
+		res = lstat(mnt, stbuf);
+		if (res == -1) {
+			fprintf(stderr,
+				"%s: failed to access mountpoint %s: %s\n",
+				progname, origmnt, strerror(errno));
+			return -1;
+		}
+
+		if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
+			fprintf(stderr, "%s: mountpoint %s not owned by user\n",
+				progname, origmnt);
+			return -1;
+		}
+
+		res = access(mnt, W_OK);
+		if (res == -1) {
+			fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
+				progname, origmnt);
+			return -1;
+		}
+	} else if (S_ISREG(stbuf->st_mode)) {
+		static char procfile[256];
+		*mountpoint_fd = open(mnt, O_WRONLY);
+		if (*mountpoint_fd == -1) {
+			fprintf(stderr, "%s: failed to open %s: %s\n",
+				progname, mnt, strerror(errno));
+			return -1;
+		}
+		res = fstat(*mountpoint_fd, stbuf);
+		if (res == -1) {
+			fprintf(stderr,
+				"%s: failed to access mountpoint %s: %s\n",
+				progname, mnt, strerror(errno));
+			return -1;
+		}
+		if (!S_ISREG(stbuf->st_mode)) {
+			fprintf(stderr,
+				"%s: mountpoint %s is no longer a regular file\n",
+				progname, mnt);
+			return -1;
+		}
+
+		sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
+		*mntp = procfile;
+	} else {
+		fprintf(stderr,
+			"%s: mountpoint %s is not a directory or a regular file\n",
+			progname, mnt);
+		return -1;
+	}
+
+
+	return 0;
+}
+
+static int try_open(const char *dev, char **devp, int silent)
+{
+	int fd = open(dev, O_RDWR);
+	if (fd != -1) {
+		*devp = strdup(dev);
+		if (*devp == NULL) {
+			fprintf(stderr, "%s: failed to allocate memory\n",
+				progname);
+			close(fd);
+			fd = -1;
+		}
+	} else if (errno == ENODEV ||
+		   errno == ENOENT)/* check for ENOENT too, for the udev case */
+		return -2;
+	else if (!silent) {
+		fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
+			strerror(errno));
+	}
+	return fd;
+}
+
+static int try_open_fuse_device(char **devp)
+{
+	int fd;
+
+	drop_privs();
+	fd = try_open(FUSE_DEV, devp, 0);
+	restore_privs();
+	return fd;
+}
+
+static int open_fuse_device(char **devp)
+{
+	int fd = try_open_fuse_device(devp);
+	if (fd >= -1)
+		return fd;
+
+	fprintf(stderr,
+		"%s: fuse device not found, try 'modprobe fuse' first\n",
+		progname);
+
+	return -1;
+}
+
+
+static int mount_fuse(const char *mnt, const char *opts)
+{
+	int res;
+	int fd;
+	char *dev;
+	struct stat stbuf;
+	char *type = NULL;
+	char *source = NULL;
+	char *mnt_opts = NULL;
+	const char *real_mnt = mnt;
+	int mountpoint_fd = -1;
+
+	fd = open_fuse_device(&dev);
+	if (fd == -1)
+		return -1;
+
+	drop_privs();
+	read_conf();
+
+	if (getuid() != 0 && mount_max != -1) {
+		int mount_count = count_fuse_fs();
+		if (mount_count >= mount_max) {
+			fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
+			goto fail_close_fd;
+		}
+	}
+
+	res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
+	restore_privs();
+	if (res != -1)
+		res = do_mount(real_mnt, &type, stbuf.st_mode & S_IFMT,
+			       fd, opts, dev, &source, &mnt_opts);
+
+	if (mountpoint_fd != -1)
+		close(mountpoint_fd);
+
+	if (res == -1)
+		goto fail_close_fd;
+
+	res = chdir("/");
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+		goto fail_close_fd;
+	}
+
+	if (geteuid() == 0) {
+		res = add_mount(source, mnt, type, mnt_opts);
+		if (res == -1) {
+			/* Can't clean up mount in a non-racy way */
+			goto fail_close_fd;
+		}
+	}
+
+out_free:
+	free(source);
+	free(type);
+	free(mnt_opts);
+	free(dev);
+
+	return fd;
+
+fail_close_fd:
+	close(fd);
+	fd = -1;
+	goto out_free;
+}
+
+static int send_fd(int sock_fd, int fd)
+{
+	int retval;
+	struct msghdr msg;
+	struct cmsghdr *p_cmsg;
+	struct iovec vec;
+	size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
+	int *p_fds;
+	char sendchar = 0;
+
+	msg.msg_control = cmsgbuf;
+	msg.msg_controllen = sizeof(cmsgbuf);
+	p_cmsg = CMSG_FIRSTHDR(&msg);
+	p_cmsg->cmsg_level = SOL_SOCKET;
+	p_cmsg->cmsg_type = SCM_RIGHTS;
+	p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+	p_fds = (int *) CMSG_DATA(p_cmsg);
+	*p_fds = fd;
+	msg.msg_controllen = p_cmsg->cmsg_len;
+	msg.msg_name = NULL;
+	msg.msg_namelen = 0;
+	msg.msg_iov = &vec;
+	msg.msg_iovlen = 1;
+	msg.msg_flags = 0;
+	/* "To pass file descriptors or credentials you need to send/read at
+	 * least one byte" (man 7 unix) */
+	vec.iov_base = &sendchar;
+	vec.iov_len = sizeof(sendchar);
+	while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
+	if (retval != 1) {
+		perror("sending file descriptor");
+		return -1;
+	}
+	return 0;
+}
+
+static void usage(void)
+{
+	printf("%s: [options] mountpoint\n"
+	       "Options:\n"
+	       " -h		    print help\n"
+	       " -V		    print version\n"
+	       " -o opt[,opt...]   mount options\n"
+	       " -u		    unmount\n"
+	       " -q		    quiet\n"
+	       " -z		    lazy unmount\n",
+	       progname);
+	exit(1);
+}
+
+static void show_version(void)
+{
+	printf("fusermount3 version: %s\n", PACKAGE_VERSION);
+	exit(0);
+}
+
+int main(int argc, char *argv[])
+{
+	sigset_t sigset;
+	int ch;
+	int fd;
+	int res;
+	char *origmnt;
+	char *mnt;
+	static int unmount = 0;
+	static int lazy = 0;
+	static int quiet = 0;
+	char *commfd;
+	int cfd;
+	const char *opts = "";
+
+	static const struct option long_opts[] = {
+		{"unmount", no_argument, NULL, 'u'},
+		{"lazy",    no_argument, NULL, 'z'},
+		{"quiet",   no_argument, NULL, 'q'},
+		{"help",    no_argument, NULL, 'h'},
+		{"version", no_argument, NULL, 'V'},
+		{0, 0, 0, 0}};
+
+	progname = strdup(argv[0]);
+	if (progname == NULL) {
+		fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
+		exit(1);
+	}
+
+	while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
+				 NULL)) != -1) {
+		switch (ch) {
+		case 'h':
+			usage();
+			break;
+
+		case 'V':
+			show_version();
+			break;
+
+		case 'o':
+			opts = optarg;
+			break;
+
+		case 'u':
+			unmount = 1;
+			break;
+
+		case 'z':
+			lazy = 1;
+			break;
+
+		case 'q':
+			quiet = 1;
+			break;
+
+		default:
+			exit(1);
+		}
+	}
+
+	if (lazy && !unmount) {
+		fprintf(stderr, "%s: -z can only be used with -u\n", progname);
+		exit(1);
+	}
+
+	if (optind >= argc) {
+		fprintf(stderr, "%s: missing mountpoint argument\n", progname);
+		exit(1);
+	} else if (argc > optind + 1) {
+		fprintf(stderr, "%s: extra arguments after the mountpoint\n",
+			progname);
+		exit(1);
+	}
+
+	origmnt = argv[optind];
+
+	drop_privs();
+	mnt = fuse_mnt_resolve_path(progname, origmnt);
+	if (mnt != NULL) {
+		res = chdir("/");
+		if (res == -1) {
+			fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+			goto err_out;
+		}
+	}
+	restore_privs();
+	if (mnt == NULL)
+		exit(1);
+
+	umask(033);
+	if (unmount)
+		goto do_unmount;
+
+	commfd = getenv(FUSE_COMMFD_ENV);
+	if (commfd == NULL) {
+		fprintf(stderr, "%s: old style mounting not supported\n",
+			progname);
+		goto err_out;
+	}
+
+	fd = mount_fuse(mnt, opts);
+	if (fd == -1)
+		goto err_out;
+
+	cfd = atoi(commfd);
+	res = send_fd(cfd, fd);
+	if (res == -1)
+		goto err_out;
+	close(fd);
+
+	if (!auto_unmount) {
+		free(mnt);
+		return 0;
+	}
+
+	/* Become a daemon and wait for the parent to exit or die.
+	   ie For the control socket to get closed.
+	   btw We don't want to use daemon() function here because
+	   it forks and messes with the file descriptors. */
+	setsid();
+	res = chdir("/");
+	if (res == -1) {
+		fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
+		goto err_out;
+	}
+
+	sigfillset(&sigset);
+	sigprocmask(SIG_BLOCK, &sigset, NULL);
+
+	lazy  = 1;
+	quiet = 1;
+
+	while (1) {
+		unsigned char buf[16];
+		int n = recv(cfd, buf, sizeof(buf), 0);
+		if (!n)
+			break;
+
+		if (n < 0) {
+			if (errno == EINTR)
+				continue;
+			break;
+		}
+	}
+
+do_unmount:
+	if (geteuid() == 0)
+		res = unmount_fuse(mnt, quiet, lazy);
+	else {
+		res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
+		if (res == -1 && !quiet)
+			fprintf(stderr,
+				"%s: failed to unmount %s: %s\n",
+				progname, mnt, strerror(errno));
+	}
+	if (res == -1)
+		goto err_out;
+	return 0;
+
+err_out:
+	free(mnt);
+	exit(1);
+}
diff --git a/util/init_script b/util/init_script
new file mode 100755
index 0000000..4815e7b
--- /dev/null
+++ b/util/init_script
@@ -0,0 +1,89 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          fuse
+# Required-Start:    
+# Should-Start:      udev
+# Required-Stop:     
+# Default-Start:     S
+# Default-Stop:
+# Short-Description: Start and stop fuse.
+# Description:       Load the fuse module and mount the fuse control
+#	filesystem.
+### END INIT INFO
+
+set -e
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+MOUNTPOINT=/sys/fs/fuse/connections
+
+# Gracefully exit if the package has been removed.
+which fusermount3 &>/dev/null || exit 5
+
+case "$1" in
+    start|restart|force-reload)
+	if ! grep -qw fuse /proc/filesystems; then
+		echo -n "Loading fuse module"
+		if ! modprobe fuse >/dev/null 2>&1; then
+			echo " failed!"
+			exit 1
+		else
+			echo "."
+		fi
+	else
+		echo "Fuse filesystem already available."
+	fi
+	if grep -qw fusectl /proc/filesystems && \
+	   ! grep -qw $MOUNTPOINT /proc/mounts; then
+		echo -n "Mounting fuse control filesystem"
+		if ! mount -t fusectl fusectl $MOUNTPOINT >/dev/null 2>&1; then
+			echo " failed!"
+			exit 1
+		else
+			echo "."
+		fi
+	else
+		echo "Fuse control filesystem already available."
+	fi
+	;;
+    stop)
+	if ! grep -qw fuse /proc/filesystems; then
+		echo "Fuse filesystem not loaded."
+		exit 7
+	fi
+	if grep -qw $MOUNTPOINT /proc/mounts; then
+		echo -n "Unmounting fuse control filesystem"
+		if ! umount $MOUNTPOINT >/dev/null 2>&1; then
+			echo " failed!"
+		else
+			echo "."
+		fi
+	else
+		echo "Fuse control filesystem not mounted."
+	fi
+	if grep -qw "^fuse" /proc/modules; then
+		echo -n "Unloading fuse module"
+		if ! rmmod fuse >/dev/null 2>&1; then
+			echo " failed!"
+		else
+			echo "."
+		fi
+	else
+		echo "Fuse module not loaded."
+	fi
+	;;
+    status)
+	echo -n "Checking fuse filesystem"
+	if ! grep -qw fuse /proc/filesystems; then
+		echo " not available."
+		exit 3
+	else
+		echo " ok."
+	fi
+	;;
+  *)
+	echo "Usage: $0 {start|stop|restart|force-reload|status}"
+	exit 1
+	;;
+esac
+
+exit 0
diff --git a/util/mount.fuse.c b/util/mount.fuse.c
new file mode 100644
index 0000000..363b12b
--- /dev/null
+++ b/util/mount.fuse.c
@@ -0,0 +1,232 @@
+/*
+  FUSE: Filesystem in Userspace
+  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
+
+  This program can be distributed under the terms of the GNU GPL.
+  See the file COPYING.
+*/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+static char *progname;
+
+static char *xstrdup(const char *s)
+{
+	char *t = strdup(s);
+	if (!t) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		exit(1);
+	}
+	return t;
+}
+
+static void *xrealloc(void *oldptr, size_t size)
+{
+	void *ptr = realloc(oldptr, size);
+	if (!ptr) {
+		fprintf(stderr, "%s: failed to allocate memory\n", progname);
+		exit(1);
+	}
+	return ptr;
+}
+
+static void add_arg(char **cmdp, const char *opt)
+{
+	size_t optlen = strlen(opt);
+	size_t cmdlen = *cmdp ? strlen(*cmdp) : 0;
+	char *cmd = xrealloc(*cmdp, cmdlen + optlen * 4 + 4);
+	char *s;
+	s = cmd + cmdlen;
+	if (*cmdp)
+		*s++ = ' ';
+
+	*s++ = '\'';
+	for (; *opt; opt++) {
+		if (*opt == '\'') {
+			*s++ = '\'';
+			*s++ = '\\';
+			*s++ = '\'';
+			*s++ = '\'';
+		} else
+			*s++ = *opt;
+	}
+	*s++ = '\'';
+	*s = '\0';
+	*cmdp = cmd;
+}
+
+static char *add_option(const char *opt, char *options)
+{
+	int oldlen = options ? strlen(options) : 0;
+
+	options = xrealloc(options, oldlen + 1 + strlen(opt) + 1);
+	if (!oldlen)
+		strcpy(options, opt);
+	else {
+		strcat(options, ",");
+		strcat(options, opt);
+	}
+	return options;
+}
+
+int main(int argc, char *argv[])
+{
+	char *type = NULL;
+	char *source;
+	const char *mountpoint;
+	char *basename;
+	char *options = NULL;
+	char *command = NULL;
+	char *setuid = NULL;
+	int i;
+	int dev = 1;
+	int suid = 1;
+
+	progname = argv[0];
+	basename = strrchr(argv[0], '/');
+	if (basename)
+		basename++;
+	else
+		basename = argv[0];
+
+	if (strncmp(basename, "mount.fuse.", 11) == 0)
+		type = basename + 11;
+	if (strncmp(basename, "mount.fuseblk.", 14) == 0)
+		type = basename + 14;
+
+	if (type && !type[0])
+		type = NULL;
+
+	if (argc < 3) {
+		fprintf(stderr,
+			"usage: %s %s destination [-t type] [-o opt[,opts...]]\n",
+			progname, type ? "source" : "type#[source]");
+		exit(1);
+	}
+
+	source = argv[1];
+	if (!source[0])
+		source = NULL;
+
+	mountpoint = argv[2];
+
+	for (i = 3; i < argc; i++) {
+		if (strcmp(argv[i], "-v") == 0) {
+			continue;
+		} else if (strcmp(argv[i], "-t") == 0) {
+			i++;
+
+			if (i == argc) {
+				fprintf(stderr,
+					"%s: missing argument to option '-t'\n",
+					progname);
+				exit(1);
+			}
+			type = argv[i];
+			if (strncmp(type, "fuse.", 5) == 0)
+				type += 5;
+			else if (strncmp(type, "fuseblk.", 8) == 0)
+				type += 8;
+
+			if (!type[0]) {
+				fprintf(stderr,
+					"%s: empty type given as argument to option '-t'\n",
+					progname);
+				exit(1);
+			}
+		} else	if (strcmp(argv[i], "-o") == 0) {
+			char *opts;
+			char *opt;
+			i++;
+			if (i == argc)
+				break;
+
+			opts = xstrdup(argv[i]);
+			opt = strtok(opts, ",");
+			while (opt) {
+				int j;
+				int ignore = 0;
+				const char *ignore_opts[] = { "",
+							      "user",
+							      "nouser",
+							      "users",
+							      "auto",
+							      "noauto",
+							      "_netdev",
+							      NULL};
+				if (strncmp(opt, "setuid=", 7) == 0) {
+					setuid = xstrdup(opt + 7);
+					ignore = 1;
+				}
+				for (j = 0; ignore_opts[j]; j++)
+					if (strcmp(opt, ignore_opts[j]) == 0)
+						ignore = 1;
+
+				if (!ignore) {
+					if (strcmp(opt, "nodev") == 0)
+						dev = 0;
+					else if (strcmp(opt, "nosuid") == 0)
+						suid = 0;
+
+					options = add_option(opt, options);
+				}
+				opt = strtok(NULL, ",");
+			}
+		}
+	}
+
+	if (dev)
+		options = add_option("dev", options);
+	if (suid)
+		options = add_option("suid", options);
+
+	if (!type) {
+		if (source) {
+			type = xstrdup(source);
+			source = strchr(type, '#');
+			if (source)
+				*source++ = '\0';
+			if (!type[0]) {
+				fprintf(stderr, "%s: empty filesystem type\n",
+					progname);
+				exit(1);
+			}
+		} else {
+			fprintf(stderr, "%s: empty source\n", progname);
+			exit(1);
+		}
+	}
+
+	add_arg(&command, type);
+	if (source)
+		add_arg(&command, source);
+	add_arg(&command, mountpoint);
+	if (options) {
+		add_arg(&command, "-o");
+		add_arg(&command, options);
+	}
+
+	if (setuid && setuid[0]) {
+		char *sucommand = command;
+		command = NULL;
+		add_arg(&command, "su");
+		add_arg(&command, "-");
+		add_arg(&command, setuid);
+		add_arg(&command, "-c");
+		add_arg(&command, sucommand);
+	} else if (!getenv("HOME")) {
+		/* Hack to make filesystems work in the boot environment */
+		setenv("HOME", "/root", 0);
+	}
+
+	execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+	fprintf(stderr, "%s: failed to execute /bin/sh: %s\n", progname,
+		strerror(errno));
+	return 1;
+}
diff --git a/util/udev.rules b/util/udev.rules
new file mode 100644
index 0000000..9585111
--- /dev/null
+++ b/util/udev.rules
@@ -0,0 +1 @@
+KERNEL=="fuse", MODE="0666"